1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-07 14:25:37 +00:00

Implement posthog product analytics

This commit is contained in:
Bram Wiepjes 2023-07-24 11:11:14 +00:00
parent 80760f85d5
commit b743afd8d4
16 changed files with 159 additions and 6 deletions
backend
requirements
src/baserow
config/settings
contrib/database/airtable
core
tests/baserow
contrib/database/airtable
core
changelog/entries/unreleased/feature
docker-compose.local-build.ymldocker-compose.no-caddy.ymldocker-compose.yml
docs/installation
web-frontend/modules/database/components/airtable

View file

@ -61,4 +61,5 @@ Brotli==1.0.9
loguru==0.6.0
django-cachalot==2.5.3
celery-singleton==0.3.1
posthog==3.0.1
https://github.com/fellowapp/prosemirror-py/archive/refs/tags/v0.3.5.zip

View file

@ -37,7 +37,9 @@ azure-core==1.26.4
azure-storage-blob==12.16.0
# via django-storages
backoff==2.2.1
# via opentelemetry-exporter-otlp-proto-http
# via
# opentelemetry-exporter-otlp-proto-http
# posthog
billiard==3.6.4.0
# via celery
boto3==1.26.103
@ -211,6 +213,8 @@ kombu==5.2.4
# via celery
loguru==0.6.0
# via -r base.in
monotonic==1.6
# via posthog
msgpack==1.0.4
# via channels-redis
ndg-httpsclient==0.5.1
@ -315,6 +319,8 @@ opentelemetry-util-http==0.38b0
# opentelemetry-instrumentation-wsgi
pillow==9.4.0
# via -r base.in
posthog==3.0.1
# via -r base.in
prompt-toolkit==3.0.31
# via click-repl
prosemirror @ https://github.com/fellowapp/prosemirror-py/archive/refs/tags/v0.3.5.zip
@ -360,6 +366,7 @@ python-dateutil==2.8.2
# botocore
# celery-redbeat
# faker
# posthog
# pysaml2
# python-crontab
python-dotenv==0.21.0
@ -393,6 +400,7 @@ requests==2.31.0
# google-api-core
# google-cloud-storage
# opentelemetry-exporter-otlp-proto-http
# posthog
# pysaml2
# requests-oauthlib
requests-oauthlib==1.3.1
@ -413,6 +421,7 @@ six==1.16.0
# click-repl
# google-auth
# isodate
# posthog
# python-dateutil
# service-identity
sniffio==1.3.0

View file

@ -23,7 +23,7 @@ pytest-html==3.2.0
coverage==7.2.2
pytest-split==0.8.0
bandit==1.7.5
pip-tools==6.12.3
pip-tools==6.13.0
autopep8==2.0.2
pytest-unordered==0.5.2
debugpy==1.6.6

View file

@ -183,7 +183,7 @@ pexpect==4.8.0
# via ipython
pickleshare==0.7.5
# via ipython
pip-tools==6.12.3
pip-tools==6.13.0
# via -r dev.in
platformdirs==2.5.2
# via black

View file

@ -12,6 +12,7 @@ from urllib.parse import urljoin, urlparse
from django.core.exceptions import ImproperlyConfigured
import dj_database_url
import posthog
from corsheaders.defaults import default_headers
from baserow.cachalot_patch import patch_cachalot_for_baserow
@ -1108,6 +1109,15 @@ USE_PG_FULLTEXT_SEARCH = str_to_bool(
PG_SEARCH_CONFIG = os.getenv("BASEROW_PG_SEARCH_CONFIG", "simple")
AUTO_VACUUM_AFTER_SEARCH_UPDATE = str_to_bool(os.getenv("BASEROW_AUTO_VACUUM", "true"))
POSTHOG_PROJECT_API_KEY = os.getenv("POSTHOG_PROJECT_API_KEY", "")
POSTHOG_HOST = os.getenv("POSTHOG_HOST", "")
POSTHOG_ENABLED = POSTHOG_PROJECT_API_KEY and POSTHOG_HOST
if POSTHOG_ENABLED:
posthog.project_api_key = POSTHOG_PROJECT_API_KEY
posthog.host = POSTHOG_HOST
else:
posthog.disabled = True
# Indicates whether we are running the tests or not. Set to True in the test.py settings
# file used by pytest.ini
TESTS = False

View file

@ -11,7 +11,7 @@ def extract_share_id_from_url(public_base_url: str) -> str:
:return: The extracted share id.
"""
result = re.search(r"https:\/\/airtable.com\/shr(.*)$", public_base_url)
result = re.search(r"https:\/\/airtable.com\/(shr|app)(.*)$", public_base_url)
if not result:
raise ValueError(
@ -19,4 +19,4 @@ def extract_share_id_from_url(public_base_url: str) -> str:
f"https://airtable.com/shrxxxxxxxxxxxxxx)"
)
return f"shr{result.group(1)}"
return f"{result.group(1)}{result.group(2)}"

View file

@ -286,6 +286,9 @@ class CoreConfig(AppConfig):
WorkspaceInvitationRejectedNotificationType()
)
# Must import the Posthog signal, otherwise it won't work.
import baserow.core.posthog # noqa: F403, F401
self._setup_health_checks()
# Clear the key after migration so we will trigger a new template sync.

View file

@ -0,0 +1,71 @@
from copy import deepcopy
from typing import Optional
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.dispatch import receiver
import posthog
from baserow.core.action.signals import ActionCommandType, action_done
from baserow.core.models import Workspace
def capture_event(
user: AbstractUser,
event: str,
properties: dict,
session: Optional[str] = None,
workspace: Optional[Workspace] = None,
):
"""
Captures a Posthog event in a consistent property format.
:param user: The user that performed the event.
:param event: Unique name identifying the event.
:param properties: A dictionary containing the properties that must be added.
Note that the `user_email`, `user_session`, `workspace_id`,
and `workspace_name` will be overwritten.
:param session: A unique session id that identifies the user throughout their
session.
:param workspace: Optionally the workspace related to the event.
"""
if not settings.POSTHOG_ENABLED:
return
properties = deepcopy(properties)
properties["user_email"] = user.email
if session is not None:
properties["user_session"] = session
if workspace is not None:
properties["workspace_id"] = workspace.id
properties["workspace_name"] = workspace.name
posthog.capture(
distinct_id=user.id,
event=event,
properties=properties,
)
@receiver(action_done)
def capture_event_action_done(
sender,
user,
action_type,
action_params,
action_timestamp,
action_command_type,
workspace,
session,
**kwargs,
):
# Only capture do commands for now because the undo might make it more difficult
# to do analytics on the data.
if action_command_type == ActionCommandType.DO:
capture_event(
user, action_type.type, action_params, workspace=workspace, session=session
)

View file

@ -11,6 +11,10 @@ def test_extract_share_id_from_url():
extract_share_id_from_url("https://airtable.com/shrxxxxxxxxxxxxxx")
== "shrxxxxxxxxxxxxxx"
)
assert (
extract_share_id_from_url("https://airtable.com/appxxxxxxxxxxxxxx")
== "appxxxxxxxxxxxxxx"
)
assert (
extract_share_id_from_url("https://airtable.com/shrXxmp0WmqsTkFWTzv")
== "shrXxmp0WmqsTkFWTzv"

View file

@ -0,0 +1,35 @@
from unittest.mock import patch
from django.test.utils import override_settings
import pytest
from baserow.core.posthog import capture_event
@pytest.mark.django_db
@override_settings(POSTHOG_ENABLED=False)
@patch("baserow.core.posthog.posthog")
def test_not_capture_event_if_not_enabled(mock_posthog, data_fixture):
user = data_fixture.create_user()
capture_event(user, "test", {})
mock_posthog.capture.assert_not_called()
@pytest.mark.django_db
@override_settings(POSTHOG_ENABLED=True)
@patch("baserow.core.posthog.posthog")
def test_capture_event_if_enabled(mock_posthog, data_fixture):
user = data_fixture.create_user()
workspace = data_fixture.create_workspace()
capture_event(user, "test", {}, session="session", workspace=workspace)
mock_posthog.capture.assert_called_once_with(
distinct_id=user.id,
event="test",
properties={
"user_email": user.email,
"user_session": "session",
"workspace_id": workspace.id,
"workspace_name": workspace.name,
},
)

View file

@ -0,0 +1,7 @@
{
"type": "feature",
"message": "Implemented optional Posthog product analytics.",
"issue_number": null,
"bullet_points": [],
"created_at": "2023-07-21"
}

View file

@ -78,6 +78,8 @@ x-backend-variables: &backend-variables
BASEROW_DEPLOYMENT_ENV:
OTEL_EXPORTER_OTLP_ENDPOINT:
OTEL_RESOURCE_ATTRIBUTES:
POSTHOG_PROJECT_API_KEY:
POSTHOG_HOST:
PRIVATE_BACKEND_URL: http://backend:8000
PUBLIC_BACKEND_URL:

View file

@ -98,6 +98,8 @@ x-backend-variables: &backend-variables
BASEROW_DEPLOYMENT_ENV:
OTEL_EXPORTER_OTLP_ENDPOINT:
OTEL_RESOURCE_ATTRIBUTES:
POSTHOG_PROJECT_API_KEY:
POSTHOG_HOST:
PRIVATE_BACKEND_URL: http://backend:8000
BASEROW_PUBLIC_URL:

View file

@ -94,6 +94,8 @@ x-backend-variables: &backend-variables
BASEROW_DEPLOYMENT_ENV:
OTEL_EXPORTER_OTLP_ENDPOINT:
OTEL_RESOURCE_ATTRIBUTES:
POSTHOG_PROJECT_API_KEY:
POSTHOG_HOST:
PRIVATE_BACKEND_URL: http://backend:8000
PUBLIC_BACKEND_URL:

View file

@ -262,3 +262,10 @@ domain than your Baserow, you need to make sure CORS is configured correctly.
| BASEROW\_PLUGIN\_URLS | A comma separated list of plugin urls to install on startup. | |
| BASEROW\_DISABLE\_PLUGIN\_INSTALL\_ON\_STARTUP | When set to any non-empty values no automatic startup check and/or install of plugins will be run. Disables the above two env variables. |
| BASEROW\_PLUGIN\_DIR | **INTERNAL** Sets the folder where the Baserow plugin scripts look for plugins. | In the all-in-one image `/baserow/data/plugins`, otherwise `/baserow/plugins` |
### Posthog configuration
| Name | Description | Defaults |
|----------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------------------|
| POSTHOG\_PROJECT\_API\_KEY | Set this to your Posthog project API key for product analytics. | |
| POSTHOG\_HOST | Set this to your Posthog host for product analytics. | |

View file

@ -149,7 +149,7 @@ export default {
validations: {
airtableUrl: {
valid(value) {
const regex = /https:\/\/airtable.com\/shr(.*)$/g
const regex = /https:\/\/airtable.com\/[shr|app](.*)$/g
return !!value.match(regex)
},
},