Skip to main content

Activity Timeline - User Guide

Overview

The Activity Timeline provides a unified feed of all user activities across the CODITECT Dev Context platform. It aggregates activities from different sources (sessions, commits, tasks, messages, files) into a single, chronologically-ordered timeline.

Purpose:

  • Track user productivity and activity patterns
  • Provide context for collaboration and handoffs
  • Generate activity reports and analytics
  • Enable activity-based notifications and alerts

Table of Contents

  1. Model Structure
  2. Activity Types
  3. API Endpoints
  4. Usage Examples
  5. Creating Timeline Entries
  6. Filtering and Querying
  7. Integration with Existing Models
  8. Performance Considerations

Model Structure

ActivityTimeline Model

Location: backend/models.py

Fields:

  • id - UUID primary key
  • tenant - ForeignKey to Tenant (multi-tenant isolation)
  • user - ForeignKey to User (who performed the activity)
  • project - ForeignKey to Project (optional, if activity is project-specific)
  • activity_type - CharField (type of activity, see Activity Types)
  • content_type - ForeignKey to ContentType (polymorphic reference to source model)
  • object_id - UUID (ID of the source object)
  • title - CharField(500) (human-readable title)
  • description - TextField (detailed description, optional)
  • timestamp - DateTimeField (when the activity occurred)
  • metadata - JSONField (additional activity-specific data)
  • created_at - DateTimeField (when the timeline entry was created)

Properties:

  • activity_icon - Returns icon name for frontend display
  • activity_color - Returns color class for frontend display

Indexes:

  • (tenant, timestamp) - Primary query pattern
  • (user, timestamp) - User-specific timelines
  • (project, timestamp) - Project-specific timelines
  • (activity_type, timestamp) - Filter by activity type
  • (content_type, object_id) - Lookup source object

Activity Types

The following activity types are currently supported:

Session Activities

  • session_start - Session Started
  • session_end - Session Ended

Git Activities

  • commit - Git Commit
  • branch_create - Branch Created
  • branch_merge - Branch Merged

Task Activities

  • task_created - Task Created
  • task_completed - Task Completed
  • task_updated - Task Updated
  • task_deleted - Task Deleted

Communication Activities

  • message_sent - Message Sent
  • conversation_started - Conversation Started

Project Activities

  • project_created - Project Created
  • project_updated - Project Updated

File Activities

  • file_created - File Created
  • file_modified - File Modified
  • file_deleted - File Deleted

Command Activities

  • command_executed - Command Executed

API Endpoints

Base URL: /api/v1/activities/

List Activities

GET /api/v1/activities/

Query Parameters:

  • project - Filter by project ID
  • user - Filter by user ID
  • activity_type - Filter by activity type (e.g., commit, task_completed)
  • date_from - Filter activities from this date (ISO format: 2025-01-15)
  • date_to - Filter activities until this date (ISO format: 2025-01-20)
  • search - Search in title and description
  • ordering - Order by field (e.g., -timestamp for newest first)

Response:

{
"count": 150,
"next": "/api/v1/activities/?page=2",
"previous": null,
"results": [
{
"id": "uuid-here",
"tenant": "uuid-here",
"user": "uuid-here",
"user_name": "John Doe",
"user_email": "john@example.com",
"project": "uuid-here",
"project_name": "My Project",
"activity_type": "commit",
"activity_type_display": "Git Commit",
"activity_icon": "git-commit",
"activity_color": "blue",
"title": "feat: Add user authentication",
"description": "Implemented JWT-based authentication with refresh tokens",
"timestamp": "2025-01-15T14:30:00Z",
"metadata": {
"commit_hash": "abc123",
"files_changed": 5,
"lines_added": 150,
"lines_removed": 20
},
"related_object_details": {
"type": "commit",
"commit_hash": "abc123",
"message": "feat: Add user authentication",
"author": "John Doe"
},
"created_at": "2025-01-15T14:31:00Z"
}
]
}

Get Today's Activities

GET /api/v1/activities/today/

Response:

{
"count": 15,
"date": "2025-01-15",
"activities": [...]
}

Get This Week's Activities

GET /api/v1/activities/week/

Response:

{
"count": 87,
"week_start": "2025-01-13",
"week_end": "2025-01-19",
"activities": [...]
}

Get Activities by Project

GET /api/v1/activities/by_project/{project_id}/

Response:

{
"project_id": "uuid-here",
"total_count": 42,
"by_type": [
{"activity_type": "commit", "count": 15},
{"activity_type": "task_completed", "count": 12},
{"activity_type": "message_sent", "count": 10},
{"activity_type": "session_start", "count": 5}
],
"activities": [...]
}

Get Activity Statistics

GET /api/v1/activities/stats/

Response:

{
"total_activities": 1542,
"recent_7_days": 87,
"by_type": [
{"activity_type": "message_sent", "count": 450},
{"activity_type": "commit", "count": 320},
{"activity_type": "task_completed", "count": 180}
],
"top_users": [
{
"user_id": "uuid",
"user__email": "john@example.com",
"user__first_name": "John",
"user__last_name": "Doe",
"count": 250
}
],
"top_projects": [
{
"project_id": "uuid",
"project__name": "Backend API",
"count": 450
}
]
}

Usage Examples

Frontend Integration (JavaScript/TypeScript)

// Fetch today's activities
async function getTodayActivities() {
const response = await fetch('/api/v1/activities/today/', {
headers: {
'Authorization': `Bearer ${accessToken}`,
}
});
const data = await response.json();
return data.activities;
}

// Filter activities by project and date range
async function getProjectActivities(projectId, dateFrom, dateTo) {
const params = new URLSearchParams({
project: projectId,
date_from: dateFrom,
date_to: dateTo,
ordering: '-timestamp'
});

const response = await fetch(`/api/v1/activities/?${params}`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
}
});
return response.json();
}

// Get activity statistics
async function getActivityStats() {
const response = await fetch('/api/v1/activities/stats/', {
headers: {
'Authorization': `Bearer ${accessToken}`,
}
});
return response.json();
}

Python Client

import requests
from datetime import datetime, timedelta

class ActivityTimelineClient:
def __init__(self, base_url, token):
self.base_url = base_url
self.headers = {'Authorization': f'Bearer {token}'}

def get_activities(self, **filters):
"""Get activities with optional filters"""
response = requests.get(
f'{self.base_url}/api/v1/activities/',
params=filters,
headers=self.headers
)
return response.json()

def get_today(self):
"""Get today's activities"""
response = requests.get(
f'{self.base_url}/api/v1/activities/today/',
headers=self.headers
)
return response.json()

def get_user_activities(self, user_id, days=7):
"""Get user activities for the last N days"""
date_from = (datetime.now() - timedelta(days=days)).isoformat()
return self.get_activities(user=user_id, date_from=date_from)

def get_project_activities(self, project_id):
"""Get all activities for a project"""
response = requests.get(
f'{self.base_url}/api/v1/activities/by_project/{project_id}/',
headers=self.headers
)
return response.json()

# Usage
client = ActivityTimelineClient('https://api.coditect.ai', 'your-token')
activities = client.get_today()
print(f"Today's activities: {activities['count']}")

Creating Timeline Entries

Manual Creation

from django.contrib.contenttypes.models import ContentType
from backend.models import ActivityTimeline, GitCommit, User, Project

# Example: Create activity when a git commit is made
commit = GitCommit.objects.get(commit_hash='abc123')
user = User.objects.get(email='john@example.com')
project = Project.objects.get(name='My Project')

ActivityTimeline.objects.create(
tenant=user.tenant,
user=user,
project=project,
activity_type='commit',
content_type=ContentType.objects.get_for_model(GitCommit),
object_id=commit.id,
title=f"Committed: {commit.message[:50]}",
description=commit.message,
timestamp=commit.committed_at,
metadata={
'commit_hash': commit.commit_hash,
'files_changed': commit.files_changed,
'lines_added': commit.lines_added,
'lines_removed': commit.lines_removed,
}
)

Location: backend/signals.py (create this file)

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.contenttypes.models import ContentType
from .models import (
GitCommit, Task, Session, Message, Project,
ActivityTimeline
)

@receiver(post_save, sender=GitCommit)
def create_commit_activity(sender, instance, created, **kwargs):
"""Create timeline entry when a commit is created"""
if created:
ActivityTimeline.objects.create(
tenant=instance.repository.tenant,
user=instance.repository.tenant.user_set.first(), # Or get from commit metadata
project=instance.repository.projects.first(),
activity_type='commit',
content_type=ContentType.objects.get_for_model(GitCommit),
object_id=instance.id,
title=f"Committed: {instance.message[:50]}",
description=instance.message,
timestamp=instance.committed_at,
metadata={
'commit_hash': instance.commit_hash,
'files_changed': instance.files_changed,
}
)

@receiver(post_save, sender=Task)
def create_task_activity(sender, instance, created, **kwargs):
"""Create timeline entry for task creation/completion"""
if created:
activity_type = 'task_created'
title = f"Created task: {instance.title}"
elif instance.current_status and instance.current_status.status == 'completed':
activity_type = 'task_completed'
title = f"Completed task: {instance.title}"
else:
activity_type = 'task_updated'
title = f"Updated task: {instance.title}"

ActivityTimeline.objects.create(
tenant=instance.tenant,
user=instance.created_by,
project=instance.task_list.project,
activity_type=activity_type,
content_type=ContentType.objects.get_for_model(Task),
object_id=instance.id,
title=title,
description=instance.description,
timestamp=instance.updated_at,
metadata={
'priority': instance.priority,
'status': instance.current_status.status if instance.current_status else None,
}
)

@receiver(post_save, sender=Session)
def create_session_activity(sender, instance, created, **kwargs):
"""Create timeline entry for session start/end"""
if created:
ActivityTimeline.objects.create(
tenant=instance.tenant,
user=instance.user,
project=instance.project,
activity_type='session_start',
content_type=ContentType.objects.get_for_model(Session),
object_id=instance.id,
title=f"Started session: {instance.title}",
description=instance.description,
timestamp=instance.started_at,
)
elif instance.ended_at:
ActivityTimeline.objects.create(
tenant=instance.tenant,
user=instance.user,
project=instance.project,
activity_type='session_end',
content_type=ContentType.objects.get_for_model(Session),
object_id=instance.id,
title=f"Ended session: {instance.title}",
description=instance.description,
timestamp=instance.ended_at,
metadata={
'duration_minutes': (instance.ended_at - instance.started_at).total_seconds() / 60,
}
)

Register signals in backend/apps.py:

from django.apps import AppConfig

class BackendConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'backend'
verbose_name = 'CODITECT Backend'

def ready(self):
# Import signals to register them
import backend.signals # noqa

Filtering and Querying

Filter by Multiple Activity Types

# Get all task-related activities
activities = ActivityTimeline.objects.filter(
activity_type__in=['task_created', 'task_completed', 'task_updated']
)

Filter by Date Range

from datetime import datetime, timedelta

# Get activities from the last 7 days
seven_days_ago = datetime.now() - timedelta(days=7)
activities = ActivityTimeline.objects.filter(
timestamp__gte=seven_days_ago
)

Filter by User and Project

# Get user's activities on a specific project
activities = ActivityTimeline.objects.filter(
user_id=user_id,
project_id=project_id
).order_by('-timestamp')

Activity Count by Type

from django.db.models import Count

# Get activity counts grouped by type
activity_counts = ActivityTimeline.objects.values('activity_type').annotate(
count=Count('id')
).order_by('-count')

Integration with Existing Models

Session Model

class Session(models.Model):
# ... existing fields ...

def create_timeline_entry(self, activity_type):
"""Helper method to create timeline entry"""
from django.contrib.contenttypes.models import ContentType

ActivityTimeline.objects.create(
tenant=self.tenant,
user=self.user,
project=self.project,
activity_type=activity_type,
content_type=ContentType.objects.get_for_model(Session),
object_id=self.id,
title=f"{activity_type.replace('_', ' ').title()}: {self.title}",
description=self.description,
timestamp=self.ended_at if activity_type == 'session_end' else self.started_at,
)

Task Model

class Task(models.Model):
# ... existing fields ...

def mark_completed(self):
"""Mark task as completed and create timeline entry"""
self.current_status.status = 'completed'
self.current_status.save()

ActivityTimeline.objects.create(
tenant=self.tenant,
user=self.created_by,
project=self.task_list.project,
activity_type='task_completed',
content_type=ContentType.objects.get_for_model(Task),
object_id=self.id,
title=f"Completed: {self.title}",
timestamp=timezone.now(),
)

Performance Considerations

Database Indexes

The ActivityTimeline model has comprehensive indexes for common query patterns:

indexes = [
models.Index(fields=['tenant', '-timestamp']), # Most common: tenant timeline
models.Index(fields=['user', '-timestamp']), # User-specific timeline
models.Index(fields=['project', '-timestamp']), # Project-specific timeline
models.Index(fields=['activity_type', '-timestamp']), # Filter by type
models.Index(fields=['content_type', 'object_id']), # Lookup source object
]

Query Optimization

Use select_related for foreign keys:

# Good - One query with JOIN
activities = ActivityTimeline.objects.select_related(
'user', 'project', 'content_type'
).filter(timestamp__gte=seven_days_ago)

# Bad - N+1 queries
activities = ActivityTimeline.objects.filter(
timestamp__gte=seven_days_ago
) # Each access to activity.user triggers a query

Pagination

Always paginate large result sets:

from django.core.paginator import Paginator

activities = ActivityTimeline.objects.all().order_by('-timestamp')
paginator = Paginator(activities, 50) # 50 items per page
page = paginator.get_page(1)

Archiving Old Activities

Consider archiving or deleting very old timeline entries to keep the table size manageable:

from datetime import datetime, timedelta

# Archive activities older than 1 year
one_year_ago = datetime.now() - timedelta(days=365)
ActivityTimeline.objects.filter(
timestamp__lt=one_year_ago
).delete()

Future Enhancements

Potential future improvements to the Activity Timeline:

  1. Real-time Updates - WebSocket push for live activity feed
  2. Activity Aggregation - Group similar activities (e.g., "5 commits in the last hour")
  3. Smart Notifications - AI-based activity pattern detection and alerts
  4. Activity Search - Full-text search across activity titles and descriptions
  5. Activity Export - Export timeline to CSV, PDF, or calendar formats
  6. Collaboration Insights - Team activity heatmaps and collaboration graphs
  7. Activity-based Recommendations - Suggest tasks based on activity patterns

Version: 2.0.0 Last Updated: 2025-11-26 Maintainer: AZ1.AI Development Team