Skip to content

Commit 2bd890b

Browse files
Refactor telemetry consent file + tests (#5557)
1 parent 9e9fada commit 2bd890b

File tree

4 files changed

+197
-81
lines changed

4 files changed

+197
-81
lines changed

v-next/hardhat/src/internal/cli/main.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { printErrorMessages } from "./error-handler.js";
4040
import { getGlobalHelpString } from "./helpers/getGlobalHelpString.js";
4141
import { getHelpString } from "./helpers/getHelpString.js";
4242
import { initHardhat } from "./init/init.js";
43-
import { getTelemetryConsent } from "./telemetry/telemetry-consent.js";
43+
import { ensureTelemetryConsent } from "./telemetry/telemetry-permissions.js";
4444
import { printVersionMessage } from "./version.js";
4545

4646
export async function main(
@@ -67,8 +67,7 @@ export async function main(
6767
return await initHardhat();
6868
}
6969

70-
// TODO: the consent will be enabled in the other PRs related to telemetry
71-
const _telemetryConsent = await getTelemetryConsent();
70+
await ensureTelemetryConsent();
7271

7372
if (builtinGlobalOptions.configPath === undefined) {
7473
builtinGlobalOptions.configPath = await resolveHardhatConfigPath();

v-next/hardhat/src/internal/cli/telemetry/telemetry-consent.ts

-78
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import path from "node:path";
2+
3+
import { getConfigDir } from "@ignored/hardhat-vnext-core/global-dir";
4+
import { isCi } from "@ignored/hardhat-vnext-utils/ci";
5+
import {
6+
exists,
7+
readJsonFile,
8+
writeJsonFile,
9+
} from "@ignored/hardhat-vnext-utils/fs";
10+
11+
import { confirmationPromptWithTimeout } from "../prompt/prompt.js";
12+
13+
interface TelemetryConsent {
14+
consent: boolean;
15+
}
16+
17+
/**
18+
* Ensure that the user's telemetry consent is set. If the consent is already provided, returns the answer.
19+
* If not, prompts the user to provide it.
20+
* Consent is only asked in interactive environments.
21+
*
22+
* @returns True if the user consents to telemetry and if current environment supports telemetry, false otherwise.
23+
*/
24+
export async function ensureTelemetryConsent(): Promise<boolean> {
25+
if (!isTelemetryAllowedInEnvironment()) {
26+
return false;
27+
}
28+
29+
const consent = await getTelemetryConsent();
30+
if (consent !== undefined) {
31+
return consent;
32+
}
33+
34+
// Telemetry consent not provided yet, ask for it
35+
return requestTelemetryConsent();
36+
}
37+
38+
/**
39+
* Checks whether telemetry is supported in the current environment and whether the user has provided consent.
40+
*
41+
* @returns True if the user consents to telemetry and if current environment supports telemetry, false otherwise.
42+
*/
43+
export async function isTelemetryAllowed(): Promise<boolean> {
44+
if (!isTelemetryAllowedInEnvironment()) {
45+
return false;
46+
}
47+
48+
const consent = await getTelemetryConsent();
49+
return consent !== undefined ? consent : false;
50+
}
51+
52+
/**
53+
* Retrieves the user's telemetry consent status from the consent file.
54+
*
55+
* @returns True if the user consents to telemetry, false if they do not consent,
56+
* and undefined if no consent has been provided.
57+
*/
58+
export async function getTelemetryConsent(): Promise<boolean | undefined> {
59+
const telemetryConsentFilePath = await getTelemetryConsentFilePath();
60+
61+
if (await exists(telemetryConsentFilePath)) {
62+
// Telemetry consent was already provided, hence return the answer
63+
return (await readJsonFile<TelemetryConsent>(telemetryConsentFilePath))
64+
.consent;
65+
}
66+
67+
return undefined;
68+
}
69+
70+
/**
71+
* Determines if telemetry is allowed in the current environment.
72+
* This function checks various environmental factors to decide if telemetry data can be collected.
73+
* It verifies that the environment is not a continuous integration (CI) environment, that the terminal is interactive,
74+
* and that telemetry has not been explicitly disabled through an environment variable.
75+
*
76+
* @returns True if telemetry is allowed in the environment, false otherwise.
77+
*/
78+
export function isTelemetryAllowedInEnvironment(): boolean {
79+
return (
80+
(!isCi() &&
81+
process.stdout.isTTY === true &&
82+
process.env.HARDHAT_DISABLE_TELEMETRY_PROMPT !== "true") ||
83+
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST === "true" // Used in tests to force telemetry execution
84+
);
85+
}
86+
87+
async function getTelemetryConsentFilePath() {
88+
const configDir = await getConfigDir();
89+
return path.join(configDir, "telemetry-consent.json");
90+
}
91+
92+
async function requestTelemetryConsent(): Promise<boolean> {
93+
const consent = await confirmTelemetryConsent();
94+
95+
if (consent === undefined) {
96+
return false;
97+
}
98+
99+
// Store user's consent choice
100+
await writeJsonFile(await getTelemetryConsentFilePath(), { consent });
101+
102+
// TODO: this will be enabled in a following PR as soon as the function to send telemetry is implemented
103+
// const subprocessFilePath = path.join(
104+
// path.dirname(fileURLToPath(import.meta.url)),
105+
// "report-telemetry-consent.js",
106+
// );
107+
// await spawnDetachedSubProcess(subprocessFilePath, [consent ? "yes" : "no"]);
108+
109+
return consent;
110+
}
111+
112+
async function confirmTelemetryConsent(): Promise<boolean | undefined> {
113+
return confirmationPromptWithTimeout(
114+
"telemetryConsent",
115+
"Help us improve Hardhat with anonymous crash reports & basic usage data?",
116+
);
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import assert from "node:assert/strict";
2+
import path from "node:path";
3+
import { afterEach, beforeEach, describe, it } from "node:test";
4+
5+
import { getConfigDir } from "@ignored/hardhat-vnext-core/global-dir";
6+
import { remove, writeJsonFile } from "@ignored/hardhat-vnext-utils/fs";
7+
8+
import {
9+
getTelemetryConsent,
10+
isTelemetryAllowed,
11+
} from "../../../../src/internal/cli/telemetry/telemetry-permissions.js";
12+
13+
async function setTelemetryConsentFile(consent: boolean) {
14+
const configDir = await getConfigDir();
15+
const filePath = path.join(configDir, "telemetry-consent.json");
16+
await writeJsonFile(filePath, { consent });
17+
}
18+
19+
async function deleteTelemetryConsentFile() {
20+
const configDir = await getConfigDir();
21+
const filePath = path.join(configDir, "telemetry-consent.json");
22+
await remove(filePath);
23+
}
24+
25+
describe("telemetry-permissions", () => {
26+
beforeEach(async () => {
27+
delete process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST;
28+
29+
await deleteTelemetryConsentFile();
30+
});
31+
32+
afterEach(async () => {
33+
delete process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST;
34+
35+
await deleteTelemetryConsentFile();
36+
});
37+
38+
describe("isTelemetryAllowed", () => {
39+
it("should return false because not an interactive environment", async () => {
40+
await setTelemetryConsentFile(true); // Needed to be sure that the file is not read and the process exits before
41+
42+
const res = await isTelemetryAllowed();
43+
assert.equal(res, false);
44+
});
45+
46+
it("should return false because the user did not give telemetry consent", async () => {
47+
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";
48+
await setTelemetryConsentFile(false);
49+
50+
const res = await isTelemetryAllowed();
51+
assert.equal(res, false);
52+
});
53+
54+
it("should return false because the telemetry consent is not set", async () => {
55+
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";
56+
57+
const res = await isTelemetryAllowed();
58+
assert.equal(res, false);
59+
});
60+
61+
it("should return true because the user gave telemetry consent", async () => {
62+
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";
63+
await setTelemetryConsentFile(true);
64+
65+
const res = await isTelemetryAllowed();
66+
assert.equal(res, true);
67+
});
68+
});
69+
70+
it("should return undefined because the telemetry consent is not set", async () => {
71+
// All other possible results are tested in the previous tests, they are included in the the function 'isTelemetryAllowed'
72+
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";
73+
74+
const res = await getTelemetryConsent();
75+
76+
assert.equal(res, undefined);
77+
});
78+
});

0 commit comments

Comments
 (0)