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]]
name = "pyright"
version = "1.1.293"
version = "1.1.294"
description = "Command line wrapper for pyright"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pyright-1.1.293-py3-none-any.whl", hash = "sha256:afc05309e775a9869c864da4e8c0c7a3e3be9d8fe202e780c3bae981bbb13936"},
{file = "pyright-1.1.293.tar.gz", hash = "sha256:9397fdfcbc684fe5b87abbf9c27f540fe3b8d75999a5f187519cae1d065be38c"},
{file = "pyright-1.1.294-py3-none-any.whl", hash = "sha256:5b27e28a1cfc60cea707fd3b644769fa6dd0b194481cdcc2399cf2a51cc5a846"},
{file = "pyright-1.1.294.tar.gz", hash = "sha256:fea5fed3d6a3f02259e622c901e86a7b8bcf237d35e1cdfe01d0e0723768dcb6"},
]
[package.dependencies]
@ -832,14 +832,14 @@ files = [
[[package]]
name = "setuptools"
version = "67.2.0"
version = "67.3.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "setuptools-67.2.0-py3-none-any.whl", hash = "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c"},
{file = "setuptools-67.2.0.tar.gz", hash = "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"},
{file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"},
{file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"},
]
[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]]
name = "typing-extensions"
version = "4.4.0"
version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
{file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
{file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
]
[[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.
redis_url: Optional[str] = None
# Telemetry opt-in.
telemetry_enabled: bool = True
# The deploy url.
deploy_url: Optional[str] = None

View File

@ -55,9 +55,9 @@ NODE_MODULES = "node_modules"
# The package lock file.
PACKAGE_LOCK = "package-lock.json"
# 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.
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.

View File

@ -7,6 +7,7 @@ import httpx
import typer
from pynecone import constants, utils
from pynecone.telemetry import pynecone_telemetry
# Create the app.
cli = typer.Typer()
@ -43,6 +44,12 @@ def init():
# Initialize the .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.
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
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.
try:
if frontend:
@ -174,6 +184,10 @@ def export(
utils.console.rule("[bold]Compiling production app and preparing for export.")
app = utils.get_app().app
utils.export_app(app, backend=backend, frontend=frontend, zip=zipping)
# Post a telemetry event.
pynecone_telemetry("export", utils.get_config().telemetry_enabled)
if zipping:
utils.console.rule(
"""Backend & Frontend compiled. See [green bold]backend.zip[/green bold]

View File

@ -1,39 +1,98 @@
"""Anonymous telemetry for Pynecone."""
import json
import multiprocessing
import platform
from datetime import datetime
import httpx
import psutil
from pynecone import constants
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):
"""Anonymous telemetry for Pynecone."""
user_os: str = ""
cpu_count: int = 0
memory: int = 0
pynecone_version: str = ""
python_version: str = ""
user_os: str = get_os()
cpu_count: int = get_cpu_count()
memory: int = get_memory()
pynecone_version: str = get_pynecone_version()
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:
"""Get the Python version."""
self.python_version = platform.python_version()
def pynecone_telemetry(event: str, telemetry_enabled: bool) -> None:
"""Send anonymous telemetry for Pynecone.
def get_pynecone_version(self) -> None:
"""Get the Pynecone version."""
self.pynecone_version = constants.VERSION
def get_cpu_count(self) -> None:
"""Get the number of CPUs."""
self.cpu_count = multiprocessing.cpu_count()
def get_memory(self) -> None:
"""Get the total memory in MB."""
self.memory = psutil.virtual_memory().total >> 20
Args:
event: The event name.
telemetry_enabled: Whether to send the telemetry.
"""
try:
if telemetry_enabled:
telemetry = Telemetry()
with open(constants.PCVERSION_APP_FILE) as f: # type: ignore
pynecone_json = json.load(f)
distinct_id = pynecone_json["project_hash"]
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.
"""
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):
return False
with open(constants.PCVERSION_APP_FILE) as f: # type: ignore
@ -484,6 +484,15 @@ def is_latest_template() -> bool:
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(
app: App, backend: bool = True, frontend: bool = True, zip: bool = False
):

View File

@ -12,28 +12,19 @@ def test_telemetry():
tel = telemetry.Telemetry()
# 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 in ["Linux", "Darwin", "Java", "Windows"]
# Check that the CPU count and memory are greater than 0.
tel.get_cpu_count()
assert tel.cpu_count > 0
# Check that the available memory is greater than 0
tel.get_memory()
assert tel.memory > 0
# Check that the Pynecone version is not None.
tel.get_python_version()
assert tel.pynecone_version is not None
# Check that the Python version is greater than 3.7.
tel.get_pynecone_version()
assert tel.python_version is not None
assert versiontuple(tel.python_version) >= versiontuple("3.7")