Skip to content

Commit

Permalink
Merge pull request #4111 from mathesar-foundation/analytics_pt_2
Browse files Browse the repository at this point in the history
Add analytics wiring RPC functions
  • Loading branch information
Anish9901 authored Jan 9, 2025
2 parents 08f7104 + 8e950a2 commit 60e95bf
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 6 deletions.
4 changes: 3 additions & 1 deletion config/settings/common_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def pipe_delim(pipe_string):
ROOT_URLCONF = "config.urls"

MODERNRPC_METHODS_MODULES = [
'mathesar.rpc.analytics',
'mathesar.rpc.collaborators',
'mathesar.rpc.columns',
'mathesar.rpc.columns.metadata',
Expand Down Expand Up @@ -231,7 +232,8 @@ def pipe_delim(pipe_string):
MATHESAR_UI_SOURCE_LOCATION = os.path.join(BASE_DIR, 'mathesar_ui/')
MATHESAR_CAPTURE_UNHANDLED_EXCEPTION = os.environ.get('CAPTURE_UNHANDLED_EXCEPTION', default=False)
MATHESAR_STATIC_NON_CODE_FILES_LOCATION = os.path.join(BASE_DIR, 'mathesar/static/non-code/')
MATHESAR_ANALYTICS_URL = os.environ.get('MATHESAR_ANALYTICS_URL', default='https://example.com')
MATHESAR_ANALYTICS_URL = os.environ.get('MATHESAR_ANALYTICS_URL', default='https://example.com/collector')
MATHESAR_INIT_REPORT_URL = os.environ.get('MATHESAR_INIT_REPORT_URL', default='https://example.com/hello')

DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

Expand Down
2 changes: 2 additions & 0 deletions config/settings/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# Override default settings
DEBUG = False
MATHESAR_MODE = 'PRODUCTION'
MATHESAR_ANALYTICS_URL = 'https://analytics.mathesar.dev/collect-analytics-reports'
MATHESAR_INIT_REPORT_URL = 'https://analytics.mathesar.dev/collect-initial-report'

'''
This tells Django to trust the X-Forwarded-Proto header that comes from our proxy,
Expand Down
3 changes: 2 additions & 1 deletion docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ services:
- DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE-config.settings.development}
- ALLOWED_HOSTS=${ALLOWED_HOSTS-*}
- SECRET_KEY=${SECRET_KEY}
- MATHESAR_ANALYTICS_URL=${MATHESAR_ANALYTICS_URL-https://example.com}
- MATHESAR_ANALYTICS_URL=${MATHESAR_ANALYTICS_URL-https://example.com/collector}
- MATHESAR_INIT_REPORT_URL=${MATHESAR_INIT_REPORT_URL-https://example.com/hello}
- MATHESAR_DATABASES=(mathesar_tables|postgresql://mathesar:mathesar@mathesar_dev_db:5432/mathesar)
- DJANGO_SUPERUSER_PASSWORD=password
- POSTGRES_DB=mathesar_django
Expand Down
8 changes: 8 additions & 0 deletions docs/docs/api/methods.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# API Methods

## Analytics
::: analytics
options:
members:
- initialize
- disable
- view_report

## Collaborators

::: collaborators
Expand Down
20 changes: 16 additions & 4 deletions mathesar/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,14 @@ def disable_analytics():


def save_analytics_report():
raw_analytics_report = prepare_analytics_report()
if raw_analytics_report["installation_id"] is not None:
analytics_report = AnalyticsReport(**raw_analytics_report)
analytics_report.save()


def prepare_analytics_report():
installation_id = InstallationID.objects.first()
if installation_id is None:
return
connected_database_count = 0
connected_database_schema_count = 0
connected_database_table_count = 0
Expand All @@ -83,7 +88,7 @@ def save_analytics_report():
except Exception:
print(f"Couldn't retrieve object counts for {d.name}")

analytics_report = AnalyticsReport(
return dict(
installation_id=installation_id,
mathesar_version=__version__,
user_count=User.objects.filter(is_active=True).count(),
Expand All @@ -99,7 +104,6 @@ def save_analytics_report():
connected_database_record_count=connected_database_record_count,
exploration_count=Explorations.objects.count(),
)
analytics_report.save()


def upload_analytics_reports():
Expand Down Expand Up @@ -137,3 +141,11 @@ def delete_stale_reports():
- timezone.timedelta(days=ANALYTICS_REPORT_MAX_AGE)
)
).delete()


def upload_initial_report():
"""Upload an initial report when Mathesar is installed"""
requests.post(
settings.MATHESAR_INIT_REPORT_URL,
json={"mathesar_version": __version__}
)
84 changes: 84 additions & 0 deletions mathesar/rpc/analytics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
Classes and functions exposed to the RPC endpoint for managing analytics.
"""
from typing import TypedDict, Optional
from mathesar.rpc.decorators import mathesar_rpc_method
from mathesar.analytics import (
initialize_analytics,
disable_analytics,
prepare_analytics_report,
)


class AnalyticsReport(TypedDict):
"""
A report with some statistics about the data accessible by Mathesar.
Attributes:
installation_id: A unique ID for this Mathesar installation.
mathesar_version: The version of Mathesar.
user_count: The number of configured users in Mathesar.
active_user_count: The number of users who have recently logged in.
configured_role_count: The number of DB roles configured.
connected_database_count: The number of databases configured.
connected_database_schema_count: The number of all schemas in
all connected databases.
connected_database_table_count: The total number of tables in
all conncted databasees.
connected_database_record_count: The total number of records in
all connected databasees (approximated)
exploration_count: The number of explorations.
"""
installation_id: Optional[str]
mathesar_version: str
user_count: int
active_user_count: int
configured_role_count: int
connected_database_count: int
connected_database_schema_count: int
connected_database_table_count: int
connected_database_record_count: int
exploration_count: int

@classmethod
def from_dict(cls, d):
if d["installation_id"] is not None:
d["installation_id"] = str(d["installation_id"].value)
return cls(d)


@mathesar_rpc_method(name="analytics.initialize")
def initialize():
"""
Initialize analytics collection and reporting in Mathesar
If initialized, analytics are gathered to a local model once per day,
and uploaded.
"""
initialize_analytics()


@mathesar_rpc_method(name="analytics.disable")
def disable():
"""
Disable analytics collection and reporting in Mathesar
Disabling analytics amounts to (for now) simply deleting the
Installation ID, ensuring that it's impossible to save analytics
reports. Any reports currently saved are removed when the
Installation ID is deleted.
"""
disable_analytics()


@mathesar_rpc_method(name="analytics.view_report")
def view_report() -> AnalyticsReport:
"""
View an example analytics report, prepared with the same function
that creates real reports that would be saved and uploaded.
Returns:
An analytics report.
"""
report = prepare_analytics_report()
return AnalyticsReport.from_dict(report)
16 changes: 16 additions & 0 deletions mathesar/tests/rpc/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import pytest
from modernrpc.auth import user_is_authenticated, user_is_superuser

from mathesar.rpc import analytics
from mathesar.rpc import collaborators
from mathesar.rpc import columns
from mathesar.rpc import constraints
Expand All @@ -22,6 +23,21 @@
from mathesar.rpc import users

METHODS = [
(
analytics.disable,
"analytics.disable",
[user_is_superuser]
),
(
analytics.initialize,
"analytics.initialize",
[user_is_superuser]
),
(
analytics.view_report,
"analytics.view_report",
[user_is_superuser]
),
(
collaborators.add,
"collaborators.add",
Expand Down

0 comments on commit 60e95bf

Please sign in to comment.