mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2025-12-30 15:49:44 -06:00
This commit introduces several high-impact features to improve user experience and productivity: New Features: - Activity Logging: Comprehensive audit trail tracking user actions across the system with Activity model, including IP address and user agent tracking - Time Entry Templates: Reusable templates for frequently logged activities with usage tracking and quick-start functionality - Saved Filters: Save and reuse common search/filter combinations across different views (projects, tasks, reports) - User Preferences: Enhanced user settings including email notifications, timezone, date/time formats, week start day, and theme preferences - Excel Export: Generate formatted Excel exports for time entries and reports with styling and proper formatting - Email Notifications: Complete email system for task assignments, overdue invoices, comments, and weekly summaries with HTML templates - Scheduled Tasks: Background task scheduler for periodic operations Models Added: - Activity: Tracks all user actions with detailed context and metadata - TimeEntryTemplate: Stores reusable time entry configurations - SavedFilter: Manages user-saved filter configurations Routes Added: - user.py: User profile and settings management - saved_filters.py: CRUD operations for saved filters - time_entry_templates.py: Template management endpoints UI Enhancements: - Bulk actions widget component - Keyboard shortcuts help modal with advanced shortcuts - Save filter widget component - Email notification templates - User profile and settings pages - Saved filters management interface - Time entry templates interface Database Changes: - Migration 022: Creates activities and time_entry_templates tables - Adds user preference columns (notifications, timezone, date/time formats) - Proper indexes for query optimization Backend Updates: - Enhanced keyboard shortcuts system (commands.js, keyboard-shortcuts-advanced.js) - Updated projects, reports, and tasks routes with activity logging - Safe database commit utilities integration - Event tracking for analytics Dependencies: - Added openpyxl for Excel generation - Added Flask-Mail dependencies - Updated requirements.txt All new features include proper error handling, activity logging integration, and maintain existing functionality while adding new capabilities.
292 lines
10 KiB
Python
292 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Quick Wins Features - Validation Test Script
|
|
|
|
This script validates that all new features can be imported
|
|
and basic functionality works without errors.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
|
|
# Add the app directory to the path
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
def test_imports():
|
|
"""Test that all new modules can be imported"""
|
|
print("🔍 Testing imports...")
|
|
|
|
try:
|
|
# Test model imports
|
|
from app.models import TimeEntryTemplate, Activity, SavedFilter, User
|
|
print("✅ Models imported successfully")
|
|
|
|
# Test route imports
|
|
from app.routes.user import user_bp
|
|
from app.routes.time_entry_templates import time_entry_templates_bp
|
|
from app.routes.saved_filters import saved_filters_bp
|
|
print("✅ Routes imported successfully")
|
|
|
|
# Test utility imports
|
|
from app.utils.email import mail, init_mail, send_email
|
|
from app.utils.excel_export import create_time_entries_excel, create_project_report_excel
|
|
from app.utils.scheduled_tasks import scheduler, check_overdue_invoices, register_scheduled_tasks
|
|
print("✅ Utilities imported successfully")
|
|
|
|
return True
|
|
except ImportError as e:
|
|
print(f"❌ Import error: {e}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"❌ Unexpected error: {e}")
|
|
return False
|
|
|
|
|
|
def test_model_attributes():
|
|
"""Test that models have expected attributes"""
|
|
print("\n🔍 Testing model attributes...")
|
|
|
|
try:
|
|
from app.models import TimeEntryTemplate, Activity, SavedFilter, User
|
|
|
|
# Test TimeEntryTemplate attributes
|
|
template_attrs = ['id', 'user_id', 'name', 'project_id', 'task_id',
|
|
'default_duration_minutes', 'default_notes', 'tags',
|
|
'usage_count', 'last_used_at']
|
|
for attr in template_attrs:
|
|
assert hasattr(TimeEntryTemplate, attr), f"TimeEntryTemplate missing {attr}"
|
|
print("✅ TimeEntryTemplate has all attributes")
|
|
|
|
# Test Activity attributes
|
|
activity_attrs = ['id', 'user_id', 'action', 'entity_type', 'entity_id',
|
|
'description', 'metadata', 'created_at']
|
|
for attr in activity_attrs:
|
|
assert hasattr(Activity, attr), f"Activity missing {attr}"
|
|
print("✅ Activity has all attributes")
|
|
|
|
# Test SavedFilter attributes
|
|
filter_attrs = ['id', 'user_id', 'name', 'scope', 'payload',
|
|
'is_shared', 'created_at', 'updated_at']
|
|
for attr in filter_attrs:
|
|
assert hasattr(SavedFilter, attr), f"SavedFilter missing {attr}"
|
|
print("✅ SavedFilter has all attributes")
|
|
|
|
# Test User new attributes
|
|
user_new_attrs = ['email_notifications', 'notification_overdue_invoices',
|
|
'notification_task_assigned', 'notification_task_comments',
|
|
'notification_weekly_summary', 'timezone', 'date_format',
|
|
'time_format', 'week_start_day']
|
|
for attr in user_new_attrs:
|
|
assert hasattr(User, attr), f"User missing new attribute {attr}"
|
|
print("✅ User has all new preference attributes")
|
|
|
|
return True
|
|
except AssertionError as e:
|
|
print(f"❌ Attribute test failed: {e}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"❌ Unexpected error: {e}")
|
|
return False
|
|
|
|
|
|
def test_model_methods():
|
|
"""Test that models have expected methods"""
|
|
print("\n🔍 Testing model methods...")
|
|
|
|
try:
|
|
from app.models import TimeEntryTemplate, Activity
|
|
|
|
# Test TimeEntryTemplate methods
|
|
template_methods = ['to_dict', 'record_usage', 'increment_usage']
|
|
for method in template_methods:
|
|
assert hasattr(TimeEntryTemplate, method), f"TimeEntryTemplate missing {method}"
|
|
print("✅ TimeEntryTemplate has all methods")
|
|
|
|
# Test Activity methods
|
|
activity_methods = ['log', 'get_recent', 'to_dict']
|
|
for method in activity_methods:
|
|
assert hasattr(Activity, method), f"Activity missing {method}"
|
|
print("✅ Activity has all methods")
|
|
|
|
return True
|
|
except AssertionError as e:
|
|
print(f"❌ Method test failed: {e}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"❌ Unexpected error: {e}")
|
|
return False
|
|
|
|
|
|
def test_blueprint_registration():
|
|
"""Test that blueprints are properly configured"""
|
|
print("\n🔍 Testing blueprint registration...")
|
|
|
|
try:
|
|
from app.routes.user import user_bp
|
|
from app.routes.time_entry_templates import time_entry_templates_bp
|
|
from app.routes.saved_filters import saved_filters_bp
|
|
|
|
# Check blueprint names
|
|
assert user_bp.name == 'user', "user_bp has wrong name"
|
|
assert time_entry_templates_bp.name == 'time_entry_templates', "time_entry_templates_bp has wrong name"
|
|
assert saved_filters_bp.name == 'saved_filters', "saved_filters_bp has wrong name"
|
|
print("✅ All blueprints properly configured")
|
|
|
|
return True
|
|
except AssertionError as e:
|
|
print(f"❌ Blueprint test failed: {e}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"❌ Unexpected error: {e}")
|
|
return False
|
|
|
|
|
|
def test_utility_functions():
|
|
"""Test that utility functions exist"""
|
|
print("\n🔍 Testing utility functions...")
|
|
|
|
try:
|
|
from app.utils.email import init_mail, send_email
|
|
from app.utils.excel_export import create_time_entries_excel, create_project_report_excel
|
|
from app.utils.scheduled_tasks import register_scheduled_tasks, check_overdue_invoices
|
|
|
|
# Check that functions are callable
|
|
assert callable(init_mail), "init_mail is not callable"
|
|
assert callable(send_email), "send_email is not callable"
|
|
assert callable(create_time_entries_excel), "create_time_entries_excel is not callable"
|
|
assert callable(create_project_report_excel), "create_project_report_excel is not callable"
|
|
assert callable(register_scheduled_tasks), "register_scheduled_tasks is not callable"
|
|
assert callable(check_overdue_invoices), "check_overdue_invoices is not callable"
|
|
print("✅ All utility functions are callable")
|
|
|
|
return True
|
|
except AssertionError as e:
|
|
print(f"❌ Utility function test failed: {e}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"❌ Unexpected error: {e}")
|
|
return False
|
|
|
|
|
|
def test_template_files():
|
|
"""Test that template files exist"""
|
|
print("\n🔍 Testing template files...")
|
|
|
|
template_files = [
|
|
'app/templates/user/settings.html',
|
|
'app/templates/user/profile.html',
|
|
'app/templates/email/overdue_invoice.html',
|
|
'app/templates/email/task_assigned.html',
|
|
'app/templates/email/weekly_summary.html',
|
|
'app/templates/email/comment_mention.html',
|
|
'app/templates/time_entry_templates/list.html',
|
|
'app/templates/time_entry_templates/create.html',
|
|
'app/templates/time_entry_templates/edit.html',
|
|
'app/templates/saved_filters/list.html',
|
|
'app/templates/components/save_filter_widget.html',
|
|
'app/templates/components/bulk_actions_widget.html',
|
|
'app/templates/components/keyboard_shortcuts_help.html',
|
|
]
|
|
|
|
missing = []
|
|
for template in template_files:
|
|
if not os.path.exists(template):
|
|
missing.append(template)
|
|
|
|
if missing:
|
|
print(f"❌ Missing templates: {', '.join(missing)}")
|
|
return False
|
|
else:
|
|
print(f"✅ All {len(template_files)} template files exist")
|
|
return True
|
|
|
|
|
|
def test_migration_file():
|
|
"""Test that migration file exists and has correct structure"""
|
|
print("\n🔍 Testing migration file...")
|
|
|
|
migration_file = 'migrations/versions/add_quick_wins_features.py'
|
|
|
|
if not os.path.exists(migration_file):
|
|
print(f"❌ Migration file not found: {migration_file}")
|
|
return False
|
|
|
|
try:
|
|
with open(migration_file, 'r') as f:
|
|
content = f.read()
|
|
|
|
# Check for required elements
|
|
required = [
|
|
"revision = '022'",
|
|
"down_revision = '021'",
|
|
'def upgrade():',
|
|
'def downgrade():',
|
|
'time_entry_templates',
|
|
'activities',
|
|
]
|
|
|
|
for req in required:
|
|
if req not in content:
|
|
print(f"❌ Migration missing required element: {req}")
|
|
return False
|
|
|
|
print("✅ Migration file is valid")
|
|
return True
|
|
except Exception as e:
|
|
print(f"❌ Error reading migration file: {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Run all tests"""
|
|
print("="*60)
|
|
print("🚀 Quick Wins Features - Validation Test")
|
|
print("="*60)
|
|
|
|
tests = [
|
|
("Imports", test_imports),
|
|
("Model Attributes", test_model_attributes),
|
|
("Model Methods", test_model_methods),
|
|
("Blueprint Registration", test_blueprint_registration),
|
|
("Utility Functions", test_utility_functions),
|
|
("Template Files", test_template_files),
|
|
("Migration File", test_migration_file),
|
|
]
|
|
|
|
results = []
|
|
for test_name, test_func in tests:
|
|
try:
|
|
result = test_func()
|
|
results.append((test_name, result))
|
|
except Exception as e:
|
|
print(f"\n❌ Test '{test_name}' crashed: {e}")
|
|
results.append((test_name, False))
|
|
|
|
# Summary
|
|
print("\n" + "="*60)
|
|
print("📊 Test Summary")
|
|
print("="*60)
|
|
|
|
passed = sum(1 for _, result in results if result)
|
|
total = len(results)
|
|
|
|
for test_name, result in results:
|
|
status = "✅ PASS" if result else "❌ FAIL"
|
|
print(f"{status} - {test_name}")
|
|
|
|
print("="*60)
|
|
print(f"Results: {passed}/{total} tests passed ({passed/total*100:.0f}%)")
|
|
print("="*60)
|
|
|
|
if passed == total:
|
|
print("\n🎉 All tests passed! Ready for deployment.")
|
|
return 0
|
|
else:
|
|
print(f"\n⚠️ {total - passed} test(s) failed. Please fix before deployment.")
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
exit(main())
|
|
|