浏览代码

Anonymous Telemetry Opt Out Available (#550)

* Added anonymous telemetry with opt-out.
Alek Petuskey 2 年之前
父节点
当前提交
b36680fefd

+ 9 - 9
poetry.lock

@@ -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.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]
 [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.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]
 [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.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]]
 [[package]]

+ 0 - 1
pynecone/.templates/web/pcversion.txt

@@ -1 +0,0 @@
-0.1.16

+ 3 - 0
pynecone/.templates/web/pynecone.json

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

+ 3 - 0
pynecone/config.py

@@ -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
 
 

+ 2 - 2
pynecone/constants.py

@@ -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.

+ 14 - 0
pynecone/pc.py

@@ -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] 

+ 81 - 22
pynecone/telemetry.py

@@ -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
 
 
 
 
-class Telemetry(Base):
-    """Anonymous telemetry for Pynecone."""
+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
 
 
-    user_os: str = ""
-    cpu_count: int = 0
-    memory: int = 0
-    pynecone_version: str = ""
-    python_version: str = ""
 
 
-    def get_os(self) -> None:
-        """Get the operating system."""
-        self.user_os = platform.system()
+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."""
 
 
-    def get_python_version(self) -> None:
-        """Get the Python version."""
-        self.python_version = platform.python_version()
+    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_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 pynecone_telemetry(event: str, telemetry_enabled: bool) -> None:
+    """Send anonymous telemetry for Pynecone.
 
 
-    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

+ 10 - 1
pynecone/utils.py

@@ -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
 ):
 ):

+ 0 - 9
tests/test_telemetry.py

@@ -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")