Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions app/routes/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, send_from_directory, send_file, jsonify, render_template_string
from flask_babel import gettext as _
from flask_login import login_required, current_user
from app import db, limiter, log_event, track_event
import app as app_module
from app import db, limiter
from app.models import User, Project, TimeEntry, Settings, Invoice
from datetime import datetime
from sqlalchemy import text
Expand Down Expand Up @@ -261,8 +262,8 @@ def telemetry_dashboard():
}

# Log dashboard access
log_event("admin.telemetry_dashboard_viewed", user_id=current_user.id)
track_event(current_user.id, "admin.telemetry_dashboard_viewed", {})
app_module.log_event("admin.telemetry_dashboard_viewed", user_id=current_user.id)
app_module.track_event(current_user.id, "admin.telemetry_dashboard_viewed", {})

return render_template('admin/telemetry.html',
telemetry=telemetry_data,
Expand All @@ -282,8 +283,8 @@ def toggle_telemetry():
installation_config.set_telemetry_preference(new_state)

# Log the change
log_event("admin.telemetry_toggled", user_id=current_user.id, new_state=new_state)
track_event(current_user.id, "admin.telemetry_toggled", {"enabled": new_state})
app_module.log_event("admin.telemetry_toggled", user_id=current_user.id, new_state=new_state)
app_module.track_event(current_user.id, "admin.telemetry_toggled", {"enabled": new_state})

if new_state:
flash('Telemetry has been enabled. Thank you for helping us improve!', 'success')
Expand Down
19 changes: 10 additions & 9 deletions app/routes/clients.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app
from flask_babel import gettext as _
from flask_login import login_required, current_user
from app import db, log_event, track_event
import app as app_module
from app import db
from app.models import Client, Project
from datetime import datetime
from decimal import Decimal
Expand Down Expand Up @@ -109,8 +110,8 @@ def create_client():
return render_template('clients/create.html')

# Log client creation
log_event("client.created", user_id=current_user.id, client_id=client.id)
track_event(current_user.id, "client.created", {"client_id": client.id})
app_module.log_event("client.created", user_id=current_user.id, client_id=client.id)
app_module.track_event(current_user.id, "client.created", {"client_id": client.id})

flash(f'Client "{name}" created successfully', 'success')
return redirect(url_for('clients.view_client', client_id=client.id))
Expand Down Expand Up @@ -180,8 +181,8 @@ def edit_client(client_id):
return render_template('clients/edit.html', client=client)

# Log client update
log_event("client.updated", user_id=current_user.id, client_id=client.id)
track_event(current_user.id, "client.updated", {"client_id": client.id})
app_module.log_event("client.updated", user_id=current_user.id, client_id=client.id)
app_module.track_event(current_user.id, "client.updated", {"client_id": client.id})

flash(f'Client "{name}" updated successfully', 'success')
return redirect(url_for('clients.view_client', client_id=client.id))
Expand All @@ -202,8 +203,8 @@ def archive_client(client_id):
flash('Client is already inactive', 'info')
else:
client.archive()
log_event("client.archived", user_id=current_user.id, client_id=client.id)
track_event(current_user.id, "client.archived", {"client_id": client.id})
app_module.log_event("client.archived", user_id=current_user.id, client_id=client.id)
app_module.track_event(current_user.id, "client.archived", {"client_id": client.id})
flash(f'Client "{client.name}" archived successfully', 'success')

return redirect(url_for('clients.list_clients'))
Expand Down Expand Up @@ -249,8 +250,8 @@ def delete_client(client_id):
return redirect(url_for('clients.view_client', client_id=client.id))

# Log client deletion
log_event("client.deleted", user_id=current_user.id, client_id=client_id_for_log)
track_event(current_user.id, "client.deleted", {"client_id": client_id_for_log})
app_module.log_event("client.deleted", user_id=current_user.id, client_id=client_id_for_log)
app_module.track_event(current_user.id, "client.deleted", {"client_id": client_id_for_log})

flash(f'Client "{client_name}" deleted successfully', 'success')
return redirect(url_for('clients.list_clients'))
Expand Down
19 changes: 10 additions & 9 deletions app/routes/tasks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, make_response
from flask_babel import gettext as _
from flask_login import login_required, current_user
from app import db, log_event, track_event
import app as app_module
from app import db
from app.models import Task, Project, User, TimeEntry, TaskActivity, KanbanColumn
from datetime import datetime, date
from decimal import Decimal
Expand Down Expand Up @@ -156,12 +157,12 @@ def create_task():
return render_template('tasks/create.html')

# Log task creation
log_event("task.created",
app_module.log_event("task.created",
user_id=current_user.id,
task_id=task.id,
project_id=project_id,
priority=priority)
track_event(current_user.id, "task.created", {
app_module.track_event(current_user.id, "task.created", {
"task_id": task.id,
"project_id": project_id,
"priority": priority
Expand Down Expand Up @@ -302,11 +303,11 @@ def edit_task(task_id):
return render_template('tasks/edit.html', task=task, projects=projects, users=users)

# Log task update
log_event("task.updated",
app_module.log_event("task.updated",
user_id=current_user.id,
task_id=task.id,
project_id=task.project_id)
track_event(current_user.id, "task.updated", {
app_module.track_event(current_user.id, "task.updated", {
"task_id": task.id,
"project_id": task.project_id
})
Expand Down Expand Up @@ -388,12 +389,12 @@ def update_task_status(task_id):
return redirect(url_for('tasks.view_task', task_id=task.id))

# Log task status change
log_event("task.status_changed",
app_module.log_event("task.status_changed",
user_id=current_user.id,
task_id=task.id,
old_status=previous_status,
new_status=new_status)
track_event(current_user.id, "task.status_changed", {
app_module.track_event(current_user.id, "task.status_changed", {
"task_id": task.id,
"old_status": previous_status,
"new_status": new_status
Expand Down Expand Up @@ -476,8 +477,8 @@ def delete_task(task_id):
return redirect(url_for('tasks.view_task', task_id=task.id))

# Log task deletion
log_event("task.deleted", user_id=current_user.id, task_id=task_id_for_log, project_id=project_id_for_log)
track_event(current_user.id, "task.deleted", {"task_id": task_id_for_log, "project_id": project_id_for_log})
app_module.log_event("task.deleted", user_id=current_user.id, task_id=task_id_for_log, project_id=project_id_for_log)
app_module.track_event(current_user.id, "task.deleted", {"task_id": task_id_for_log, "project_id": project_id_for_log})

flash(f'Task "{task_name}" deleted successfully', 'success')
return redirect(url_for('tasks.list_tasks'))
Expand Down
11 changes: 8 additions & 3 deletions app/utils/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,18 @@ def get_all_config(self) -> Dict:

# Global instance
_installation_config = None
_installation_config_path = None


def get_installation_config() -> InstallationConfig:
"""Get the global installation configuration instance"""
global _installation_config
if _installation_config is None:
_installation_config = InstallationConfig()
global _installation_config, _installation_config_path
# Reinitialize if config path changed (e.g., tests overriding directories)
tmp = InstallationConfig()
current_path = tmp.config_path
if (_installation_config is None) or (_installation_config_path != current_path):
_installation_config = tmp
_installation_config_path = current_path
return _installation_config


Expand Down
23 changes: 18 additions & 5 deletions app/utils/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,33 @@ def is_telemetry_enabled() -> bool:
Checks both environment variable and user preference from installation config.
User preference takes precedence over environment variable.
"""
# Environment variable takes precedence for tests/CI and explicit overrides
env_value = os.getenv("ENABLE_TELEMETRY")
if env_value is not None:
enabled = env_value.lower()
return enabled in ("true", "1", "yes", "on")

try:
# Import here to avoid circular imports
from app.utils.installation import get_installation_config

# Get user preference from installation config
installation_config = get_installation_config()
if installation_config.is_setup_complete():
return installation_config.get_telemetry_preference()
except Exception:
pass

# Fallback to environment variable
enabled = os.getenv("ENABLE_TELEMETRY", "false").lower()
return enabled in ("true", "1", "yes", "on")

# Default disabled if not explicitly enabled
return False


# Re-export helper for tests to patch
try:
from app.utils.installation import get_installation_config # type: ignore
except Exception:
def get_installation_config(): # type: ignore
raise RuntimeError("installation config unavailable")


def _ensure_posthog_initialized() -> bool:
Expand Down
14 changes: 12 additions & 2 deletions tests/test_analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,19 @@

@pytest.fixture
def app():
"""Create test Flask application"""
"""Create test Flask application and initialize DB tables."""
app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:'})
return app
from app import db
with app.app_context():
db.create_all()
try:
yield app
finally:
try:
with app.app_context():
db.drop_all()
except Exception:
pass


@pytest.fixture
Expand Down
Loading