Files
TimeTracker/docs/implementation-notes/ACTIVITY_LOGGING_INTEGRATION_GUIDE.md
T
Dries Peeters 29f7186ee8 docs: Reorganize documentation structure for better navigation
Complete reorganization of project documentation to improve discoverability,
navigation, and maintainability. All documentation has been restructured into
a clear, role-based hierarchy.

## Major Changes

### New Directory Structure
- Created `docs/api/` for API documentation
- Created `docs/admin/` with subdirectories:
  - `admin/configuration/` - Configuration guides
  - `admin/deployment/` - Deployment guides
  - `admin/security/` - Security documentation
  - `admin/monitoring/` - Monitoring and analytics
- Created `docs/development/` for developer documentation
- Created `docs/guides/` for user-facing guides
- Created `docs/reports/` for analysis reports and summaries
- Created `docs/changelog/` for detailed changelog entries (ready for future use)

### File Organization

#### Moved from Root Directory (40+ files)
- Implementation notes → `docs/implementation-notes/`
- Test reports → `docs/testing/`
- Analysis reports → `docs/reports/`
- User guides → `docs/guides/`

#### Reorganized within docs/
- API documentation → `docs/api/`
- Administrator documentation → `docs/admin/` (with subdirectories)
- Developer documentation → `docs/development/`
- Security documentation → `docs/admin/security/`
- Telemetry documentation → `docs/admin/monitoring/`

### Documentation Updates

#### docs/README.md
- Complete rewrite with improved navigation
- Added visual documentation map
- Organized by role (Users, Administrators, Developers)
- Better categorization and quick links
- Updated all internal links to new structure

#### README.md (root)
- Updated all documentation links to reflect new structure
- Fixed 8 broken links

#### app/templates/main/help.html
- Enhanced "Where can I get additional help?" section
- Added links to new documentation structure
- Added documentation index link
- Added admin documentation link for administrators
- Improved footer with organized documentation links
- Added "Complete Documentation" section with role-based links

### New Index Files
- Created README.md files for all new directories:
  - `docs/api/README.md`
  - `docs/guides/README.md`
  - `docs/reports/README.md`
  - `docs/development/README.md`
  - `docs/admin/README.md`

### Cleanup
- Removed empty `docs/security/` directory (moved to `admin/security/`)
- Removed empty `docs/telemetry/` directory (moved to `admin/monitoring/`)
- Root directory now only contains: README.md, CHANGELOG.md, LICENSE

## Results

**Before:**
- 45+ markdown files cluttering root directory
- Documentation scattered across root and docs/
- Difficult to find relevant documentation
- No clear organization structure

**After:**
- 3 files in root directory (README, CHANGELOG, LICENSE)
- Clear directory structure organized by purpose and audience
- Easy navigation with role-based organization
- All documentation properly categorized
- Improved discoverability

## Benefits

1. Better Organization - Documentation grouped by purpose and audience
2. Easier Navigation - Role-based sections (Users, Admins, Developers)
3. Improved Discoverability - Clear structure with README files in each directory
4. Cleaner Root - Only essential files at project root
5. Maintainability - Easier to add and organize new documentation

## Files Changed

- 40+ files moved from root to appropriate docs/ subdirectories
- 15+ files reorganized within docs/
- 3 major documentation files updated (docs/README.md, README.md, help.html)
- 5 new README index files created
- 2 empty directories removed

All internal links have been updated to reflect the new structure.
2025-12-14 07:56:07 +01:00

15 KiB

Activity Logging Integration Guide

This guide shows how to integrate Activity logging throughout the TimeTracker application.

Already Integrated

Projects (app/routes/projects.py)

  • Project creation - Line 173

🔧 Integration Pattern

Basic Pattern

from app.models import Activity

Activity.log(
    user_id=current_user.id,
    action='created',  # or 'updated', 'deleted', 'started', 'stopped', etc.
    entity_type='project',  # 'project', 'task', 'time_entry', 'invoice', etc.
    entity_id=entity.id,
    entity_name=entity.name,
    description=f'Human-readable description of what happened',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

📝 Integration Checklist

1. Projects (app/routes/projects.py)

Create Project - DONE (line 173)

Update Project - Add after successful update:

# Find: flash(f'Project "{project.name}" updated successfully', 'success')
# Add before it:
Activity.log(
    user_id=current_user.id,
    action='updated',
    entity_type='project',
    entity_id=project.id,
    entity_name=project.name,
    description=f'Updated project "{project.name}"',
    metadata={'fields_updated': ['name', 'description']},  # optional
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Archive Project - Find the archive route and add:

Activity.log(
    user_id=current_user.id,
    action='archived' if project.status == 'archived' else 'unarchived',
    entity_type='project',
    entity_id=project.id,
    entity_name=project.name,
    description=f'{"Archived" if project.status == "archived" else "Unarchived"} project "{project.name}"',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Delete Project - Add after successful deletion:

Activity.log(
    user_id=current_user.id,
    action='deleted',
    entity_type='project',
    entity_id=project_id,
    entity_name=project_name,  # Store before deletion
    description=f'Deleted project "{project_name}"',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

2. Tasks (app/routes/tasks.py)

Import: Add Activity to imports at the top:

from app.models import Task, Project, User, Activity

Create Task:

# After task creation and commit
Activity.log(
    user_id=current_user.id,
    action='created',
    entity_type='task',
    entity_id=task.id,
    entity_name=task.name,
    description=f'Created task "{task.name}" in project "{task.project.name if task.project else "No project"}"',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Update Task:

Activity.log(
    user_id=current_user.id,
    action='updated',
    entity_type='task',
    entity_id=task.id,
    entity_name=task.name,
    description=f'Updated task "{task.name}"',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Status Change (Important!):

Activity.log(
    user_id=current_user.id,
    action='status_changed',
    entity_type='task',
    entity_id=task.id,
    entity_name=task.name,
    description=f'Changed task "{task.name}" status from {old_status} to {new_status}',
    metadata={'old_status': old_status, 'new_status': new_status},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Task Assignment:

Activity.log(
    user_id=current_user.id,
    action='assigned',
    entity_type='task',
    entity_id=task.id,
    entity_name=task.name,
    description=f'Assigned task "{task.name}" to {assigned_user.display_name}',
    metadata={'assigned_to': assigned_user.id, 'assigned_to_name': assigned_user.display_name},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Delete Task:

Activity.log(
    user_id=current_user.id,
    action='deleted',
    entity_type='task',
    entity_id=task.id,
    entity_name=task.name,
    description=f'Deleted task "{task.name}"',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

3. Time Entries (app/routes/timer.py)

Import: Add Activity to imports

Start Timer:

# After timer starts successfully
Activity.log(
    user_id=current_user.id,
    action='started',
    entity_type='time_entry',
    entity_id=entry.id,
    entity_name=f'{entry.project.name if entry.project else "No project"}',
    description=f'Started timer for {entry.project.name if entry.project else "No project"}',
    metadata={'project_id': entry.project_id},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Stop Timer:

# After timer stops successfully
Activity.log(
    user_id=current_user.id,
    action='stopped',
    entity_type='time_entry',
    entity_id=entry.id,
    entity_name=f'{entry.project.name if entry.project else "No project"}',
    description=f'Stopped timer for {entry.project.name if entry.project else "No project"} - Duration: {entry.duration_formatted}',
    metadata={'duration_hours': entry.duration_hours, 'project_id': entry.project_id},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Manual Time Entry:

Activity.log(
    user_id=current_user.id,
    action='created',
    entity_type='time_entry',
    entity_id=entry.id,
    entity_name=f'{entry.project.name if entry.project else "No project"}',
    description=f'Added manual time entry for {entry.project.name if entry.project else "No project"} - {entry.duration_formatted}',
    metadata={'duration_hours': entry.duration_hours, 'source': 'manual'},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Edit Time Entry:

Activity.log(
    user_id=current_user.id,
    action='updated',
    entity_type='time_entry',
    entity_id=entry.id,
    entity_name=f'{entry.project.name if entry.project else "No project"}',
    description=f'Updated time entry for {entry.project.name if entry.project else "No project"}',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Delete Time Entry:

Activity.log(
    user_id=current_user.id,
    action='deleted',
    entity_type='time_entry',
    entity_id=entry.id,
    entity_name=f'{entry.project.name if entry.project else "No project"}',
    description=f'Deleted time entry for {entry.project.name if entry.project else "No project"} - {entry.duration_formatted}',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

4. Invoices (app/routes/invoices.py)

Import: Add Activity to imports

Create Invoice:

Activity.log(
    user_id=current_user.id,
    action='created',
    entity_type='invoice',
    entity_id=invoice.id,
    entity_name=invoice.invoice_number,
    description=f'Created invoice {invoice.invoice_number} for {invoice.client_name} - {invoice.currency_code} {invoice.total_amount}',
    metadata={'client_id': invoice.client_id, 'amount': float(invoice.total_amount)},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Update Invoice:

Activity.log(
    user_id=current_user.id,
    action='updated',
    entity_type='invoice',
    entity_id=invoice.id,
    entity_name=invoice.invoice_number,
    description=f'Updated invoice {invoice.invoice_number}',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Status Change:

Activity.log(
    user_id=current_user.id,
    action='status_changed',
    entity_type='invoice',
    entity_id=invoice.id,
    entity_name=invoice.invoice_number,
    description=f'Changed invoice {invoice.invoice_number} status from {old_status} to {new_status}',
    metadata={'old_status': old_status, 'new_status': new_status},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Payment Recorded:

Activity.log(
    user_id=current_user.id,
    action='paid',
    entity_type='invoice',
    entity_id=invoice.id,
    entity_name=invoice.invoice_number,
    description=f'Recorded payment for invoice {invoice.invoice_number} - {invoice.currency_code} {amount_paid}',
    metadata={'amount_paid': float(amount_paid), 'payment_method': payment_method},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Send Invoice:

Activity.log(
    user_id=current_user.id,
    action='sent',
    entity_type='invoice',
    entity_id=invoice.id,
    entity_name=invoice.invoice_number,
    description=f'Sent invoice {invoice.invoice_number} to {invoice.client_email}',
    metadata={'sent_to': invoice.client_email},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Delete Invoice:

Activity.log(
    user_id=current_user.id,
    action='deleted',
    entity_type='invoice',
    entity_id=invoice.id,
    entity_name=invoice.invoice_number,
    description=f'Deleted invoice {invoice.invoice_number}',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

5. Clients (app/routes/clients.py)

Import: Add Activity to imports

Create Client:

Activity.log(
    user_id=current_user.id,
    action='created',
    entity_type='client',
    entity_id=client.id,
    entity_name=client.name,
    description=f'Added new client "{client.name}"',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Update Client:

Activity.log(
    user_id=current_user.id,
    action='updated',
    entity_type='client',
    entity_id=client.id,
    entity_name=client.name,
    description=f'Updated client "{client.name}"',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

Delete Client:

Activity.log(
    user_id=current_user.id,
    action='deleted',
    entity_type='client',
    entity_id=client.id,
    entity_name=client.name,
    description=f'Deleted client "{client.name}"',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

6. Comments (app/routes/comments.py)

Create Comment:

Activity.log(
    user_id=current_user.id,
    action='commented',
    entity_type='task',
    entity_id=comment.task_id,
    entity_name=task.name,
    description=f'Commented on task "{task.name}"',
    metadata={'comment_id': comment.id},
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)

🎯 Quick Integration Script

Here's a Python script to help add Activity logging to a route:

def add_activity_logging(route_function_source_code):
    """
    Helper to suggest where to add Activity.log() calls.
    Returns suggested code to insert.
    """
    template = '''
# Log activity
Activity.log(
    user_id=current_user.id,
    action='ACTION_HERE',  # created, updated, deleted, started, stopped, etc.
    entity_type='ENTITY_TYPE',  # project, task, time_entry, invoice, client
    entity_id=entity.id,
    entity_name=entity.name,
    description=f'DESCRIPTION_HERE',
    ip_address=request.remote_addr,
    user_agent=request.headers.get('User-Agent')
)
'''
    return template

📊 Activity Action Types

Use these standardized action types:

Action When to Use
created When creating any entity
updated When modifying entity fields
deleted When removing an entity
started When starting a timer
stopped When stopping a timer
assigned When assigning tasks to users
commented When adding comments
status_changed When changing status fields
sent When sending invoices/emails
paid When recording payments
archived When archiving entities
unarchived When unarchiving entities

🧪 Testing Activity Logging

from app.models import Activity

# Get recent activities
activities = Activity.get_recent(limit=50)
for act in activities:
    print(f"{act.user.username}: {act.action} {act.entity_type} - {act.description}")

# Get activities by entity type
project_activities = Activity.get_recent(entity_type='project', limit=20)

# Get user-specific activities
user_activities = Activity.get_recent(user_id=user.id, limit=30)

📈 Performance Considerations

  1. Don't let activity logging break main flow:

    • Activity.log() includes try/except internally
    • Failures are logged but don't raise exceptions
  2. Batch operations:

    • For bulk operations, consider logging summary activities
  3. Database indexes:

    • Activity table has indexes on user_id, created_at, and composite indexes

🎨 Creating Activity Feed UI

Once Activity logging is integrated, create an activity feed widget:

app/templates/widgets/activity_feed.html:

<div class="activity-feed">
    <h3>Recent Activity</h3>
    {% for activity in activities %}
    <div class="activity-item">
        <i class="{{ activity.get_icon() }}"></i>
        <div>
            <strong>{{ activity.user.display_name }}</strong>
            {{ activity.description }}
        </div>
        <span class="timestamp">{{ activity.created_at|timeago }}</span>
    </div>
    {% endfor %}
</div>

Route for activity feed:

@main_bp.route('/api/activities')
@login_required
def get_activities():
    limit = request.args.get('limit', 20, type=int)
    entity_type = request.args.get('entity_type')
    
    activities = Activity.get_recent(
        user_id=current_user.id if not current_user.is_admin else None,
        limit=limit,
        entity_type=entity_type
    )
    
    return jsonify({
        'activities': [a.to_dict() for a in activities]
    })

Integration Checklist

Use this to track your progress:

  • Projects - Create (DONE)
  • Projects - Update
  • Projects - Delete
  • Projects - Archive/Unarchive
  • Tasks - Create
  • Tasks - Update
  • Tasks - Delete
  • Tasks - Status Change
  • Tasks - Assignment
  • Time Entries - Start Timer
  • Time Entries - Stop Timer
  • Time Entries - Manual Create
  • Time Entries - Update
  • Time Entries - Delete
  • Invoices - Create
  • Invoices - Update
  • Invoices - Status Change
  • Invoices - Payment
  • Invoices - Send
  • Invoices - Delete
  • Clients - Create
  • Clients - Update
  • Clients - Delete
  • Comments - Create
  • User Settings - Update (DONE in user.py)

Estimated Time: 2-3 hours to integrate activity logging throughout the entire application.

Priority Areas: Start with Projects, Tasks, and Time Entries as these are the most frequently used features.