Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor telemetry-consent file and add tests #5557

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions v-next/hardhat/src/internal/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { printErrorMessages } from "./error-handler.js";
import { getGlobalHelpString } from "./helpers/getGlobalHelpString.js";
import { getHelpString } from "./helpers/getHelpString.js";
import { initHardhat } from "./init/init.js";
import { getTelemetryConsent } from "./telemetry/telemetry-consent.js";
import { ensureTelemetryConsent } from "./telemetry/telemetry-permissions.js";
import { printVersionMessage } from "./version.js";

export async function main(
Expand All @@ -67,8 +67,7 @@ export async function main(
return await initHardhat();
}

// TODO: the consent will be enabled in the other PRs related to telemetry
const _telemetryConsent = await getTelemetryConsent();
await ensureTelemetryConsent();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the new logic, the telemetry consent will be read directly when needed.
This function purpose is to ensure that telemetry-consent is registered, so no value needs to be returned


if (builtinGlobalOptions.configPath === undefined) {
builtinGlobalOptions.configPath = await resolveHardhatConfigPath();
Expand Down
78 changes: 0 additions & 78 deletions v-next/hardhat/src/internal/cli/telemetry/telemetry-consent.ts

This file was deleted.

117 changes: 117 additions & 0 deletions v-next/hardhat/src/internal/cli/telemetry/telemetry-permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import path from "node:path";

import { getConfigDir } from "@ignored/hardhat-vnext-core/global-dir";
import { isCi } from "@ignored/hardhat-vnext-utils/ci";
import {
exists,
readJsonFile,
writeJsonFile,
} from "@ignored/hardhat-vnext-utils/fs";

import { confirmationPromptWithTimeout } from "../prompt/prompt.js";

interface TelemetryConsent {
consent: boolean;
}

/**
* Ensure that the user's telemetry consent is set. If the consent is already provided, returns the answer.
* If not, prompts the user to provide it.
* Consent is only asked in interactive environments.
*
* @returns True if the user consents to telemetry and if current environment supports telemetry, false otherwise.
*/
export async function ensureTelemetryConsent(): Promise<boolean> {
if (!isTelemetryAllowedInEnvironment()) {
return false;
}

const consent = await getTelemetryConsent();
if (consent !== undefined) {
return consent;
}

// Telemetry consent not provided yet, ask for it
return requestTelemetryConsent();
}

/**
* Checks whether telemetry is supported in the current environment and whether the user has provided consent.
*
* @returns True if the user consents to telemetry and if current environment supports telemetry, false otherwise.
*/
export async function isTelemetryAllowed(): Promise<boolean> {
if (!isTelemetryAllowedInEnvironment()) {
return false;
}

const consent = await getTelemetryConsent();
return consent !== undefined ? consent : false;
}

/**
* Retrieves the user's telemetry consent status from the consent file.
*
* @returns True if the user consents to telemetry, false if they do not consent,
* and undefined if no consent has been provided.
*/
export async function getTelemetryConsent(): Promise<boolean | undefined> {
const telemetryConsentFilePath = await getTelemetryConsentFilePath();

if (await exists(telemetryConsentFilePath)) {
// Telemetry consent was already provided, hence return the answer
return (await readJsonFile<TelemetryConsent>(telemetryConsentFilePath))
.consent;
}

return undefined;
}

/**
* Determines if telemetry is allowed in the current environment.
* This function checks various environmental factors to decide if telemetry data can be collected.
* It verifies that the environment is not a continuous integration (CI) environment, that the terminal is interactive,
* and that telemetry has not been explicitly disabled through an environment variable.
*
* @returns True if telemetry is allowed in the environment, false otherwise.
*/
export function isTelemetryAllowedInEnvironment(): boolean {
return (
(!isCi() &&
process.stdout.isTTY === true &&
process.env.HARDHAT_DISABLE_TELEMETRY_PROMPT !== "true") ||
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST === "true" // Used in tests to force telemetry execution
);
}

async function getTelemetryConsentFilePath() {
const configDir = await getConfigDir();
return path.join(configDir, "telemetry-consent.json");
}

async function requestTelemetryConsent(): Promise<boolean> {
const consent = await confirmTelemetryConsent();

if (consent === undefined) {
return false;
}

// Store user's consent choice
await writeJsonFile(await getTelemetryConsentFilePath(), { consent });

// TODO: this will be enabled in a following PR as soon as the function to send telemetry is implemented
// const subprocessFilePath = path.join(
// path.dirname(fileURLToPath(import.meta.url)),
// "report-telemetry-consent.js",
// );
// await spawnDetachedSubProcess(subprocessFilePath, [consent ? "yes" : "no"]);

return consent;
}

async function confirmTelemetryConsent(): Promise<boolean | undefined> {
return confirmationPromptWithTimeout(
"telemetryConsent",
"Help us improve Hardhat with anonymous crash reports & basic usage data?",
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import assert from "node:assert/strict";
import path from "node:path";
import { afterEach, beforeEach, describe, it } from "node:test";

import { getConfigDir } from "@ignored/hardhat-vnext-core/global-dir";
import { remove, writeJsonFile } from "@ignored/hardhat-vnext-utils/fs";

import {
getTelemetryConsent,
isTelemetryAllowed,
} from "../../../../src/internal/cli/telemetry/telemetry-permissions.js";

async function setTelemetryConsentFile(consent: boolean) {
const configDir = await getConfigDir();
const filePath = path.join(configDir, "telemetry-consent.json");
await writeJsonFile(filePath, { consent });
}

async function deleteTelemetryConsentFile() {
const configDir = await getConfigDir();
const filePath = path.join(configDir, "telemetry-consent.json");
await remove(filePath);
}

describe("telemetry-permissions", () => {
beforeEach(async () => {
delete process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST;

await deleteTelemetryConsentFile();
});

afterEach(async () => {
delete process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST;

await deleteTelemetryConsentFile();
});

describe("isTelemetryAllowed", () => {
it("should return false because not an interactive environment", async () => {
await setTelemetryConsentFile(true); // Needed to be sure that the file is not read and the process exits before

const res = await isTelemetryAllowed();
assert.equal(res, false);
});

it("should return false because the user did not give telemetry consent", async () => {
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";
await setTelemetryConsentFile(false);

const res = await isTelemetryAllowed();
assert.equal(res, false);
});

it("should return false because the telemetry consent is not set", async () => {
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";

const res = await isTelemetryAllowed();
assert.equal(res, false);
});

it("should return true because the user gave telemetry consent", async () => {
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";
await setTelemetryConsentFile(true);

const res = await isTelemetryAllowed();
assert.equal(res, true);
});
});

it("should return undefined because the telemetry consent is not set", async () => {
// All other possible results are tested in the previous tests, they are included in the the function 'isTelemetryAllowed'
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";

const res = await getTelemetryConsent();

assert.equal(res, undefined);
});
});
Loading