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

feat(cli): send telemetry data from cli #3067

Merged
merged 13 commits into from
Nov 28, 2022
Merged
6 changes: 6 additions & 0 deletions .changeset/many-shoes-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@pankod/refine-cli": minor
---

Added: `whoami` command to `refine-cli`. It's shows details of the development environment.
Added: telemetry to `refine-cli` commands.
54 changes: 43 additions & 11 deletions documentation/docs/further-readings/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,38 @@ title: Telemetry
sidebar_label: Telemetry
---

# Telemetry
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

# Telemetry

## Summary

**refine** implements a **simple** and **transparent** telemetry module for collecting usage statistics defined in a **very limited scope**.
**refine** implements a **simple** and **transparent** telemetry module for collecting usage statistics defined in a **very limited scope**.

Tracking is totally **secure** and **anonymous**. It includes no personally identifiable information and **does not use cookies**. Participation is optional and users may easily **opt-out**.


## Why do we need this?

We try to answer the question **how many users are actively using the Refine framework**. This information is critical for open-source projects like Refine to better understand their communities and measure their growth metrics.


## How do we collect data?

The tracking happens when a Refine application is loaded on the user's browser. On application init, a single HTTP request is sent to [https://telemetry.refine.dev](https://telemetry.refine.dev). The request body is encoded with Base64 to be decoded on Refine servers.
<Tabs>
<TabItem value="refine-core" label="refine core" default>
The tracking happens when a Refine application is loaded on the user's browser. On application init, a single HTTP request is sent to <a target="_blank" rel="noopener" href="https://telemetry.refine.dev">https://telemetry.refine.dev</a>. The request body is encoded with Base64 to be decoded on Refine servers.

There are no consequent requests for that session, as we do NOT collect any behavioral information such as _page views_, _button clicks_, etc.


## What is collected?

The HTTP call has a JSON payload having the following application-specific attributes:

| Value | Type | Description |
| ------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
| providers | boolean[] | List of providers used in the project (auth, data, router, live, notification, auditLog, i18n or accessControl) |
| version | string | Version of the refine package. |
| resourceCount | number | Number of total resources. |
| Value | Type | Description |
| ------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
| providers | `boolean[]` | List of providers used in the project (auth, data, router, live, notification, auditLog, i18n or accessControl) |
| version | `string` | Version of the refine package. |
| resourceCount | `number` | Number of total resources. |

Additionally, the following information is extracted and collected from the HTTP header:

Expand All @@ -48,3 +49,34 @@ Additionally, the following information is extracted and collected from the HTTP
## How to opt-out?

You can opt out of telemetry by simply adding `disableTelemetry` prop to the `<Refine />` component.

</TabItem>

<TabItem value="refine-cli" label="refine CLI">

After running a command with the `refine` CLI, a single HTTP request is sent to <a target="_blank" rel="noopener" href="https://telemetry.refine.dev/cli">https://telemetry.refine.dev/cli</a>.

## What is collected?

| Value | Type | Description |
| ---------------- | ------------------------------------------- | ------------------------------------------------------------ |
| nodeEnv | `string` | Specifies the environment in which an application is running. |
| nodeVersion | `string` | Installed Node.js version. |
| os | `string` | Operating system name. |
| osVersion | `string` | Operating system version. |
| command | `string` | Running script name. |
| packages | `{ "name": "string", "version": "string" }[]` | Installed `refine` packages. |
| projectFramework | `string` | Installed `react` framework. |

Additionally, the following information is extracted and collected from the HTTP header:

| Value | Description |
| ---------- | ----------------------------------------------------- |
| IP Address | IP Address of the machine the request is coming from. |

## How to opt-out?

You can opt out of telemetry by simply adding `REFINE_NO_TELEMETRY=true` to environment variables.

</TabItem>
</Tabs>
21 changes: 13 additions & 8 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,40 @@
},
"devDependencies": {
"@esbuild-plugins/node-resolve": "^0.1.4",
"@types/envinfo": "^7.8.1",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.2.5",
"@types/jest": "^26.0.24",
"@types/temp": "^0.9.1",
"@types/jscodeshift": "^0.11.5",
"@types/node-fetch": "^2.6.2",
"@types/temp": "^0.9.1",
"jest": "^27.5.1",
"ts-jest": "^27.1.3",
"tsup": "^5.11.13",
"typescript": "^4.7.4",
"figlet": "^1.5.2"
},
"dependencies": {
"tslib": "^2.3.1",
"handlebars": "^4.7.7",
"fs-extra": "^10.1.0",
"temp": "^0.9.4",
"pluralize": "^8.0.0",
"chalk": "^4.1.2",
"commander": "9.4.1",
"conf": "^10.2.0",
"dotenv": "^16.0.3",
"envinfo": "^7.8.1",
"execa": "^5.1.1",
"fs-extra": "^10.1.0",
"handlebars": "^4.7.7",
"ink": "^3.2.0",
"ink-table": "^3.0.0",
"inquirer": "^8.2.5",
"jscodeshift": "^0.14.0",
"node-env-type": "^0.0.8",
"node-fetch": "^2.6.7",
"ora": "^5.4.1",
"pluralize": "^8.0.0",
"preferred-pm": "^3.0.3",
"jscodeshift": "^0.14.0",
"semver-diff": "^3.1.1"
"semver-diff": "^3.1.1",
"temp": "^0.9.4",
"tslib": "^2.3.1"
},
"author": "refine",
"license": "MIT",
Expand Down
37 changes: 37 additions & 0 deletions packages/cli/src/commands/whoami/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getInstalledRefinePackages } from "@utils/package";
import { Command } from "commander";
import envinfo from "envinfo";
import ora from "ora";

const whoami = (program: Command) => {
return program
.command("whoami")
.description("View the details of the development environment")
.action(action);
};

const action = async () => {
const spinner = ora("Loading environment details...").start();
const info = await envinfo.run(
{
System: ["OS", "CPU"],
Binaries: ["Node", "Yarn", "npm"],
Browsers: ["Chrome", "Firefox", "Safari"],
},
{ showNotFound: true, markdown: true },
);

const packages = await getInstalledRefinePackages();
const packagesMarkdown = packages
.map((pkg) => {
return ` - ${pkg.name}: ${pkg.version}`;
})
.join("\n");

spinner.stop();
console.log(info);
console.log("## Refine Packages:");
console.log(packagesMarkdown);
};

export default whoami;
1 change: 1 addition & 0 deletions packages/cli/src/definitions/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./projectTypes";
export * from "./uiFrameworks";
export * from "./package";
export * from "./node";
8 changes: 8 additions & 0 deletions packages/cli/src/definitions/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type NODE_ENV =
| "development"
| "production"
| "test"
| "continuous-integration"
| "system-integration-testing"
| "user-acceptance-testing"
| "custom";
10 changes: 10 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import figlet from "figlet";

import checkUpdates from "@commands/check-updates";
import createResource from "@commands/create-resource";
import whoami from "@commands/whoami";
import update from "@commands/update";
import { dev, build, start, run } from "@commands/runner";
import "@utils/env";
import { getPackageJson } from "@utils/package";
import { telemetryHook } from "@telemetryindex";

const bootstrap = () => {
let packageJson;
Expand Down Expand Up @@ -62,6 +64,14 @@ const bootstrap = () => {
build(program);
start(program);
run(program);
whoami(program);

program.hook("postAction", (thisCommand) => {
const command = thisCommand.args[0];
if (["run"].includes(command)) return;

telemetryHook();
});

program.parse(process.argv);

Expand Down
50 changes: 50 additions & 0 deletions packages/cli/src/telemetry/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NODE_ENV } from "@definitions/node";
import { ProjectTypes } from "@definitions/projectTypes";
import { ENV, parseNodeEnv } from "@utils/env";
import { getOS } from "@utils/os";
import { getInstalledRefinePackages } from "@utils/package";
import { getProjectType } from "@utils/project";
import fetch from "node-fetch";

interface TelemetryData {
nodeEnv?: NODE_ENV;
nodeVersion: string;
os: string;
osVersion: string;
command: string;
packages: {
name: string;
version: string;
}[];
projectFramework: ProjectTypes;
}

export const getTelemetryData = async (): Promise<TelemetryData> => {
const os = await getOS();

const data = {
nodeEnv: parseNodeEnv(),
nodeVersion: process.version,
os: os.name,
osVersion: os.version,
command: process.argv[2],
packages: await getInstalledRefinePackages(),
projectFramework: getProjectType(),
};

return data;
};

export const telemetryHook = async () => {
if (ENV.REFINE_NO_TELEMETRY === "true") return;

try {
const data = await getTelemetryData();

fetch("https://telemetry.refine.dev/cli", {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
} catch (error) {}
};
14 changes: 14 additions & 0 deletions packages/cli/src/utils/env/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { NODE_ENV } from "@definitions/node";
import { env } from "node-env-type";
import * as dotenv from "dotenv";
dotenv.config();

export const ENV = {
REFINE_NO_TELEMETRY: process.env.REFINE_NO_TELEMETRY || "false",
UPDATE_NOTIFIER_IS_DISABLED:
process.env.UPDATE_NOTIFIER_IS_DISABLED || "false",
UPDATE_NOTIFIER_CACHE_TTL:
process.env.UPDATE_NOTIFIER_CACHE_TTL || 1000 * 60 * 60 * 24, // 24 hours,
};

export const parseNodeEnv = (): NODE_ENV => {
if (env.isDev) return "development";
if (env.isProd) return "production";
if (env.isTest) return "test";
if (env.isCI) return "continuous-integration";
if (env.isUAT) return "user-acceptance-testing";
if (env.isSIT) return "system-integration-testing";

return "custom";
};
25 changes: 25 additions & 0 deletions packages/cli/src/utils/os/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import envinfo from "envinfo";
import os from "os";

export const getOSType = () => {
const osPlatform = os.type();

const types: Record<string, "macOS" | "Linux" | "Windows"> = {
Darwin: "macOS",
Linux: "Linux",
Windows_NT: "Windows",
};

return types[osPlatform];
};

export const getOS = async () => {
// returns as a ['OS', 'macOS Mojave 10.14.5']
alicanerdurmaz marked this conversation as resolved.
Show resolved Hide resolved
const [_, OSInfo] =
(await envinfo.helpers.getOSInfo()) as unknown as string[];

return {
name: getOSType(),
version: OSInfo,
};
};
45 changes: 13 additions & 32 deletions packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"include": [
"src"
],
"include": ["src"],
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
Expand All @@ -10,34 +8,17 @@
"importHelpers": false,
"strict": true,
"paths": {
"@definitions/*": [
"src/definitions/*"
],
"@definitions": [
"src/definitions"
],
"@utils/*": [
"src/utils/*"
],
"@utils": [
"src/utils"
],
"@commands/*": [
"src/commands/*"
],
"@commands": [
"src/commands"
],
"@components/*": [
"src/components/*"
],
"@components": [
"src/components"
]
"@definitions/*": ["src/definitions/*"],
"@definitions": ["src/definitions"],
"@utils/*": ["src/utils/*"],
"@utils": ["src/utils"],
"@commands/*": ["src/commands/*"],
"@commands": ["src/commands"],
"@components/*": ["src/components/*"],
"@components": ["src/components"],
"@telemetry*": ["src/telemetry/*"],
"@telemetry": ["src/telemetry"]
},
"typeRoots": [
"./src/types",
"../../node_modules/@types"
]
"typeRoots": ["./src/types", "../../node_modules/@types"]
}
}
}