Skip to main content
Back to ScopeForged

ScopeForged Documentation

Technical documentation, guides, and feature references for the ScopeForged client portal.

Admin & Compliance/Activity Logging

Activity Logging Guide

Last Updated: 2026-01-13 Status: Enhanced with Dashboard Widget, Quick Filters & Comparison View Plan Reference: 011-activity-logging.md, 076-activity-logging-improvement.md, 097-playwright-activity-logging-tests.md


Overview

The Activity Logging system provides a comprehensive audit trail of all significant actions performed in the portal. It tracks user actions, data changes, and system events to support auditing, debugging, and compliance requirements.


Table of Contents

  1. Accessing Activity Logs
  2. What Gets Logged
  3. How to Use
  4. Technical Architecture
  5. Related Features

Accessing Activity Logs

Access PointLocationURLRole
Activity LogAdmin sidebar/admin/activityAdmin
Entity ActivityDetail pagesTab on entityAdmin
User ActivityUser profile/admin/users/{id}/activityAdmin
My ActivityProfile/profile/activityBoth

Permissions

ActionAdminClient User
View all activity
View own activity
View client activityOwn client only
Export activity logs
Delete activity logs

What Gets Logged

User Actions

ActionEvent TypeDetails Logged
Loginuser.loginIP, user agent, timestamp
Logoutuser.logoutSession duration
Password changeuser.password_changedUser ID
Profile updateuser.updatedChanged fields
Failed loginauth.failedEmail attempted, IP

Entity Operations

EntityEventsDetails
Clientcreated, updated, deletedAll changed fields
Projectcreated, updated, status_changed, deletedFields + old/new values
Invoicecreated, sent, paid, deletedStatus changes, amounts
Fileuploaded, downloaded, deletedFilename, size, user

System Events

EventTypeDetails
Email sentmail.sentRecipient, subject
Notificationnotification.sentType, recipient
Export generatedexport.createdType, format, user
Settings changedsettings.updatedKey, old/new value

How to Use

Viewing Activity Log

  1. Navigate to Admin → Activity Log
  2. View chronological list of activities
  3. Each entry shows:
    • Timestamp
    • User who performed action
    • Action type
    • Entity affected
    • Description

Filtering Activities

FilterOptions
Date RangeToday, This Week, Custom
UserSelect specific user
Action TypeLogin, Create, Update, Delete
Entity TypeClient, Project, Invoice, File
SearchFree-text search

Viewing Entity Activity

  1. Navigate to entity detail (e.g., Client)
  2. Click "Activity" tab
  3. View activity related to that entity only

Exporting Activity

  1. Apply desired filters
  2. Click "Export"
  3. Select format (CSV, PDF)
  4. Download file

Log Entry Details

Standard Log Entry

{
  "id": 12345,
  "user_id": 1,
  "event": "client.updated",
  "subject_type": "App\\Models\\Client",
  "subject_id": 42,
  "properties": {
    "old": {
      "company_name": "Old Name",
      "email": "old@example.com"
    },
    "new": {
      "company_name": "New Name",
      "email": "new@example.com"
    }
  },
  "ip_address": "192.168.1.1",
  "user_agent": "Mozilla/5.0...",
  "created_at": "2026-01-09T14:30:00Z"
}

Viewing Entry Details

  1. Click on any log entry
  2. Expand to see full details:
    • Changed fields (before/after)
    • User information
    • IP address and device
    • Timestamp

Technical Architecture

Model

Location: app/Models/ActivityLog.php

class ActivityLog extends Model
{
    protected $fillable = [
        'user_id',
        'event',
        'subject_type',
        'subject_id',
        'causer_type',
        'causer_id',
        'properties',
        'ip_address',
        'user_agent',
    ];

    protected $casts = [
        'properties' => 'array',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function subject(): MorphTo
    {
        return $this->morphTo();
    }

    public function causer(): MorphTo
    {
        return $this->morphTo();
    }
}

Logging Trait

Location: app/Traits/LogsActivity.php

trait LogsActivity
{
    protected static function bootLogsActivity(): void
    {
        static::created(function ($model) {
            activity()
                ->performedOn($model)
                ->causedBy(auth()->user())
                ->withProperties(['new' => $model->toArray()])
                ->log('created');
        });

        static::updated(function ($model) {
            activity()
                ->performedOn($model)
                ->causedBy(auth()->user())
                ->withProperties([
                    'old' => $model->getOriginal(),
                    'new' => $model->getChanges(),
                ])
                ->log('updated');
        });

        static::deleted(function ($model) {
            activity()
                ->performedOn($model)
                ->causedBy(auth()->user())
                ->withProperties(['old' => $model->toArray()])
                ->log('deleted');
        });
    }
}

Activity Logger Service

Location: app/Services/ActivityLogger.php

class ActivityLogger
{
    public function log(string $event, ?Model $subject = null, array $properties = []): ActivityLog
    {
        return ActivityLog::create([
            'user_id' => auth()->id(),
            'event' => $event,
            'subject_type' => $subject ? get_class($subject) : null,
            'subject_id' => $subject?->id,
            'properties' => $properties,
            'ip_address' => request()->ip(),
            'user_agent' => request()->userAgent(),
        ]);
    }
}

Usage in Models

class Client extends Model
{
    use LogsActivity;

    // Activity logging is automatic for create, update, delete
}

Manual Logging

// Log custom activity
activity()
    ->performedOn($invoice)
    ->causedBy(auth()->user())
    ->withProperties(['status' => 'paid', 'amount' => $invoice->total])
    ->log('invoice.paid');

// Or via service
app(ActivityLogger::class)->log('invoice.paid', $invoice, [
    'status' => 'paid',
    'amount' => $invoice->total,
]);

Controller

Location: app/Http/Controllers/Admin/ActivityLogController.php

MethodRouteDescription
index()GET /admin/activityList all activity
show()GET /admin/activity/{log}View details
export()GET /admin/activity/exportExport logs

Routes

Route::prefix('admin/activity')->group(function () {
    Route::get('/', [ActivityLogController::class, 'index'])->name('admin.activity.index');
    Route::get('/export', [ActivityLogController::class, 'export'])->name('admin.activity.export');
    Route::get('/{log}', [ActivityLogController::class, 'show'])->name('admin.activity.show');
});

Database Schema

Table: activity_logs

ColumnTypeDescription
idbigintPrimary key
user_idbigintUser who acted
eventstringEvent type
subject_typestringModel class
subject_idbigintModel ID
causer_typestringWho caused it
causer_idbigintCauser ID
propertiesjsonAdditional data
ip_addressstringClient IP
user_agentstringBrowser info
created_attimestampWhen logged

Indexes

// Migration
$table->index(['subject_type', 'subject_id']);
$table->index(['user_id', 'created_at']);
$table->index('event');
$table->index('created_at');

Retention Policy

Default Retention

Log TypeRetention Period
Security events1 year
User actions90 days
System events30 days

Cleanup Command

php artisan activity:cleanup --days=90

Scheduled Cleanup

// app/Console/Kernel.php
$schedule->command('activity:cleanup --days=90')->daily();

Advanced Features

Dashboard Widget (New)

The Activity Summary widget displays key activity metrics on the admin dashboard.

Component: resources/views/components/activity-summary-widget.blade.php

Features:

  • Total activity count for selected period
  • Action breakdown by type (created, updated, deleted, etc.)
  • Top active users with activity counts
  • Recent activity timeline
  • Period selector (day, week, month)

Usage in Dashboard:

<x-activity-summary-widget
    :stats="$activityStats"
    :period="$activityPeriod"
/>

Clickable Elements:

  • Action badges link to filtered activity list
  • User names link to user-filtered activity list
  • Recent activities link to comparison view for updates

One-Click Filtering (New)

The activity index page now supports instant filtering by clicking on any attribute:

Clickable Elements:

ElementFilter AppliedExample URL
User name?user_id={id}/admin/activity?user_id=1
Action badge?action={action}/admin/activity?action=created
IP address?ip_address={ip}/admin/activity?ip_address=192.168.1.1
Subject type?subject_type={type}/admin/activity?subject_type=Client

Visual Indicators:

  • Active filters are highlighted with indigo color
  • Action badges show ring indicator when active
  • Clear button appears when any filter is active

Comparison View (New)

The comparison view displays before/after changes for update activities.

Access: /admin/activity/comparison

Features:

  • Shows only "updated" activities with change data
  • Expandable cards for each activity
  • Side-by-side before/after comparison table
  • Field-by-field change visualization
  • Red highlighting for old values
  • Green highlighting for new values
  • Support for arrays, booleans, and null values

Filtering:

FilterDescription
UserFilter by who made the change
Resource TypeFilter by entity type (Client, Project, etc.)
Date RangeFilter by change date

Controller Method: ActivityController@comparison

Timeline View

The timeline view provides a visual, chronological display of activity logs grouped by date.

Access: /admin/activity/timeline

Features:

  • Activities grouped by date
  • Visual timeline with color-coded action types
  • Date range filtering
  • User and action filtering
  • Quick navigation between days

Advanced Filtering

The ActivityFilterService provides comprehensive filtering:

Location: app/Services/ActivityFilterService.php

class ActivityFilterService
{
    public function filter(Request $request): LengthAwarePaginator
    {
        // Supports: user_id, action, subject_type, date_from, date_to, ip_address, search
    }

    public function getAvailableFilters(): array
    {
        // Returns available actions and subject types
    }

    public function getStatistics(string $period = 'week'): array
    {
        // Returns activity counts by action and top users
    }
}

Available Filters:

FilterDescriptionExample
user_idFilter by specific user?user_id=1
actionFilter by action type?action=created
subject_typeFilter by entity type?subject_type=Client
date_fromStart date?date_from=2026-01-01
date_toEnd date?date_to=2026-01-31
ip_addressFilter by IP?ip_address=192.168
searchSearch description/properties?search=invoice

Export Functionality

Export activity logs in CSV or JSON format.

Location: app/Services/ActivityExportService.php

class ActivityExportService
{
    public function toCsv(Collection $activities): string;
    public function toJson(Collection $activities): string;
    public function toArray(Collection $activities): array;
}

Routes:

RouteDescription
GET /admin/activity/export?format=csvExport as CSV
GET /admin/activity/export?format=jsonExport as JSON
GET /admin/activity/export?limit=500Limit export to 500 records

Filters can be combined with export:

/admin/activity/export?format=csv&action=created&date_from=2026-01-01

Log Archival

Archive old logs to storage before deletion:

Command: php artisan activity:archive

# Archive logs older than 90 days
php artisan activity:archive --days=90

# Preview without making changes
php artisan activity:archive --days=90 --dry-run

# Archive but keep original records
php artisan activity:archive --days=90 --keep

Archive Location: storage/app/activity-logs/archive-{date}.json

Archive Format:

{
  "archived_at": "2026-01-12T10:30:00Z",
  "cutoff_date": "2025-10-14T00:00:00Z",
  "count": 1234,
  "activities": [...]
}

Scheduled Archival:

// app/Console/Kernel.php
$schedule->command('activity:archive --days=90')->monthly();

Activity Statistics API

Get activity statistics for dashboards:

Route: GET /admin/activity/statistics?period=week

Periods: day, week, month, year

Response:

{
  "period": "week",
  "start_date": "2026-01-06T00:00:00Z",
  "total_count": 523,
  "by_action": {
    "created": 145,
    "updated": 234,
    "deleted": 12,
    "login": 132
  },
  "top_users": [
    {"user": "John Doe", "count": 89},
    {"user": "Jane Smith", "count": 67}
  ]
}

Dependencies

FeatureRelationship
AuthenticationLogs auth events
AuthorizationControls log access

Complementary Features

FeatureDescription
Audit ComplianceCompliance reporting
ReportsActivity reports
AnalyticsActivity metrics

Best Practices

For Security

  1. Don't log sensitive data (passwords, tokens)
  2. Index for efficient queries
  3. Archive old logs instead of deleting
  4. Protect log access with authorization

For Developers

  1. Use traits for automatic logging
  2. Log meaningful events only
  3. Include context in properties
  4. Consider performance for high-volume

Testing

E2E Tests (Playwright)

Activity logging includes comprehensive end-to-end tests using Playwright.

Location: e2e/activity-logging/

Test FileDescription
index.spec.tsActivity log list and display
filtering.spec.tsFilter functionality
detail.spec.tsDetail view tests
timeline.spec.tsTimeline view tests
export.spec.tsExport functionality
statistics.spec.tsStatistics page
permissions.spec.tsAccess control
logging.spec.tsVerify logging works
entity-activity.spec.tsEntity-specific activity

Running Tests:

# Run all E2E tests
npm run test:e2e

# Run with UI
npm run test:e2e:ui

# Run headed (visible browser)
npm run test:e2e:headed

# Debug mode
npm run test:e2e:debug

# View report
npm run test:e2e:report

Troubleshooting

IssueSolution
Logs not appearingCheck trait is used on model
Performance slowAdd indexes, prune old logs
Too much loggingConfigure what to log
Missing userCheck auth middleware order

See Also