Anonymous Telemetry Opt Out Available (#550)

* Added anonymous telemetry with opt-out.
This commit is contained in:
Alek Petuskey 2023-02-16 00:11:53 -08:00 committed by GitHub
parent d0b47e1c23
commit b36680fefd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 122 additions and 44 deletions

18
poetry.lock generated
View File

@ -628,14 +628,14 @@ plugins = ["importlib-metadata"]
[[package]] [[package]]
name = "pyright" name = "pyright"
version = "1.1.293" version = "1.1.294"
description = "Command line wrapper for pyright" description = "Command line wrapper for pyright"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "pyright-1.1.293-py3-none-any.whl", hash = "sha256:afc05309e775a9869c864da4e8c0c7a3e3be9d8fe202e780c3bae981bbb13936"}, {file = "pyright-1.1.294-py3-none-any.whl", hash = "sha256:5b27e28a1cfc60cea707fd3b644769fa6dd0b194481cdcc2399cf2a51cc5a846"},
{file = "pyright-1.1.293.tar.gz", hash = "sha256:9397fdfcbc684fe5b87abbf9c27f540fe3b8d75999a5f187519cae1d065be38c"}, {file = "pyright-1.1.294.tar.gz", hash = "sha256:fea5fed3d6a3f02259e622c901e86a7b8bcf237d35e1cdfe01d0e0723768dcb6"},
] ]
[package.dependencies] [package.dependencies]
@ -832,14 +832,14 @@ files = [
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "67.2.0" version = "67.3.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "setuptools-67.2.0-py3-none-any.whl", hash = "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c"}, {file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"},
{file = "setuptools-67.2.0.tar.gz", hash = "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"}, {file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"},
] ]
[package.extras] [package.extras]
@ -1082,14 +1082,14 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.4.0" version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+" description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
] ]
[[package]] [[package]]

View File

@ -1 +0,0 @@
0.1.16

View File

@ -0,0 +1,3 @@
{
"version": "0.1.16"
}

View File

@ -27,6 +27,9 @@ class Config(Base):
# The redis url. # The redis url.
redis_url: Optional[str] = None redis_url: Optional[str] = None
# Telemetry opt-in.
telemetry_enabled: bool = True
# The deploy url. # The deploy url.
deploy_url: Optional[str] = None deploy_url: Optional[str] = None

View File

@ -55,9 +55,9 @@ NODE_MODULES = "node_modules"
# The package lock file. # The package lock file.
PACKAGE_LOCK = "package-lock.json" PACKAGE_LOCK = "package-lock.json"
# The pcversion template file. # The pcversion template file.
PCVERSION_TEMPLATE_FILE = os.path.join(WEB_TEMPLATE_DIR, "pcversion.txt") PCVERSION_TEMPLATE_FILE = os.path.join(WEB_TEMPLATE_DIR, "pynecone.json")
# The pcversion app file. # The pcversion app file.
PCVERSION_APP_FILE = os.path.join(WEB_DIR, "pcversion.txt") PCVERSION_APP_FILE = os.path.join(WEB_DIR, "pynecone.json")
# Commands to run the app. # Commands to run the app.

View File

@ -7,6 +7,7 @@ import httpx
import typer import typer
from pynecone import constants, utils from pynecone import constants, utils
from pynecone.telemetry import pynecone_telemetry
# Create the app. # Create the app.
cli = typer.Typer() cli = typer.Typer()
@ -43,6 +44,12 @@ def init():
# Initialize the .gitignore. # Initialize the .gitignore.
utils.initialize_gitignore() utils.initialize_gitignore()
# Set the pynecone project hash.
utils.set_pynecone_project_hash()
# Post a telemetry event.
pynecone_telemetry("init", utils.get_config().telemetry_enabled)
# Finish initializing the app. # Finish initializing the app.
utils.console.log(f"[bold green]Finished Initializing: {app_name}") utils.console.log(f"[bold green]Finished Initializing: {app_name}")
@ -100,6 +107,9 @@ def run(
frontend_cmd, backend_cmd = utils.run_frontend_prod, utils.run_backend_prod frontend_cmd, backend_cmd = utils.run_frontend_prod, utils.run_backend_prod
assert frontend_cmd and backend_cmd, "Invalid env" assert frontend_cmd and backend_cmd, "Invalid env"
# Post a telemetry event.
pynecone_telemetry(f"run-{env.value}", utils.get_config().telemetry_enabled)
# Run the frontend and backend. # Run the frontend and backend.
try: try:
if frontend: if frontend:
@ -174,6 +184,10 @@ def export(
utils.console.rule("[bold]Compiling production app and preparing for export.") utils.console.rule("[bold]Compiling production app and preparing for export.")
app = utils.get_app().app app = utils.get_app().app
utils.export_app(app, backend=backend, frontend=frontend, zip=zipping) utils.export_app(app, backend=backend, frontend=frontend, zip=zipping)
# Post a telemetry event.
pynecone_telemetry("export", utils.get_config().telemetry_enabled)
if zipping: if zipping:
utils.console.rule( utils.console.rule(
"""Backend & Frontend compiled. See [green bold]backend.zip[/green bold] """Backend & Frontend compiled. See [green bold]backend.zip[/green bold]

View File

@ -1,39 +1,98 @@
"""Anonymous telemetry for Pynecone.""" """Anonymous telemetry for Pynecone."""
import json
import multiprocessing import multiprocessing
import platform import platform
from datetime import datetime
import httpx
import psutil import psutil
from pynecone import constants from pynecone import constants
from pynecone.base import Base from pynecone.base import Base
def get_os() -> str:
"""Get the operating system.
Returns:
The operating system.
"""
return platform.system()
def get_python_version() -> str:
"""Get the Python version.
Returns:
The Python version.
"""
return platform.python_version()
def get_pynecone_version() -> str:
"""Get the Pynecone version.
Returns:
The Pynecone version.
"""
return constants.VERSION
def get_cpu_count() -> int:
"""Get the number of CPUs.
Returns:
The number of CPUs.
"""
return multiprocessing.cpu_count()
def get_memory() -> int:
"""Get the total memory in MB.
Returns:
The total memory in MB.
"""
return psutil.virtual_memory().total >> 20
class Telemetry(Base): class Telemetry(Base):
"""Anonymous telemetry for Pynecone.""" """Anonymous telemetry for Pynecone."""
user_os: str = "" user_os: str = get_os()
cpu_count: int = 0 cpu_count: int = get_cpu_count()
memory: int = 0 memory: int = get_memory()
pynecone_version: str = "" pynecone_version: str = get_pynecone_version()
python_version: str = "" python_version: str = get_python_version()
def get_os(self) -> None:
"""Get the operating system."""
self.user_os = platform.system()
def get_python_version(self) -> None: def pynecone_telemetry(event: str, telemetry_enabled: bool) -> None:
"""Get the Python version.""" """Send anonymous telemetry for Pynecone.
self.python_version = platform.python_version()
def get_pynecone_version(self) -> None: Args:
"""Get the Pynecone version.""" event: The event name.
self.pynecone_version = constants.VERSION telemetry_enabled: Whether to send the telemetry.
"""
def get_cpu_count(self) -> None: try:
"""Get the number of CPUs.""" if telemetry_enabled:
self.cpu_count = multiprocessing.cpu_count() telemetry = Telemetry()
with open(constants.PCVERSION_APP_FILE) as f: # type: ignore
def get_memory(self) -> None: pynecone_json = json.load(f)
"""Get the total memory in MB.""" distinct_id = pynecone_json["project_hash"]
self.memory = psutil.virtual_memory().total >> 20 post_hog = {
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
"event": event,
"properties": {
"distinct_id": distinct_id,
"user_os": telemetry.user_os,
"pynecone_version": telemetry.pynecone_version,
"python_version": telemetry.python_version,
"cpu_count": telemetry.cpu_count,
"memory": telemetry.memory,
},
"timestamp": datetime.utcnow().isoformat(),
}
httpx.post("https://app.posthog.com/capture/", json=post_hog)
except Exception:
pass

View File

@ -476,7 +476,7 @@ def is_latest_template() -> bool:
Whether the app is using the latest template. Whether the app is using the latest template.
""" """
with open(constants.PCVERSION_TEMPLATE_FILE) as f: # type: ignore with open(constants.PCVERSION_TEMPLATE_FILE) as f: # type: ignore
template_version = f.read() template_version = json.load(f)["version"]
if not os.path.exists(constants.PCVERSION_APP_FILE): if not os.path.exists(constants.PCVERSION_APP_FILE):
return False return False
with open(constants.PCVERSION_APP_FILE) as f: # type: ignore with open(constants.PCVERSION_APP_FILE) as f: # type: ignore
@ -484,6 +484,15 @@ def is_latest_template() -> bool:
return app_version >= template_version return app_version >= template_version
def set_pynecone_project_hash():
"""Write the hash of the Pynecone project to a PCVERSION_APP_FILE."""
with open(constants.PCVERSION_APP_FILE) as f: # type: ignore
pynecone_json = json.load(f)
pynecone_json["project_hash"] = random.getrandbits(128)
with open(constants.PCVERSION_APP_FILE, "w") as f:
json.dump(pynecone_json, f, ensure_ascii=False)
def export_app( def export_app(
app: App, backend: bool = True, frontend: bool = True, zip: bool = False app: App, backend: bool = True, frontend: bool = True, zip: bool = False
): ):

View File

@ -12,28 +12,19 @@ def test_telemetry():
tel = telemetry.Telemetry() tel = telemetry.Telemetry()
# Check that the user OS is one of the supported operating systems. # Check that the user OS is one of the supported operating systems.
tel.get_os()
assert tel.user_os is not None assert tel.user_os is not None
assert tel.user_os in ["Linux", "Darwin", "Java", "Windows"] assert tel.user_os in ["Linux", "Darwin", "Java", "Windows"]
# Check that the CPU count and memory are greater than 0. # Check that the CPU count and memory are greater than 0.
tel.get_cpu_count()
assert tel.cpu_count > 0 assert tel.cpu_count > 0
# Check that the available memory is greater than 0 # Check that the available memory is greater than 0
tel.get_memory()
assert tel.memory > 0 assert tel.memory > 0
# Check that the Pynecone version is not None. # Check that the Pynecone version is not None.
tel.get_python_version()
assert tel.pynecone_version is not None assert tel.pynecone_version is not None
# Check that the Python version is greater than 3.7. # Check that the Python version is greater than 3.7.
tel.get_pynecone_version()
assert tel.python_version is not None assert tel.python_version is not None
assert versiontuple(tel.python_version) >= versiontuple("3.7") assert versiontuple(tel.python_version) >= versiontuple("3.7")