Skip to content

Commit e0fd5c4

Browse files
authored
Merge pull request #47 from mapbox/telemetry-config
Add mapbox config set telemetry off
2 parents 8d009d5 + 7854265 commit e0fd5c4

12 files changed

Lines changed: 286 additions & 32 deletions

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,15 @@ that may never merge. They are not releases and are not listed here.
269269
code; it does find a `~/.mapbox/history` directory it didn't before, and
270270
`mapbox config list` now reports a second key, `history`.
271271

272+
- `mapbox config set telemetry off` turns telemetry off for good, in every
273+
shell, the way `MAPBOX_CLI_NO_TELEMETRY=1` does for one: the run's event,
274+
the `User-Agent` markers and the update notice. The run that turns it off
275+
records nothing either. `mapbox config list` now also reports `telemetry`,
276+
and `mapbox doctor` reports it as `telemetry_persisted`; its
277+
`telemetry_allowed` still means the environment variable alone. Version
278+
0.3.0 doesn't know this key, and its `mapbox config set` drops it from the
279+
file, so with two installs, set it again after using the older one.
280+
272281
- Each run sends one `cli.command` telemetry event to Mapbox, from a
273282
background process the command doesn't wait for, with your own token
274283
(`--token`, `MAPBOX_ACCESS_TOKEN` or your login) or, when you have none,

‎README.md‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,9 @@ deeper where that reads better, as in `mapbox styles draft get`.
282282
parameters and sample output.
283283

284284
Every API request a command makes sends `User-Agent: mapbox-cli/<version>`
285-
and nothing else about you or your machine. `MAPBOX_CLI_NO_TELEMETRY=1`
286-
keeps even future markers out of that header.
285+
and nothing else about you or your machine. `MAPBOX_CLI_NO_TELEMETRY=1` or
286+
`mapbox config set telemetry off` keeps even future markers out of that
287+
header.
287288

288289
Each run also sends one event to Mapbox:
289290

@@ -305,7 +306,7 @@ or your login) when you have one, and with a token built into the CLI
305306
otherwise; Mapbox Events keeps the token an event was sent with, and the
306307
account it belongs to, alongside the event. A build from source has no
307308
built-in token, so with no token of your own the event is dropped.
308-
`MAPBOX_CLI_NO_TELEMETRY=1` turns it off.
309+
`MAPBOX_CLI_NO_TELEMETRY=1` or `mapbox config set telemetry off` turns it off.
309310

310311
### Diagnostics and settings
311312

@@ -572,7 +573,7 @@ kept narrow:
572573
| What it sends | A `GET` for the channel's `latest/manifest.json`, with no token, no account, no command, and nothing about you or your machine beyond `User-Agent: mapbox-cli/<version>` |
573574
| When | At most once a day, and only when stderr is a terminal, so CI and piped runs never check and never print |
574575
| Where | A detached background process. Your command never waits on it: offline, the timing is unchanged and nothing is printed |
575-
| Off | `MAPBOX_NO_UPDATE_CHECK=1`, or `MAPBOX_CLI_NO_TELEMETRY=1`, which silences this too, for the shell session it's set in |
576+
| Off | `MAPBOX_NO_UPDATE_CHECK=1`, or `MAPBOX_CLI_NO_TELEMETRY=1`, which silences this too, for the shell session it's set in; `mapbox config set telemetry off` silences it for good |
576577

577578
`~/.mapbox/update-check.json` (or `$MAPBOX_CONFIG_DIR`) holds the answer
578579
between runs. A build that names no release channel never checks at all, and
@@ -601,7 +602,7 @@ mapbox history show be40d711 # or one run, by any prefix of its id
601602
are not recorded. `mapbox config set history off` turns history off for
602603
good, and `MAPBOX_HISTORY=0` for one shell; with it off, nothing is written
603604
and no directory is created, but what was already recorded stays until you
604-
delete `~/.mapbox/history`. `MAPBOX_CLI_NO_TELEMETRY` does not affect it.
605+
delete `~/.mapbox/history`. Neither telemetry opt-out affects it.
605606

606607
### Diagnostic logs
607608

@@ -669,6 +670,12 @@ our CLIs by setting
669670
MAPBOX_CLI_NO_TELEMETRY=1
670671
```
671672

673+
or, to turn it off in every shell,
674+
675+
```sh
676+
mapbox config set telemetry off
677+
```
678+
672679
For additional information on our data processing activities and your
673680
related rights, please see our Mapbox
674681
[Privacy Policy](https://www.mapbox.com/legal/privacy).

‎docs/commands.md‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4265,6 +4265,7 @@ was set in, and stays in every future shell instead.
42654265
| `update-check` | `on` | The update notice; mirrors `MAPBOX_NO_UPDATE_CHECK` (see [Update notices](../README.md#update-notices)) |
42664266
| `history` | `on` | [Command history](../README.md#command-history), read by `mapbox history`; `MAPBOX_HISTORY=0` or `=1` overrides it for a session |
42674267
| `log` | `off` | [Diagnostic logs](../README.md#diagnostic-logs), shown by `mapbox history show`; `MAPBOX_LOG=1` or `=0` overrides it for a session. Needs `history` on: `config set log on` with history off fails with `history_required` |
4268+
| `telemetry` | `on` | Telemetry — the run's event, the `User-Agent` markers and the update notice; mirrors `MAPBOX_CLI_NO_TELEMETRY` (see [Privacy](../README.md#privacy)) |
42684269

42694270
### `mapbox config get`
42704271

@@ -4276,7 +4277,7 @@ than failing, the same forgiving read the update-check cache itself uses.
42764277

42774278
| Parameter | Effect |
42784279
| --- | --- |
4279-
| `<key>` | Which setting to read: `update-check`, `history` or `log`. |
4280+
| `<key>` | Which setting to read: `update-check`, `history`, `log` or `telemetry`. |
42804281

42814282
#### Examples
42824283

@@ -4315,7 +4316,7 @@ without an environment variable.
43154316

43164317
| Parameter | Effect |
43174318
| --- | --- |
4318-
| `<key>` | Which setting to change: `update-check`, `history` or `log`. |
4319+
| `<key>` | Which setting to change: `update-check`, `history`, `log` or `telemetry`. |
43194320
| `<value>` | `on` or `off`. |
43204321

43214322
#### Examples
@@ -4374,6 +4375,7 @@ mapbox config list
43744375
update-check on
43754376
history on
43764377
log off
4378+
telemetry on
43774379
```
43784380

43794381
</td><td>
@@ -4391,6 +4393,10 @@ log off
43914393
{
43924394
"key": "log",
43934395
"value": false
4396+
},
4397+
{
4398+
"key": "telemetry",
4399+
"value": true
43944400
}
43954401
]
43964402
```
@@ -4409,7 +4415,7 @@ default, a key explicitly set to the old default value does not.
44094415

44104416
| Parameter | Effect |
44114417
| --- | --- |
4412-
| `<key>` | Which setting to clear: `update-check`, `history` or `log`. |
4418+
| `<key>` | Which setting to clear: `update-check`, `history`, `log` or `telemetry`. |
44134419

44144420
#### Examples
44154421

@@ -4703,6 +4709,7 @@ Telemetry: on
47034709
"proxy": { "active": [] },
47044710
"switches": {
47054711
"telemetry_allowed": true,
4712+
"telemetry_persisted": true,
47064713
"update_check_env_opt_out": false,
47074714
"update_check_persisted": true
47084715
},
@@ -4719,6 +4726,10 @@ Telemetry: on
47194726
</td></tr>
47204727
</table>
47214728

4729+
In `switches`, `telemetry_allowed` is `MAPBOX_CLI_NO_TELEMETRY` alone and
4730+
`telemetry_persisted` is `mapbox config set telemetry`; telemetry is on only
4731+
when both are `true`.
4732+
47224733
With `--verify`, a `connectivity` object joins the JSON and a `Reachable:`
47234734
line joins the text — `{ "reachable": true, "status": 200 }`, or `{
47244735
"reachable": false }` (plus an `error` field under `--debug`) when the

‎src/config.rs‎

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use std::path::PathBuf;
1919
use anyhow::{Context, Result};
2020
use clap::builder::PossibleValuesParser;
2121
use clap::{Arg, ArgMatches, Command};
22-
use serde::{Deserialize, Serialize};
23-
use serde_json::{json, Value};
22+
use serde::Serialize;
23+
use serde_json::{json, Map, Value};
2424

2525
use crate::auth;
2626
use crate::output::{self, CliError, Mode};
@@ -33,7 +33,8 @@ const CONFIG_FILE: &str = "config.json";
3333
const UPDATE_CHECK_KEY: &str = "update-check";
3434
const HISTORY_KEY: &str = "history";
3535
const LOG_KEY: &str = "log";
36-
const KEYS: &[&str] = &[UPDATE_CHECK_KEY, HISTORY_KEY, LOG_KEY];
36+
const TELEMETRY_KEY: &str = "telemetry";
37+
const KEYS: &[&str] = &[UPDATE_CHECK_KEY, HISTORY_KEY, LOG_KEY, TELEMETRY_KEY];
3738

3839
const ON: &str = "on";
3940
const OFF: &str = "off";
@@ -42,14 +43,43 @@ const OFF: &str = "off";
4243
/// default is `on` — distinct from `Some(true)`, which is someone turning it
4344
/// back on after having turned it off, but read identically by
4445
/// [`update_check_setting`].
45-
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
46+
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
4647
struct Config {
4748
#[serde(default, skip_serializing_if = "Option::is_none")]
4849
update_check: Option<bool>,
4950
#[serde(default, skip_serializing_if = "Option::is_none")]
5051
history: Option<bool>,
5152
#[serde(default, skip_serializing_if = "Option::is_none")]
5253
log: Option<bool>,
54+
#[serde(default, skip_serializing_if = "Option::is_none")]
55+
telemetry: Option<bool>,
56+
/// Keys this version doesn't know, kept so that writing the file doesn't
57+
/// erase a setting a newer version saved.
58+
#[serde(flatten)]
59+
rest: Map<String, Value>,
60+
}
61+
62+
impl Config {
63+
/// Reads each key on its own, so one malformed value resets only that
64+
/// key. Parsed as a whole, a single bad value would reset every key,
65+
/// `telemetry` included, and an opt-out would quietly turn back on.
66+
fn parse(text: &str) -> Config {
67+
let Ok(Value::Object(mut rest)) = serde_json::from_str(text) else {
68+
return Config::default();
69+
};
70+
let mut flag = |key: &str| rest.remove(key).and_then(|value| value.as_bool());
71+
let update_check = flag("update_check");
72+
let history = flag("history");
73+
let log = flag("log");
74+
let telemetry = flag("telemetry");
75+
Config {
76+
update_check,
77+
history,
78+
log,
79+
telemetry,
80+
rest,
81+
}
82+
}
5383
}
5484

5585
fn config_path() -> Option<PathBuf> {
@@ -64,7 +94,7 @@ fn config_path() -> Option<PathBuf> {
6494
fn read_config() -> Config {
6595
config_path()
6696
.and_then(|path| std::fs::read_to_string(path).ok())
67-
.and_then(|text| serde_json::from_str(&text).ok())
97+
.map(|text| Config::parse(&text))
6898
.unwrap_or_default()
6999
}
70100

@@ -102,6 +132,12 @@ pub fn log_enabled() -> bool {
102132
read_config().log.unwrap_or(false)
103133
}
104134

135+
/// Whether telemetry is on, per the persisted setting. Read through
136+
/// [`crate::telemetry::telemetry_allowed`], alongside `MAPBOX_CLI_NO_TELEMETRY`.
137+
pub fn telemetry_enabled() -> bool {
138+
read_config().telemetry.unwrap_or(true)
139+
}
140+
105141
fn on_off(enabled: bool) -> &'static str {
106142
if enabled {
107143
ON
@@ -118,6 +154,7 @@ fn resolve(config: &Config, key: &str) -> bool {
118154
UPDATE_CHECK_KEY => update_check_setting(config),
119155
HISTORY_KEY => config.history.unwrap_or(true),
120156
LOG_KEY => config.log.unwrap_or(false),
157+
TELEMETRY_KEY => config.telemetry.unwrap_or(true),
121158
_ => unreachable!("clap's value_parser restricts `key` to {KEYS:?}"),
122159
}
123160
}
@@ -132,6 +169,7 @@ fn clear(config: &mut Config, key: &str) {
132169
UPDATE_CHECK_KEY => config.update_check = None,
133170
HISTORY_KEY => config.history = None,
134171
LOG_KEY => config.log = None,
172+
TELEMETRY_KEY => config.telemetry = None,
135173
_ => unreachable!("clap's value_parser restricts `key` to {KEYS:?}"),
136174
}
137175
}
@@ -214,6 +252,7 @@ pub fn set(matches: &ArgMatches, mode: Mode) -> Result<()> {
214252
UPDATE_CHECK_KEY => config.update_check = Some(enabled),
215253
HISTORY_KEY => config.history = Some(enabled),
216254
LOG_KEY => config.log = Some(enabled),
255+
TELEMETRY_KEY => config.telemetry = Some(enabled),
217256
_ => unreachable!("clap's value_parser restricts `key` to {KEYS:?}"),
218257
}
219258
write_config(&config)?;
@@ -273,15 +312,32 @@ mod tests {
273312
};
274313
let text = serde_json::to_string(&off).expect("serialize");
275314
assert_eq!(text, r#"{"update_check":false}"#);
276-
let read: Config = serde_json::from_str(&text).expect("deserialize");
277-
assert_eq!(read, off);
315+
assert_eq!(Config::parse(&text), off);
278316

279317
// A file from before this key existed, or one with nothing set yet.
280-
let empty: Config = serde_json::from_str("{}").expect("an empty object");
318+
let empty = Config::parse("{}");
281319
assert_eq!(empty.update_check, None);
282320
assert!(update_check_setting(&empty));
283321
}
284322

323+
#[test]
324+
fn a_malformed_value_resets_only_its_own_key() {
325+
let config = Config::parse(r#"{"telemetry":false,"history":"off"}"#);
326+
assert_eq!(config.telemetry, Some(false));
327+
assert_eq!(config.history, None);
328+
assert_eq!(Config::parse("not json"), Config::default());
329+
}
330+
331+
#[test]
332+
fn keys_from_a_newer_version_survive_a_write() {
333+
let mut config = Config::parse(r#"{"history":true,"future_key":[1]}"#);
334+
config.history = Some(false);
335+
assert_eq!(
336+
serde_json::to_string(&config).expect("serialize"),
337+
r#"{"history":false,"future_key":[1]}"#
338+
);
339+
}
340+
285341
#[test]
286342
fn on_and_off_round_trip_through_on_off() {
287343
assert_eq!(on_off(true), ON);

‎src/doctor.rs‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,11 @@ impl ProxyReport {
238238

239239
#[derive(Serialize)]
240240
struct SwitchesReport {
241+
/// `MAPBOX_CLI_NO_TELEMETRY` alone, as before `telemetry_persisted`
242+
/// existed; a script that read it keeps reading the same fact.
241243
telemetry_allowed: bool,
244+
/// `mapbox config set telemetry`; the event needs this and the above.
245+
telemetry_persisted: bool,
242246
update_check_env_opt_out: bool,
243247
update_check_persisted: bool,
244248
}
@@ -248,7 +252,8 @@ impl SwitchesReport {
248252
let env_opted_out =
249253
std::env::var(update_check::NO_UPDATE_CHECK_ENV).is_ok_and(|v| !v.trim().is_empty());
250254
SwitchesReport {
251-
telemetry_allowed: telemetry::telemetry_allowed(),
255+
telemetry_allowed: telemetry::env_allows_telemetry(),
256+
telemetry_persisted: config::telemetry_enabled(),
252257
update_check_env_opt_out: env_opted_out,
253258
update_check_persisted: config::update_check_enabled(),
254259
}
@@ -264,14 +269,19 @@ impl SwitchesReport {
264269
/// and `update_check_env_opt_out` alone, so `MAPBOX_CLI_NO_TELEMETRY=1`
265270
/// printed "Update check: on" for a check that would not run.
266271
fn update_check_on(&self) -> bool {
267-
self.update_check_persisted && !self.update_check_env_opt_out && self.telemetry_allowed
272+
self.update_check_persisted
273+
&& !self.update_check_env_opt_out
274+
&& self.telemetry_allowed
275+
&& self.telemetry_persisted
268276
}
269277

270278
fn update_check_field(&self) -> (&'static str, String) {
271279
let reason = if self.update_check_env_opt_out {
272280
format!(" ({} is set)", update_check::NO_UPDATE_CHECK_ENV)
273281
} else if !self.telemetry_allowed {
274282
" (MAPBOX_CLI_NO_TELEMETRY silences this too)".to_string()
283+
} else if !self.telemetry_persisted {
284+
" (mapbox config set telemetry off silences this too)".to_string()
275285
} else if !self.update_check_persisted {
276286
" (mapbox config set update-check off)".to_string()
277287
} else {
@@ -282,8 +292,14 @@ impl SwitchesReport {
282292
}
283293

284294
fn telemetry_field(&self) -> (&'static str, String) {
285-
let state = if self.telemetry_allowed { "on" } else { "off" };
286-
("Telemetry:", state.to_string())
295+
let value = if !self.telemetry_allowed {
296+
"off (MAPBOX_CLI_NO_TELEMETRY is set)"
297+
} else if !self.telemetry_persisted {
298+
"off (mapbox config set telemetry off)"
299+
} else {
300+
"on"
301+
};
302+
("Telemetry:", value.to_string())
287303
}
288304
}
289305

0 commit comments

Comments
 (0)