databricks-tellr-app 0.3.11.dev6__tar.gz → 0.3.11.dev8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/PKG-INFO +1 -1
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/databricks_tellr_app.egg-info/SOURCES.txt +8 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/pyproject.toml +1 -1
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/main.py +10 -1
- databricks_tellr_app-0.3.11.dev8/src/api/routes/admin_usage.py +69 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/feedback.py +27 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/sessions.py +5 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/services/feedback_service.py +43 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/services/session_manager.py +18 -0
- databricks_tellr_app-0.3.11.dev8/src/api/services/usage_events.py +117 -0
- databricks_tellr_app-0.3.11.dev8/src/api/services/usage_service.py +400 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/__init__.py +2 -0
- databricks_tellr_app-0.3.11.dev8/src/database/models/usage_event.py +39 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/README.md +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/databricks_tellr_app/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/databricks_tellr_app/run.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/databricks_tellr_app.egg-info/requires.txt +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/databricks_tellr_app.egg-info/top_level.txt +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/setup.cfg +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/setup.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/_otel_bootstrap.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/fixtures/tour_demo_deck.json +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/mcp_auth.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/mcp_server.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/middleware/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/middleware/request_logging.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/admin.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/agent_config.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/chat.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/deck_contributors.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/export.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/google_slides.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/images.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/local_version.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/profiles.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/settings/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/settings/contributors.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/settings/deck_prompts.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/settings/identities.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/settings/slide_styles.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/setup.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/slides.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/tools.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/tour.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/verification.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/version.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/agent_config.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/feedback.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/requests.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/responses.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/settings/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/settings/requests.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/settings/responses.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/schemas/streaming.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/services/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/services/chat_service.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/services/export_job_queue.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/services/job_queue.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/services/session_naming.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/utils/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/utils/validation.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/config_loader.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/context_utils.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/database.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/databricks_client.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/defaults.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/encryption.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/init_default_profile.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/lakebase.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/migrate_profiles_to_agent_config.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/mlflow_agent_spans.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/mlflow_tracing.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/otel_logging.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/permission_context.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/prompt_modules.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/settings_db.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/core/user_context.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/deck_contributor.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/feedback.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/genie_space.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/google_global_credentials.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/google_oauth_token.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/identity.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/image.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/profile.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/profile_contributor.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/prompts.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/request_log.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/session.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/slide_deck_prompt.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/slide_style_library.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/database/models/user_preference.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/domain/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/domain/slide.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/domain/slide_deck.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/agent.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/agent_factory.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/config_service.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/config_validator.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/drive_uploader.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/evaluation/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/evaluation/llm_judge.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/genie_service.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/google_slides_auth.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/google_slides_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/html_to_google_slides.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/html_to_pptx.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/identity_provider.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/identity_providers/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/identity_providers/account_provider.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/identity_providers/local_provider.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/identity_providers/workspace_provider.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/image_service.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/image_tools.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/permission_service.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/pptx_from_html_huashu.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/pptx_from_records.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/pptx_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/profile_service.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/streaming_callback.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/tools/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/tools/agent_bricks_tool.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/tools/genie_tool.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/tools/mcp_tool.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/tools/model_endpoint_tool.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/tools/vector_tool.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/services/validator.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/__init__.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/css_utils.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/error_handling.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/html_safety.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/html_utils.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/image_utils.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/js_validator.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/logging_config.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/pi_filter.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/slide_hash.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/spotlight.py +0 -0
- {databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/utils/text_caps.py +0 -0
|
@@ -14,6 +14,7 @@ setup.py
|
|
|
14
14
|
./src/api/middleware/request_logging.py
|
|
15
15
|
./src/api/routes/__init__.py
|
|
16
16
|
./src/api/routes/admin.py
|
|
17
|
+
./src/api/routes/admin_usage.py
|
|
17
18
|
./src/api/routes/agent_config.py
|
|
18
19
|
./src/api/routes/chat.py
|
|
19
20
|
./src/api/routes/deck_contributors.py
|
|
@@ -51,6 +52,8 @@ setup.py
|
|
|
51
52
|
./src/api/services/job_queue.py
|
|
52
53
|
./src/api/services/session_manager.py
|
|
53
54
|
./src/api/services/session_naming.py
|
|
55
|
+
./src/api/services/usage_events.py
|
|
56
|
+
./src/api/services/usage_service.py
|
|
54
57
|
./src/api/utils/__init__.py
|
|
55
58
|
./src/api/utils/validation.py
|
|
56
59
|
./src/core/__init__.py
|
|
@@ -86,6 +89,7 @@ setup.py
|
|
|
86
89
|
./src/database/models/session.py
|
|
87
90
|
./src/database/models/slide_deck_prompt.py
|
|
88
91
|
./src/database/models/slide_style_library.py
|
|
92
|
+
./src/database/models/usage_event.py
|
|
89
93
|
./src/database/models/user_preference.py
|
|
90
94
|
./src/domain/__init__.py
|
|
91
95
|
./src/domain/slide.py
|
|
@@ -153,6 +157,7 @@ src/api/middleware/__init__.py
|
|
|
153
157
|
src/api/middleware/request_logging.py
|
|
154
158
|
src/api/routes/__init__.py
|
|
155
159
|
src/api/routes/admin.py
|
|
160
|
+
src/api/routes/admin_usage.py
|
|
156
161
|
src/api/routes/agent_config.py
|
|
157
162
|
src/api/routes/chat.py
|
|
158
163
|
src/api/routes/deck_contributors.py
|
|
@@ -190,6 +195,8 @@ src/api/services/feedback_service.py
|
|
|
190
195
|
src/api/services/job_queue.py
|
|
191
196
|
src/api/services/session_manager.py
|
|
192
197
|
src/api/services/session_naming.py
|
|
198
|
+
src/api/services/usage_events.py
|
|
199
|
+
src/api/services/usage_service.py
|
|
193
200
|
src/api/utils/__init__.py
|
|
194
201
|
src/api/utils/validation.py
|
|
195
202
|
src/core/__init__.py
|
|
@@ -225,6 +232,7 @@ src/database/models/request_log.py
|
|
|
225
232
|
src/database/models/session.py
|
|
226
233
|
src/database/models/slide_deck_prompt.py
|
|
227
234
|
src/database/models/slide_style_library.py
|
|
235
|
+
src/database/models/usage_event.py
|
|
228
236
|
src/database/models/user_preference.py
|
|
229
237
|
src/domain/__init__.py
|
|
230
238
|
src/domain/slide.py
|
|
@@ -16,7 +16,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
16
16
|
from fastapi.responses import FileResponse
|
|
17
17
|
from fastapi.staticfiles import StaticFiles
|
|
18
18
|
|
|
19
|
-
from src.api.routes import admin, agent_config, chat, export, feedback, images, profiles, sessions, slides, tools, tour, verification, version, google_slides, setup, local_version
|
|
19
|
+
from src.api.routes import admin, admin_usage, agent_config, chat, export, feedback, images, profiles, sessions, slides, tools, tour, verification, version, google_slides, setup, local_version
|
|
20
20
|
from src.api.routes.deck_contributors import router as deck_contributors_router
|
|
21
21
|
from src.core.databricks_client import get_or_create_user_client, set_user_client
|
|
22
22
|
from src.core.user_context import get_current_user as get_ctx_user, set_current_user
|
|
@@ -381,6 +381,14 @@ async def user_auth_middleware(request: Request, call_next):
|
|
|
381
381
|
)
|
|
382
382
|
set_permission_context(permission_ctx)
|
|
383
383
|
|
|
384
|
+
# Record login usage-event (visit-deduped, non-blocking, all envs)
|
|
385
|
+
if user_name:
|
|
386
|
+
try:
|
|
387
|
+
from src.api.services.usage_events import record_login
|
|
388
|
+
record_login(user_name)
|
|
389
|
+
except Exception as e:
|
|
390
|
+
logger.debug(f"Failed to record login usage event: {e}")
|
|
391
|
+
|
|
384
392
|
# Record user login for local identity table (non-blocking)
|
|
385
393
|
if user_id and user_name and IS_PRODUCTION:
|
|
386
394
|
try:
|
|
@@ -411,6 +419,7 @@ app.add_middleware(RequestLoggingMiddleware)
|
|
|
411
419
|
|
|
412
420
|
# Include API routers
|
|
413
421
|
app.include_router(admin.router)
|
|
422
|
+
app.include_router(admin_usage.router)
|
|
414
423
|
app.include_router(agent_config.router)
|
|
415
424
|
app.include_router(chat.router)
|
|
416
425
|
app.include_router(feedback.router)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Admin usage-analytics endpoints (ungated, like the rest of /admin)."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
6
|
+
from sqlalchemy.orm import Session
|
|
7
|
+
|
|
8
|
+
from src.api.services.usage_service import ALLOWED_WINDOWS, UsageService
|
|
9
|
+
from src.core.database import get_db
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
router = APIRouter(prefix="/api/admin/usage", tags=["admin-usage"])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _validated_days(days: int) -> int:
|
|
16
|
+
if days not in ALLOWED_WINDOWS:
|
|
17
|
+
raise HTTPException(
|
|
18
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
19
|
+
detail=f"days must be one of {sorted(ALLOWED_WINDOWS)}",
|
|
20
|
+
)
|
|
21
|
+
return days
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _handle(callable_, *args, **kwargs):
|
|
25
|
+
try:
|
|
26
|
+
return callable_(*args, **kwargs)
|
|
27
|
+
except HTTPException:
|
|
28
|
+
raise
|
|
29
|
+
except Exception as e:
|
|
30
|
+
logger.error(f"Usage analytics error: {e}", exc_info=True)
|
|
31
|
+
raise HTTPException(
|
|
32
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
33
|
+
detail="Failed to compute usage analytics",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get("/summary")
|
|
38
|
+
def get_summary(days: int = Query(default=7), db: Session = Depends(get_db)):
|
|
39
|
+
days = _validated_days(days)
|
|
40
|
+
return _handle(UsageService().get_summary, db, days=days)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@router.get("/daily")
|
|
44
|
+
def get_daily(days: int = Query(default=7), db: Session = Depends(get_db)):
|
|
45
|
+
days = _validated_days(days)
|
|
46
|
+
return _handle(UsageService().get_daily, db, days=days)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.get("/top-users")
|
|
50
|
+
def get_top_users(days: int = Query(default=7), db: Session = Depends(get_db)):
|
|
51
|
+
days = _validated_days(days)
|
|
52
|
+
return _handle(UsageService().get_top_users, db, days=days)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@router.get("/funnel")
|
|
56
|
+
def get_funnel(days: int = Query(default=7), db: Session = Depends(get_db)):
|
|
57
|
+
days = _validated_days(days)
|
|
58
|
+
return _handle(UsageService().get_funnel, db, days=days)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@router.get("/retention")
|
|
62
|
+
def get_retention(db: Session = Depends(get_db)):
|
|
63
|
+
return _handle(UsageService().get_retention, db)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@router.get("/heatmap")
|
|
67
|
+
def get_heatmap(days: int = Query(default=7), db: Session = Depends(get_db)):
|
|
68
|
+
days = _validated_days(days)
|
|
69
|
+
return _handle(UsageService().get_heatmap, db, days=days)
|
{databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/feedback.py
RENAMED
|
@@ -88,6 +88,33 @@ def get_stats_report(weeks: int = Query(default=12, ge=1, le=52), db: Session =
|
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
@router.get("/list")
|
|
92
|
+
def list_feedback(
|
|
93
|
+
weeks: int = Query(default=12, ge=1, le=52),
|
|
94
|
+
category: str | None = Query(default=None),
|
|
95
|
+
severity: str | None = Query(default=None),
|
|
96
|
+
page: int = Query(default=1, ge=1),
|
|
97
|
+
page_size: int = Query(default=20, ge=1, le=100),
|
|
98
|
+
db: Session = Depends(get_db),
|
|
99
|
+
):
|
|
100
|
+
try:
|
|
101
|
+
service = FeedbackService()
|
|
102
|
+
return service.list_feedback(
|
|
103
|
+
db=db,
|
|
104
|
+
weeks=weeks,
|
|
105
|
+
category=category,
|
|
106
|
+
severity=severity,
|
|
107
|
+
page=page,
|
|
108
|
+
page_size=page_size,
|
|
109
|
+
)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error(f"Feedback list error: {e}", exc_info=True)
|
|
112
|
+
raise HTTPException(
|
|
113
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
114
|
+
detail="Failed to list feedback",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
91
118
|
@router.get("/report/summary")
|
|
92
119
|
def get_feedback_summary(weeks: int = Query(default=4, ge=1, le=52), db: Session = Depends(get_db)):
|
|
93
120
|
try:
|
{databricks_tellr_app-0.3.11.dev6 → databricks_tellr_app-0.3.11.dev8}/src/api/routes/sessions.py
RENAMED
|
@@ -25,6 +25,7 @@ from src.api.services.session_manager import (
|
|
|
25
25
|
SessionNotFoundError,
|
|
26
26
|
get_session_manager,
|
|
27
27
|
)
|
|
28
|
+
from src.api.services.usage_events import record_deck_retrieved
|
|
28
29
|
from src.core.database import get_db, get_db_session
|
|
29
30
|
from src.core.permission_context import get_permission_context
|
|
30
31
|
from src.core.user_context import get_current_user
|
|
@@ -467,6 +468,10 @@ async def get_session(session_id: str, db: Session = Depends(get_db)):
|
|
|
467
468
|
# Check permission
|
|
468
469
|
permission = _require_session_access(session, db, PermissionLevel.CAN_VIEW)
|
|
469
470
|
|
|
471
|
+
# Record deck-open usage event (deduped per user+deck, non-blocking)
|
|
472
|
+
if current_user:
|
|
473
|
+
record_deck_retrieved(current_user, session.get("id"))
|
|
474
|
+
|
|
470
475
|
# Only the session creator sees chat messages — conversations are private
|
|
471
476
|
is_creator = session.get("created_by") == current_user
|
|
472
477
|
messages = []
|
|
@@ -215,6 +215,49 @@ class FeedbackService:
|
|
|
215
215
|
"usage": usage,
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
def list_feedback(
|
|
219
|
+
self,
|
|
220
|
+
db: Session,
|
|
221
|
+
weeks: int = 12,
|
|
222
|
+
category: str | None = None,
|
|
223
|
+
severity: str | None = None,
|
|
224
|
+
page: int = 1,
|
|
225
|
+
page_size: int = 20,
|
|
226
|
+
) -> Dict[str, Any]:
|
|
227
|
+
"""Paginated raw feedback conversations, newest first. Anonymous."""
|
|
228
|
+
cutoff = datetime.utcnow() - timedelta(weeks=weeks)
|
|
229
|
+
query = db.query(FeedbackConversation).filter(
|
|
230
|
+
FeedbackConversation.created_at >= cutoff
|
|
231
|
+
)
|
|
232
|
+
if category:
|
|
233
|
+
query = query.filter(FeedbackConversation.category == category)
|
|
234
|
+
if severity:
|
|
235
|
+
query = query.filter(FeedbackConversation.severity == severity)
|
|
236
|
+
|
|
237
|
+
total = query.count()
|
|
238
|
+
items = (
|
|
239
|
+
query.order_by(FeedbackConversation.created_at.desc())
|
|
240
|
+
.offset((page - 1) * page_size)
|
|
241
|
+
.limit(page_size)
|
|
242
|
+
.all()
|
|
243
|
+
)
|
|
244
|
+
return {
|
|
245
|
+
"items": [
|
|
246
|
+
{
|
|
247
|
+
"id": item.id,
|
|
248
|
+
"created_at": item.created_at.isoformat(),
|
|
249
|
+
"category": item.category,
|
|
250
|
+
"severity": item.severity,
|
|
251
|
+
"summary": item.summary,
|
|
252
|
+
"raw_conversation": item.raw_conversation,
|
|
253
|
+
}
|
|
254
|
+
for item in items
|
|
255
|
+
],
|
|
256
|
+
"total": total,
|
|
257
|
+
"page": page,
|
|
258
|
+
"page_size": page_size,
|
|
259
|
+
}
|
|
260
|
+
|
|
218
261
|
def get_feedback_summary(self, db: Session, weeks: int = 4) -> Dict[str, Any]:
|
|
219
262
|
"""Generate an AI summary of feedback conversations."""
|
|
220
263
|
cutoff = datetime.utcnow() - timedelta(weeks=weeks)
|
|
@@ -689,6 +689,17 @@ class SessionManager:
|
|
|
689
689
|
db.add(deck)
|
|
690
690
|
db.flush()
|
|
691
691
|
|
|
692
|
+
# Record deck-created usage event (non-blocking)
|
|
693
|
+
try:
|
|
694
|
+
from src.api.services.usage_events import record_deck_created
|
|
695
|
+
from src.core.user_context import get_current_user as _get_user
|
|
696
|
+
|
|
697
|
+
record_deck_created(
|
|
698
|
+
modified_by or _get_user(), deck_owner.id
|
|
699
|
+
)
|
|
700
|
+
except Exception:
|
|
701
|
+
logger.debug("Failed to record deck_created event", exc_info=True)
|
|
702
|
+
|
|
692
703
|
# Update activity on both the requesting session and the deck owner
|
|
693
704
|
session.last_activity = datetime.utcnow()
|
|
694
705
|
if session.id != deck_owner.id:
|
|
@@ -1781,6 +1792,13 @@ class SessionManager:
|
|
|
1781
1792
|
def cleanup_expired_sessions(self) -> int:
|
|
1782
1793
|
"""Delete sessions that have exceeded TTL.
|
|
1783
1794
|
|
|
1795
|
+
WARNING: user_sessions is the app's durable usage history. This
|
|
1796
|
+
method is intentionally NOT scheduled anywhere — it is only
|
|
1797
|
+
reachable via the manual POST /api/sessions/cleanup endpoint.
|
|
1798
|
+
Do not wire it to a scheduler; doing so would destroy the
|
|
1799
|
+
history that the /admin usage dashboard's pre-event-log
|
|
1800
|
+
aggregations rely on.
|
|
1801
|
+
|
|
1784
1802
|
Returns:
|
|
1785
1803
|
Number of sessions deleted
|
|
1786
1804
|
"""
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Fire-and-forget usage-event recording with in-process dedup.
|
|
2
|
+
|
|
3
|
+
Mirrors the request_logs middleware pattern: writes happen on an executor
|
|
4
|
+
thread (or inline when already off the event loop) and never raise.
|
|
5
|
+
|
|
6
|
+
Dedup semantics (per spec):
|
|
7
|
+
- ``login``: one event per "visit" — the first authenticated request after
|
|
8
|
+
>= 30 minutes without any authenticated request (sliding window: every
|
|
9
|
+
request refreshes the timer, so continuous activity never re-emits).
|
|
10
|
+
- ``deck_retrieved``: one event per (username, session_id) per 30-minute window.
|
|
11
|
+
- ``deck_created``: never deduped.
|
|
12
|
+
|
|
13
|
+
Caches are per-process; a restart may produce occasional extra login
|
|
14
|
+
events, which is acceptable.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
from src.database.models.usage_event import (
|
|
22
|
+
EVENT_DECK_CREATED,
|
|
23
|
+
EVENT_DECK_RETRIEVED,
|
|
24
|
+
EVENT_LOGIN,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_DEDUP_WINDOW_SECONDS = 30 * 60
|
|
30
|
+
|
|
31
|
+
_login_cache: dict[str, float] = {}
|
|
32
|
+
_retrieval_cache: dict[tuple[str, str], float] = {}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def reset_dedup_caches() -> None:
|
|
36
|
+
"""Clear dedup caches (test helper)."""
|
|
37
|
+
_login_cache.clear()
|
|
38
|
+
_retrieval_cache.clear()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _write_event(username: str, event_type: str, session_id) -> None:
|
|
42
|
+
"""Synchronously insert one usage event. Never raises."""
|
|
43
|
+
try:
|
|
44
|
+
from src.core.database import get_session_local
|
|
45
|
+
from src.database.models.usage_event import UsageEvent
|
|
46
|
+
|
|
47
|
+
session_factory = get_session_local()
|
|
48
|
+
db = session_factory()
|
|
49
|
+
try:
|
|
50
|
+
db.add(
|
|
51
|
+
UsageEvent(
|
|
52
|
+
username=username,
|
|
53
|
+
event_type=event_type,
|
|
54
|
+
session_id=session_id,
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
db.commit()
|
|
58
|
+
except Exception:
|
|
59
|
+
logger.debug("Failed to write usage event", exc_info=True)
|
|
60
|
+
db.rollback()
|
|
61
|
+
finally:
|
|
62
|
+
db.close()
|
|
63
|
+
except Exception:
|
|
64
|
+
logger.debug("Failed to write usage event", exc_info=True)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _submit(username: str, event_type: str, session_id) -> None:
|
|
68
|
+
"""Dispatch a write without blocking the caller.
|
|
69
|
+
|
|
70
|
+
On the event loop -> run_in_executor; in a worker thread (no running
|
|
71
|
+
loop) -> write inline, which is already off the loop.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
loop = asyncio.get_running_loop()
|
|
75
|
+
loop.run_in_executor(None, _write_event, username, event_type, session_id)
|
|
76
|
+
except RuntimeError:
|
|
77
|
+
_write_event(username, event_type, session_id)
|
|
78
|
+
except Exception:
|
|
79
|
+
logger.debug("Failed to submit usage event", exc_info=True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def record_login(username) -> None:
|
|
83
|
+
"""Record a login "visit": the first authenticated request after >=30
|
|
84
|
+
minutes without any authenticated request (sliding window).
|
|
85
|
+
|
|
86
|
+
Every call refreshes the cached timestamp, so continuous activity keeps
|
|
87
|
+
the window sliding and emits no further events until the user has been
|
|
88
|
+
inactive for a full window.
|
|
89
|
+
"""
|
|
90
|
+
if not username:
|
|
91
|
+
return
|
|
92
|
+
now = time.monotonic()
|
|
93
|
+
last = _login_cache.get(username)
|
|
94
|
+
_login_cache[username] = now
|
|
95
|
+
if last is not None and (now - last) < _DEDUP_WINDOW_SECONDS:
|
|
96
|
+
return
|
|
97
|
+
_submit(username, EVENT_LOGIN, None)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def record_deck_created(username, session_id) -> None:
|
|
101
|
+
"""Record a deck creation. Never deduped."""
|
|
102
|
+
if not username:
|
|
103
|
+
return
|
|
104
|
+
_submit(username, EVENT_DECK_CREATED, session_id)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def record_deck_retrieved(username, session_id) -> None:
|
|
108
|
+
"""Record a deck open, deduped per (user, deck) per 30-minute window."""
|
|
109
|
+
if not username:
|
|
110
|
+
return
|
|
111
|
+
key = (username, str(session_id))
|
|
112
|
+
now = time.monotonic()
|
|
113
|
+
last = _retrieval_cache.get(key)
|
|
114
|
+
if last is not None and (now - last) < _DEDUP_WINDOW_SECONDS:
|
|
115
|
+
return
|
|
116
|
+
_retrieval_cache[key] = now
|
|
117
|
+
_submit(username, EVENT_DECK_RETRIEVED, session_id)
|