### Example URL with Filters Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/2.filament-ui.md An example of a URL demonstrating the use of query parameters for filtering. These parameters correspond to the `#[Url]` attributes defined in the Livewire component. ```text /admin/people/42?type=activity_log&from=2026-01-01&to=2026-04-30 ``` -------------------------------- ### Setup BeforeEach Hook Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/5.testing/1.index.md Log in an admin user and create a record before each test that interacts with the timeline UI. ```php use App\Models\Person; use App\Models\User; beforeEach(function (): void { $this->actingAs(User::factory()->admin()->create()); $this->record = Person::factory()->create(); }); ``` -------------------------------- ### Install Package via Composer Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/1.getting-started/1.installation.md Use this command to add the relaticle/activity-log package to your project dependencies. ```bash composer require relaticle/activity-log ``` -------------------------------- ### Setup Base Test Case Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/5.testing/1.index.md Configure the base test case for Pest, enabling database refreshing for feature tests. ```php // tests/Pest.php use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; uses(TestCase::class, RefreshDatabase::class)->in('Feature'); ``` -------------------------------- ### Minimal Custom Timeline Renderer Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/4.customization.md Implement a custom timeline renderer by extending the TimelineRenderer contract. This example delegates to ActivityLogSummary for data and renders a custom Blade view. ```php use Illuminate\Contracts\View\View; use Relaticle\ActivityLog\Contracts\TimelineRenderer; use Relaticle\ActivityLog\Support\ActivityLogSummary; use Relaticle\ActivityLog\Timeline\TimelineEntry; final class AuditedActivityLogRenderer implements TimelineRenderer { public function render(TimelineEntry $entry): View { $summary = ActivityLogSummary::from($entry); return view('audit.timeline.entry', [ 'entry' => $entry, 'summary' => $summary, ]); } } ``` -------------------------------- ### Blade View for Custom Timeline Entry Source: https://context7.com/relaticle/activity-log/llms.txt Example Blade view for rendering a custom timeline entry, utilizing Filament components and displaying entry details. ```blade {{-- resources/views/app/timeline/email-sent.blade.php --}}
{{ $entry->title }}
{{ $entry->description }} · {{ $entry->occurredAt->diffForHumans() }}
``` -------------------------------- ### Retrieve Timeline Entries Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/3.refining-the-timeline.md Fetch timeline entries using `get()` for a collection, `paginate()` for paginated results, or `count()` for the total number of entries. Note that `count()` runs `get()` internally. ```php $entries = $record->timeline()->fromActivityLog()->get(); ``` ```php $page = $record->timeline()->fromActivityLog()->paginate(perPage: 25, page: 2); ``` ```php $total = $record->timeline()->fromActivityLog()->count(); ``` -------------------------------- ### Get entries from the subject's own activity log Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/1.sources.md Use `fromActivityLog()` to register the `ActivityLogSource`. This reads from the `activity_log` table where `subject_type` and `subject_id` match the subject. The subject must be persisted before calling `timeline()`. The default priority is 10. ```php public function fromActivityLog(?int $priority = null): self ``` ```php $entries = $record->timeline() ->fromActivityLog() ->get(); ``` -------------------------------- ### Enable Per-Call Caching with TTL Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/5.caching.md Wrap a `paginate()` call with `cached()` to enable per-call caching. Specify the Time-To-Live in seconds. This should be called before `paginate()` or `get()`. ```php $entries = $record->timeline()->cached(ttlSeconds: 300)->paginate(); ``` -------------------------------- ### Get entries from related models' activity logs Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/1.sources.md Use `fromActivityLogOf()` to register the `RelatedActivityLogSource`. This loads related rows and then queries `activity_log` for matching `(subject_type, subject_id)` pairs. Be aware of potential performance issues with relations containing thousands of rows, as all related rows are loaded into memory. ```php public function fromActivityLogOf(array $relations, ?int $priority = null): self ``` ```php $entries = $opportunity->timeline() ->fromActivityLogOf(['comments', 'tasks']) ->get(); ``` -------------------------------- ### Publish Configuration File Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/1.getting-started/1.installation.md Run this Artisan command to publish the configuration file if you need to override default settings. ```bash php artisan vendor:publish --tag=activity-log-config ``` -------------------------------- ### Run Tests Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/7.community/1.contributing.md Execute the project's test suite using Composer. ```bash composer test ``` -------------------------------- ### Registering Timeline Sources Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/2.concepts/1.how-it-works.md Demonstrates how to chain different built-in sources to the TimelineBuilder, including custom configurations for related models. ```php $record->timeline() ->fromActivityLog() // ActivityLogSource ->fromActivityLogOf(['comments']) // RelatedActivityLogSource ->fromRelation('invoices', fn ($s) => $s->title(...)) // RelatedModelSource ->fromCustom(fn ($subject, $window) => yield ...); // CustomEventSource ``` -------------------------------- ### Make Model Timeline-Capable Source: https://github.com/relaticle/activity-log/blob/1.x/README.md Implement the HasTimeline contract and use the InteractsWithTimeline concern in your Eloquent model. Define the timeline method to configure the TimelineBuilder, starting with the fromActivityLog source. ```php use Relaticle\ActivityLog\Concerns\InteractsWithTimeline; use Relaticle\ActivityLog\Contracts\HasTimeline; use Relaticle\ActivityLog\Timeline\TimelineBuilder; use Spatie\Activitylog\Traits\LogsActivity; class Person extends Model implements HasTimeline { use InteractsWithTimeline; use LogsActivity; public function timeline(): TimelineBuilder { return TimelineBuilder::make($this)->fromActivityLog(); } } ``` -------------------------------- ### Activity Log Configuration Reference Source: https://context7.com/relaticle/activity-log/llms.txt This is a reference for the `config/activity-log.php` file, showing default values and options for pagination, deduplication, source priorities, renderers, and caching. ```php // config/activity-log.php (after vendor:publish --tag=activity-log-config) return [ 'default_per_page' => 20, // used by paginate(null); UI surfaces have own defaults 'pagination_buffer' => 2, // cap = perPage × (page + buffer); increase for heavy dedup 'deduplicate_by_default' => true, // override per-call with ->deduplicate(false) 'source_priorities' => [ 'activity_log' => 10, // ActivityLogSource default priority 'related_activity_log' => 10, // RelatedActivityLogSource priority (entries still type='activity_log') 'related_model' => 20, // RelatedModelSource default priority 'custom' => 30, // CustomEventSource default priority ], 'renderers' => [ // 'email_sent' => \App\Timeline\Renderers\EmailSentRenderer::class, ], 'cache' => [ 'store' => null, // null = default cache; set to dedicated store name recommended 'ttl_seconds' => 0, // reserved; use ->cached($ttl) on the builder instead 'key_prefix' => 'activity-log', // prefix for all timeline cache keys ], ]; ``` -------------------------------- ### Publish Activity Log Configuration Source: https://context7.com/relaticle/activity-log/llms.txt Optionally publish the package's configuration file to customize its behavior. This command makes the configuration file available for modification. ```bash # Optionally publish the config php artisan vendor:publish --tag=activity-log-config ``` -------------------------------- ### Format Code Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/7.community/1.contributing.md Format the project's code according to the defined style using Composer. ```bash composer pint ``` -------------------------------- ### Test Header Action Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/5.testing/1.index.md Verify that clicking the default 'activityLog' header action correctly opens the activity log slide-over. ```php use App\Filament\Resources\People\Pages\ViewPerson; use function Pest\Livewire\livewire; it('opens the activity log slide-over', function (): void { livewire(ViewPerson::class, ['record' => $this->record->getKey()]) ->callAction('activityLog') ->assertSuccessful(); }); ``` -------------------------------- ### Use Activity Factory Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/5.testing/1.index.md Demonstrates how to use the custom Activity factory to create an activity associated with a record. Ensure Laravel's factory resolver is overridden in a service provider. ```php use Database\Factories\Spatie\ActivityFactory; ActivityFactory::new()->for($this->record)->create(); ``` -------------------------------- ### Register and Unregister Renderers via Facade Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/4.customization.md Demonstrates registering a renderer using its class string and then unregistering it. Unregistering is useful for tests or conditional logic to revert to default renderers. ```php use Relaticle\ActivityLog\Facades\Timeline; Timeline::registerRenderer('email_sent', \App\Timeline\Renderers\EmailSentRenderer::class); Timeline::unregisterRenderer('email_sent'); ``` -------------------------------- ### Implementing a Reusable Timeline Source Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/1.sources.md Create a class that implements the `TimelineSource` contract for reusable sources. This allows for constructor dependencies and better testability. Register the instance using `addSource()`. ```php namespace App\Timeline\Sources; use Carbon\CarbonImmutable; use Illuminate\Database\Eloquent\Model; use Relaticle\ActivityLog\Contracts\TimelineSource; use Relaticle\ActivityLog\Timeline\TimelineEntry; use Relaticle\ActivityLog\Timeline\Window; final class StripePaymentSource implements TimelineSource { public function __construct(private readonly int $priority = 30) {} public function priority(): int { return $this->priority; } public function resolve(Model $subject, Window $window): iterable { $query = $subject->stripePayments() ->orderByDesc('created_at') ->limit($window->cap); if ($window->from instanceof CarbonImmutable) { $query->where('created_at', '>=', $window->from); } foreach ($query->cursor() as $payment) { yield new TimelineEntry( id: "stripe:payment:{$payment->id}", type: 'custom', event: "payment.{$payment->status}", occurredAt: CarbonImmutable::parse($payment->created_at), dedupKey: "stripe:payment:{$payment->id}", sourcePriority: $this->priority, subject: $subject, title: "Payment {$payment->amount_formatted}", ); } } } ``` -------------------------------- ### Retrieve Own Spatie Activity Log Entries Source: https://context7.com/relaticle/activity-log/llms.txt Use `fromActivityLog()` to register the `ActivityLogSource` for reading the `activity_log` table where the subject matches. Subject must be persisted. ```php $entries = $person->timeline() ->fromActivityLog() // default priority: 10 (config: source_priorities.activity_log) // ->fromActivityLog(priority: 50) // per-call override ->get(); // Each entry has: // $entry->type => 'activity_log' // $entry->event => 'created' | 'updated' | 'deleted' | 'restored' // $entry->causer => User model or null // $entry->properties => ['attributes' => [...], 'old' => [...]] // $entry->occurredAt => CarbonImmutable ``` -------------------------------- ### Implement Custom Timeline Source with addSource() Source: https://context7.com/relaticle/activity-log/llms.txt Implement the TimelineSource contract to create reusable custom sources. Register them using addSource() to integrate with the activity log timeline. Constructor dependencies can be injected. ```php namespace App\Timeline\Sources; use Carbon\CarbonImmutable; use Illuminate\Database\Eloquent\Model; use Relaticle\ActivityLog\Contracts\TimelineSource; use Relaticle\ActivityLog\Timeline\TimelineEntry; use Relaticle\ActivityLog\Timeline\Window; final class StripePaymentSource implements TimelineSource { public function __construct(private readonly int $priority = 30) {} public function priority(): int { return $this->priority; } public function resolve(Model $subject, Window $window): iterable { $query = $subject->stripePayments() ->orderByDesc('created_at') ->limit($window->cap); if ($window->from instanceof CarbonImmutable) { $query->where('created_at', '>=', $window->from); } foreach ($query->cursor() as $payment) { yield new TimelineEntry( id: "stripe:payment:{$payment->id}", type: 'custom', event: "payment.{$payment->status}", occurredAt: CarbonImmutable::parse($payment->created_at), dedupKey: "stripe:payment:{$payment->id}", sourcePriority: $this->priority, subject: $subject, title: "Payment {$payment->amount_formatted}", ); } } } // Registration $person->timeline() ->fromActivityLog() ->addSource(new StripePaymentSource(priority: 50)); ``` -------------------------------- ### fromActivityLog() Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/1.sources.md Registers the ActivityLogSource to include entries from the subject's own spatie activity log. Reads from the 'activity_log' table where subject_type and subject_id match the subject. Each row becomes a TimelineEntry with type='activity_log'. The subject must be persisted. An optional priority can be set. ```APIDOC ## `fromActivityLog()` Registers `Relaticle ActivityLog Timeline Sources ActivityLogSource` — the subject's own spatie activity log. ```php public function fromActivityLog(?int $priority = null): self ``` ```php $entries = $record->timeline() ->fromActivityLog() ->get(); ``` Reads from the `activity_log` table where `subject_type` and `subject_id` match the subject. Each row becomes a `TimelineEntry` with `type='activity_log'`. ::callout{icon="i-lucide-alert-triangle" color="warning"} **Subject must be persisted.** `ActivityLogSource::resolve()` throws a `DomainException` when `$subject->getKey() === null`. Don't call `timeline()` from a `creating` model event or on a fresh, unsaved instance. :: `$priority` overrides the default of `10` (see `source_priorities.activity_log`). ``` -------------------------------- ### Registering a Custom Event Source Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/1.sources.md Use `fromCustom` to register a closure that yields `TimelineEntry` instances directly. Ensure the closure respects the `Window`'s cap, from, and to parameters to manage memory and filtering. Yielding types other than `TimelineEntry` will result in a `TypeError`. ```php use Carbon\CarbonImmutable; use Relaticle\ActivityLog\Timeline\TimelineEntry; use Relaticle\ActivityLog\Timeline\Window; $record->timeline() ->fromCustom(function ($subject, Window $window) { $query = $subject->stripeCharges() ->orderByDesc('created_at') ->limit($window->cap); if ($window->from instanceof CarbonImmutable) { $query->where('created_at', '>=', $window->from); } if ($window->to instanceof CarbonImmutable) { $query->where('created_at', '<=', $window->to); } foreach ($query->cursor() as $charge) { yield new TimelineEntry( id: "stripe:charge:{$charge->id}", type: 'custom', event: 'charge.succeeded', occurredAt: CarbonImmutable::parse($charge->created_at), dedupKey: "stripe:charge:{$charge->id}", sourcePriority: 30, subject: $subject, title: "Charge {$charge->amount_formatted}", ); } }); ``` -------------------------------- ### Display Activity Log in Person View Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/4.recipes/1.crm-person-feed.md Integrate the `ActivityLog` infolist component into the Person view page to display the activity feed. Configure options like grouping by date and items per page. ```php use Relaticle\ActivityLog\Filament\Infolists\Components\ActivityLog; public function infolist(Schema $schema): Schema { return $schema->components([ // ... your existing entries ActivityLog::make('feed') ->heading('Activity') ->groupByDate() ->perPage(20) ->columnSpanFull(), ]); } ``` -------------------------------- ### Adding a Reusable Timeline Source Instance Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/1.sources.md Register an instance of a custom `TimelineSource` implementation with the activity log. This is preferred over `fromCustom` for reusable or complex sources. ```php $record->timeline() ->fromActivityLog() ->addSource(new StripePaymentSource()); ``` -------------------------------- ### Activity Log URL Filter Parameter Mapping Source: https://context7.com/relaticle/activity-log/llms.txt Explains the mapping between URL parameters (`type`, `from`, `to`) and their corresponding builder method calls in the Activity Log package. ```php // The three URL parameters map to builder calls internally: // ?type=activity_log → $builder->ofType(['activity_log']) // ?from=2026-01-01 → $builder->between(CarbonImmutable::parse('2026-01-01'), ...) // ?to=2026-04-30 → $builder->between(..., CarbonImmutable::parse('2026-04-30')) // Valid ?type values: 'activity_log', 'related_model', 'custom' // 'related_activity_log' matches nothing — see type taxonomy in docs ``` -------------------------------- ### Registering Custom Events with `fromCustom` Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/1.sources.md Use the `fromCustom` method to register a closure that yields `TimelineEntry` instances directly. This is useful for ad-hoc or model-specific event generation. The closure receives the subject and a `Window` object to manage data fetching. ```APIDOC ## `fromCustom(Closure $resolver, ?int $priority = null)` Registers `Relaticle\ActivityLog\Timeline\Sources\CustomEventSource` — yields `Relaticle\ActivityLog\Timeline\TimelineEntry` instances directly, with no assumptions about where they come from. ### Parameters #### Path Parameters - **resolver** (Closure) - Required - A closure that resolves timeline entries. - **priority** (int) - Optional - The priority of this source. ### Request Example ```php use Carbon\CarbonImmutable; use Relaticle\ActivityLog\Timeline\TimelineEntry; use Relaticle\ActivityLog\Timeline\Window; $record->timeline() ->fromCustom(function ($subject, Window $window) { $query = $subject->stripeCharges() ->orderByDesc('created_at') ->limit($window->cap); if ($window->from instanceof CarbonImmutable) { $query->where('created_at', '>=', $window->from); } if ($window->to instanceof CarbonImmutable) { $query->where('created_at', '<=', $window->to); } foreach ($query->cursor() as $charge) { yield new TimelineEntry( id: "stripe:charge:{$charge->id}", type: 'custom', event: 'charge.succeeded', occurredAt: CarbonImmutable::parse($charge->created_at), dedupKey: "stripe:charge:{$charge->id}", sourcePriority: 30, subject: $subject, title: "Charge {$charge->amount_formatted}", ); } }); ``` ### Notes - The closure receives the subject and the active `Window` — respect `cap`, `from`, and `to` to keep memory bounded and honor `->between(...)` filters. - Yielding anything other than a `TimelineEntry` instance throws `TypeError`. ``` -------------------------------- ### Embed Activity Log in Filament Infolist Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/2.filament-ui.md Use the `ActivityLog` infolist component to display the timeline within an existing Filament infolist. Configure options like grouping by date, items per page, and infinite scrolling. ```php use Relaticle\ActivityLog\Filament\Infolists\Components\ActivityLog; ActivityLog::make('timeline') ->groupByDate() ->perPage(10) ->emptyState('Nothing here yet — give it time.') ->infiniteScroll() ->columnSpanFull(), ``` -------------------------------- ### Make Model Timeline-Capable Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/1.getting-started/2.quick-start.md Integrates a model with the activity log and timeline functionality. Ensures Spatie's LogsActivity records changes and the model can generate a timeline from its activity log. ```php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Relaticle\ActivityLog\Concerns\InteractsWithTimeline; use Relaticle\ActivityLog\Contracts\HasTimeline; use Relaticle\ActivityLog\Timeline\TimelineBuilder; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\LogsActivity; final class Person extends Model implements HasTimeline { use InteractsWithTimeline; use LogsActivity; protected $fillable = ['name', 'email']; public function getActivitylogOptions(): LogOptions { return LogOptions::defaults()->logFillable(); } public function timeline(): TimelineBuilder { return TimelineBuilder::make($this)->fromActivityLog(); } } ``` -------------------------------- ### Test Infolist Entry Rendering Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/5.testing/1.index.md Assert that an activity log entry is rendered on the person view page after a record update. This test targets the default diff sentence output. ```php use App\Filament\Resources\People\Pages\ViewPerson; use function Pest\Livewire\livewire; it('renders an entry on the person view page after an update', function (): void { $this->record->update(['name' => 'Updated name']); livewire(ViewPerson::class, ['record' => $this->record->getKey()]) ->assertSeeHtml('Updated name'); }); ``` -------------------------------- ### Trigger Activity via Tinker Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/1.getting-started/2.quick-start.md Demonstrates how to trigger an activity log entry by updating a model instance using Artisan Tinker. This is useful for testing the timeline functionality. ```bash php artisan tinker --execute 'App\Models\Person::find(1)->update(["name" => "New name"]);' ``` -------------------------------- ### Add Activity Log Action to Filament Header Source: https://context7.com/relaticle/activity-log/llms.txt Use `ActivityLogAction` to open the timeline slide-over from any page header. Standard Filament action overrides apply. ```php use Relaticle\ActivityLog\Filament\Actions\ActivityLogAction; protected function getHeaderActions(): array { return [ ActivityLogAction::make(), // Standard Filament action overrides work normally // ActivityLogAction::make()->label('History')->color('primary'), ]; } // In tests $this->callAction('activityLog'); ``` -------------------------------- ### Configure Default Renderers in Config File Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/4.customization.md Set static default renderers in the `config/activity-log.php` file. This is suitable for application-wide defaults that do not depend on runtime state. ```php // config/activity-log.php 'renderers' => [ 'email_sent' => \App\Timeline\Renderers\EmailSentRenderer::class, ], ``` -------------------------------- ### Assert Timeline Entries Directly Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/5.testing/1.index.md Test the activity log builder by creating an activity entry and asserting the properties of the generated timeline entries. ```php use Spatie\Activitylog\Models\Activity; it('emits an entry per activity row', function (): void { Activity::create([ 'log_name' => 'default', 'description' => 'updated', 'event' => 'updated', 'subject_type' => $this->record->getMorphClass(), 'subject_id' => $this->record->getKey(), 'properties' => ['attributes' => ['name' => 'New'], 'old' => ['name' => 'Old']], ]); $entries = $this->record->timeline()->fromActivityLog()->get(); expect($entries) ->toHaveCount(1) ->and($entries->first()->event)->toBe('updated'); }); ``` -------------------------------- ### Add Activity Log Header Action Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/2.filament-ui.md Register the `ActivityLogAction` in your resource's `getHeaderActions()` method to enable a slide-over panel displaying the activity log from any page header. ```php use Relaticle\ActivityLog\Filament\Actions\ActivityLogAction; protected function getHeaderActions(): array { return [ ActivityLogAction::make(), ]; } ``` -------------------------------- ### Add Activity Log Infolist to Person Resource Source: https://context7.com/relaticle/activity-log/llms.txt Integrates the ActivityLog component into the 'view' page of the Person resource using Filament's infolist schema. This displays the timeline feed. ```php public function infolist(Schema $schema): Schema { return $schema->components([ ActivityLog::make('feed') ->heading('Activity') ->groupByDate() ->perPage(20) ->columnSpanFull(), ]); } ``` -------------------------------- ### Register Custom Renderers via Facade Source: https://context7.com/relaticle/activity-log/llms.txt Register or unregister timeline renderers globally at runtime using the `Timeline` facade. Useful for dynamic registration or in tests. ```php use Relaticle\ActivityLog\Facades\Timeline; // In AppServiceProvider::boot() Timeline::registerRenderer('email_sent', \App\Timeline\Renderers\EmailSentRenderer::class); // Unregister (useful in tests or when a feature flag is off) Timeline::unregisterRenderer('email_sent'); ``` -------------------------------- ### Register Custom Timeline Events from a Closure Source: https://context7.com/relaticle/activity-log/llms.txt Use `fromCustom()` to register timeline entries generated by a closure. The closure receives the subject and a `Window` object. It must yield `TimelineEntry` instances and respect the `$window->cap`, `$window->from`, and `$window->to` parameters for efficient data retrieval. A `TypeError` is thrown if the closure yields anything other than `TimelineEntry` instances. ```php use Carbon\CarbonImmutable; use Relaticle\ActivityLog\Timeline\TimelineEntry; use Relaticle\ActivityLog\Timeline\Window; $person->timeline() ->fromCustom(function ($subject, Window $window) { $query = $subject->stripeCharges() ->orderByDesc('created_at') ->limit($window->cap); if ($window->from instanceof CarbonImmutable) { $query->where('created_at', '>=', $window->from); } if ($window->to instanceof CarbonImmutable) { $query->where('created_at', '<=', $window->to); } foreach ($query->cursor() as $charge) { yield new TimelineEntry( id: "stripe:charge:{$charge->id}", type: 'custom', event: 'charge.succeeded', occurredAt: CarbonImmutable::parse($charge->created_at), dedupKey: "stripe:charge:{$charge->id}", sourcePriority: 30, subject: $subject, title: "Charge {$charge->amount_formatted}", icon: 'heroicon-o-credit-card', color: 'success', ); } }) ->get(); // TypeError is thrown if the closure yields anything other than TimelineEntry ``` -------------------------------- ### Build and Paginate Timeline Entries with TimelineBuilder Source: https://context7.com/relaticle/activity-log/llms.txt Use TimelineBuilder to chain sources, filters, and options to retrieve paginated activity log entries. Ensure the subject is persisted when using `fromActivityLog()`. ```php use Relaticle\ActivityLog\Timeline\TimelineBuilder; use Relaticle\ActivityLog\Timeline\Sources\RelatedModelSource; use Carbon\CarbonImmutable; $entries = TimelineBuilder::make($person) // Sources ->fromActivityLog() // own spatie log, priority 10 ->fromActivityLogOf(['notes', 'tasks']) // related models' spatie logs, priority 10 ->fromRelation('invoices', fn (RelatedModelSource $s) => $s ->event('issued_at', 'invoice.issued', icon: 'heroicon-o-document-text', color: 'info') ->event('paid_at', 'invoice.paid', icon: 'heroicon-o-check-circle', color: 'success') ) ->fromCustom(fn ($subject, $window) => []) // custom closure source, priority 30 // Filtering ->between(now()->subMonth(), now()) // date window (either side nullable) ->ofType(['activity_log']) ->exceptType(['custom']) ->ofEvent(['created', 'updated']) ->exceptEvent(['draft_saved']) // Sorting ->sortByDateDesc() // default; newest first // ->sortByDateAsc() // oldest first // Deduplication ->deduplicate(true) // default: true ->dedupKeyUsing(fn ($entry) => "{$entry->type}:{$entry->event}:{$entry->occurredAt->toDateString()}") // Caching (optional, per-call; call before paginate()) ->cached(ttlSeconds: 300) // Execution ->paginate(perPage: 25, page: 1); // LengthAwarePaginator // ->get(); // Collection (cap 10000) // ->count(); // int (runs get() internally) ``` -------------------------------- ### Adding Reusable Timeline Sources with `addSource` Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/1.sources.md Implement the `TimelineSource` contract in a separate class and register it using `addSource`. This is preferred for reusable sources, sources with constructor dependencies, or those needing isolated testing. ```APIDOC ## `addSource(TimelineSource $source)` Registers a reusable source class that implements the `Relaticle\ActivityLog\Contracts\TimelineSource` contract. ### Parameters #### Path Parameters - **source** (TimelineSource) - Required - An instance of a class implementing the `TimelineSource` contract. ### Request Example ```php namespace App\Timeline\Sources; use Carbon\CarbonImmutable; use Illuminate\Database\Eloquent\Model; use Relaticle\ActivityLog\Contracts\TimelineSource; use Relaticle\ActivityLog\Timeline\TimelineEntry; use Relaticle\ActivityLog\Timeline\Window; final class StripePaymentSource implements TimelineSource { public function __construct(private readonly int $priority = 30) {} public function priority(): int { return $this->priority; } public function resolve(Model $subject, Window $window): iterable { $query = $subject->stripePayments() ->orderByDesc('created_at') ->limit($window->cap); if ($window->from instanceof CarbonImmutable) { $query->where('created_at', '>=', $window->from); } foreach ($query->cursor() as $payment) { yield new TimelineEntry( id: "stripe:payment:{$payment->id}", type: 'custom', event: "payment.{$payment->status}", occurredAt: CarbonImmutable::parse($payment->created_at), dedupKey: "stripe:payment:{$payment->id}", sourcePriority: $this->priority, subject: $subject, title: "Payment {$payment->amount_formatted}", ); } } } ``` ```php $record->timeline() ->fromActivityLog() ->addSource(new StripePaymentSource()); ``` ``` -------------------------------- ### User Model with Focused Timeline Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/4.recipes/2.audit-log-for-admins.md Configure the User model to implement `HasTimeline` and use `LogsActivity`. Specify `logFillable()` and `logOnlyDirty()` for focused logging and filter events to CRUD operations using `ofEvent([...])` for a clean audit stream. ```php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Relaticle\ActivityLog\Concerns\InteractsWithTimeline; use Relaticle\ActivityLog\Contracts\HasTimeline; use Relaticle\ActivityLog\Timeline\TimelineBuilder; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\LogsActivity; final class User extends Authenticatable implements HasTimeline { use InteractsWithTimeline; use LogsActivity; protected $fillable = ['name', 'email', 'role', 'password']; protected $hidden = ['password', 'remember_token']; public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() ->logFillable() ->logOnlyDirty() ->dontSubmitEmptyLogs(); } public function isAdmin(): bool { return $this->role === 'admin'; } public function timeline(): TimelineBuilder { return TimelineBuilder::make($this) ->fromActivityLog() ->ofEvent(['created', 'updated', 'deleted', 'restored']); } } ``` -------------------------------- ### Register Filament Panel Plugin Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/1.getting-started/1.installation.md This PHP code shows how to register the activity log panel plugin within your Filament panel configuration. This is necessary for custom renderers and auto-discovery covers other aspects. ```php use Relaticle\ActivityLog\Filament\ActivityLogPlugin; public function panel(Panel $panel): Panel { return $panel ->plugin(ActivityLogPlugin::make()) // ... rest of panel config ; } ``` -------------------------------- ### Paginate Timeline with Trait Helper Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/3.refining-the-timeline.md Use the `paginateTimeline()` helper method from the `InteractsWithTimeline` trait for convenient pagination. This method is a pass-through to the builder's `paginate()` method and is used internally by UI components. ```php $page = $opportunity->paginateTimeline(perPage: 20, page: 1); ``` -------------------------------- ### Add Index to spatie activity_log Table Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/1.getting-started/1.installation.md This PHP code snippet demonstrates how to add a required compound index to the `activity_log` table for optimal performance. This is crucial for responsive timeline queries. ```php Schema::table('activity_log', function (Blueprint $table) { $table->index(['subject_type', 'subject_id', 'created_at']); }); ``` -------------------------------- ### Admin-Gated Header Action for Audit Log Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/4.recipes/2.audit-log-for-admins.md Add the `ActivityLogAction` to the Filament User view page. Use `->visible()` with an authorization check to ensure only admins can see the 'Audit log' action. ```php namespace App\Filament\Resources\Users\Pages; use App\Filament\Resources\Users\UserResource; use Filament\Resources\Pages\ViewRecord; use Relaticle\ActivityLog\Filament\Actions\ActivityLogAction; final class ViewUser extends ViewRecord { protected static string $resource = UserResource::class; protected function getHeaderActions(): array { return [ ActivityLogAction::make() ->label('Audit log') ->icon('heroicon-o-shield-check') ->visible(fn (): bool => auth()->user()?->isAdmin() ?? false), ]; } } ``` -------------------------------- ### Timeline Pipeline Flow Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/2.concepts/1.how-it-works.md Illustrates the data flow from a record to a rendered timeline view, showing the stages of resolution, filtering, deduplication, sorting, and pagination. ```text $record->timeline() [TimelineBuilder] ↓ resolves TimelineSource(s) per source [resolve(subject, Window)] ↓ yields TimelineEntry stream [filter → dedup → sort] ↓ paginated LengthAwarePaginator → Renderer → Blade view ``` -------------------------------- ### Render Activity Log in Filament Infolist Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/1.getting-started/2.quick-start.md Adds the ActivityLog component to a Filament resource's infolist to display timeline entries. This component must be used on a page that renders a model record. ```php namespace App\Filament\Resources;\n\nuse Filament\Schemas\Schema;\nuse Relaticle\ActivityLog\Filament\Infolists\Components\ActivityLog;\n\npublic function infolist(Schema $schema): Schema\n{\n return $schema->components([\n // ... your existing entries\n ActivityLog::make('activity')->columnSpanFull(),\n ]);\n} ``` -------------------------------- ### Test Relation Manager Mounting Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/5.testing/1.index.md Mount the Activity Log relation manager for a specific record, ensuring it attaches correctly to the host resource page. ```php use App\Filament\Resources\People\Pages\EditPerson; use Relaticle\ActivityLog\Filament\RelationManagers\ActivityLogRelationManager; use function Pest\Livewire\livewire; it('mounts the activity log relation manager for a record', function (): void { livewire(ActivityLogRelationManager::class, [ 'ownerRecord' => $this->record, 'pageClass' => EditPerson::class, ])->assertSuccessful(); }); ``` -------------------------------- ### Register Renderer with Class String Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/4.customization.md Register a custom renderer using its class string. The renderer will be resolved through the container, allowing for dependency injection in its constructor. ```php use Relaticle\ActivityLog\Facades\Timeline; Timeline::registerRenderer('email_sent', \App\Timeline\Renderers\EmailSentRenderer::class); ``` -------------------------------- ### Register Activity Log Filament Plugin with Custom Renderers Source: https://context7.com/relaticle/activity-log/llms.txt Register the `ActivityLogPlugin` when custom renderers are needed. Specify custom renderers using the `renderers()` method. ```php use Relaticle\ActivityLog\Filament\ActivityLogPlugin; public function panel(Panel $panel): Panel { return $panel ->plugin( ActivityLogPlugin::make()->renderers([ 'email_sent' => \App\Timeline\Renderers\EmailSentRenderer::class, ]) ); } ``` -------------------------------- ### Retrieve Related Models' Spatie Logs Source: https://context7.com/relaticle/activity-log/llms.txt Use `fromActivityLogOf()` to register `RelatedActivityLogSource`, which queries `activity_log` for related model IDs. Related-log entries have a non-null `$entry->relatedModel`. ```php $entries = $opportunity->timeline() ->fromActivityLog() ->fromActivityLogOf(['comments', 'tasks']) // default priority: 10 ->get(); // Filter: related-log entries only (inspect relatedModel, not type) $relatedOnly = $entries->filter(fn ($entry) => $entry->relatedModel !== null); ``` -------------------------------- ### Use ActivityLogSummary in Custom Renderer Source: https://context7.com/relaticle/activity-log/llms.txt Utilize `ActivityLogSummary` within a custom renderer to parse `TimelineEntry` data from spatie activity-log, providing structured access to changes and operations. ```php use Relaticle\ActivityLog\Contracts\TimelineRenderer; use Relaticle\ActivityLog\Support\ActivityLogSummary; use Relaticle\ActivityLog\Support\ActivityLogOperation; use Relaticle\ActivityLog\Timeline\TimelineEntry; use Illuminate\Contracts\View\View; final class AuditedActivityLogRenderer implements TimelineRenderer { public function render(TimelineEntry $entry): View { $summary = ActivityLogSummary::from($entry); // Available on $summary: // ->causerName string "Alice" or "System" // ->operation ?ActivityLogOperation Created|Updated|Deleted|Restored or null // ->changedFieldLabels list ["Name", "Email"] // ->summarySentence string "Alice changed Name" // ->diffRows list // ->hasDiff bool true only for Updated with rows foreach ($summary->diffRows as $row) { // $row->label "Email Address" // $row->formattedOld() "old@example.com" // $row->formattedNew() "new@example.com" } return view('audit.timeline.entry', [ 'entry' => $entry, 'summary' => $summary, ]); } } // ActivityLogOperation enum $op = ActivityLogOperation::Updated; $op->verb(); // "updated" $op->icon(); // "ri-edit-line" ``` -------------------------------- ### Register Renderer with Closure Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/4.customization.md Register a custom renderer using a closure for simple, inline markup or quick prototyping. The closure receives the TimelineEntry object and should return Blade markup. ```php use Illuminate\Support\HtmlString; use Relaticle\ActivityLog\Facades\Timeline; use Relaticle\ActivityLog\Timeline\TimelineEntry; Timeline::registerRenderer('task_done', fn (TimelineEntry $entry) => new HtmlString( "{$entry->title} finished", )); ``` -------------------------------- ### Render Activity Log in Filament Infolist Source: https://github.com/relaticle/activity-log/blob/1.x/README.md Add the ActivityLog component to your Filament resource's infolist schema to display the activity feed. This component is designed to span the full width of the column. ```php use Filament\Schemas\Schema; use Relaticle\ActivityLog\Filament\Infolists\Components\ActivityLog; public static function infolist(Schema $schema): Schema { return $schema->components([ ActivityLog::make('activity')->columnSpanFull(), ]); } ``` -------------------------------- ### Configure Person Model for Activity Timeline Source: https://context7.com/relaticle/activity-log/llms.txt Defines the Person model to include its own activity log and integrate logs from related models like emails, notes, and tasks. Customizes timeline events for specific relations. ```php final class Person extends Model implements HasTimeline { use InteractsWithTimeline; use LogsActivity; protected $fillable = ['name', 'email', 'company']; public function getActivitylogOptions(): LogOptions { return LogOptions::defaults()->logFillable(); } public function emails(): HasMany { return $this->hasMany(Email::class); } public function notes(): HasMany { return $this->hasMany(Note::class); } public function tasks(): HasMany { return $this->hasMany(Task::class); } public function timeline(): TimelineBuilder { return TimelineBuilder::make($this) ->fromActivityLog() ->fromActivityLogOf(['emails', 'notes', 'tasks']) ->fromRelation('emails', function (RelatedModelSource $source): void { $source ->event('sent_at', 'email_sent', icon: 'heroicon-o-paper-airplane', color: 'primary') ->title(fn (Email $e): string => $e->subject ?? 'Email') ->description(fn (Email $e): string => "to {$e->recipient}") ->causer(fn (Email $e) => $e->sender); }) ->fromRelation('tasks', function (RelatedModelSource $source): void { $source ->event('completed_at', 'task_completed', icon: 'heroicon-o-check-circle', color: 'success') ->title(fn (Task $t): string => $t->title); }); } } ``` -------------------------------- ### Define Activity Factory Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/5.testing/1.index.md Defines a custom factory for the Spatie Activity model. This factory includes a `for` method to associate activities with a specific subject model. ```php namespace Database\Factories\Spatie; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Model; use Spatie\Activitylog\Models\Activity; /** @extends Factory */ final class ActivityFactory extends Factory { protected $model = Activity::class; public function definition(): array { return [ 'log_name' => 'default', 'description' => $this->faker->word(), 'event' => $this->faker->randomElement(['created', 'updated', 'deleted']), 'subject_type' => null, 'subject_id' => null, 'causer_type' => null, 'causer_id' => null, 'properties' => [], ]; } public function for(Model $subject): static { return $this->state([ 'subject_type' => $subject->getMorphClass(), 'subject_id' => $subject->getKey(), ]); } } ``` -------------------------------- ### Composing Timeline Sources in Person Model Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/4.recipes/1.crm-person-feed.md Defines the `timeline()` method on the `Person` model to aggregate activity from multiple sources. It includes spatie logs from the person and related models, as well as custom events derived from timestamp columns like `sent_at` and `completed_at`. This method configures the `TimelineBuilder` to pull data from the person's own activity log, related emails, notes, and tasks, and defines custom event details for email sending and task completion. ```php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Relaticle\ActivityLog\Concerns\InteractsWithTimeline; use Relaticle\ActivityLog\Contracts\HasTimeline; use Relaticle\ActivityLog\Timeline\Sources\RelatedModelSource; use Relaticle\ActivityLog\Timeline\TimelineBuilder; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\LogsActivity; final class Person extends Model implements HasTimeline { use InteractsWithTimeline; use LogsActivity; protected $fillable = ['name', 'email', 'company']; public function getActivitylogOptions(): LogOptions { return LogOptions::defaults()->logFillable(); } public function emails(): HasMany { return $this->hasMany(Email::class); } public function notes(): HasMany { return $this->hasMany(Note::class); } public function tasks(): HasMany { return $this->hasMany(Task::class); } public function timeline(): TimelineBuilder { return TimelineBuilder::make($this) ->fromActivityLog() ->fromActivityLogOf(['emails', 'notes', 'tasks']) ->fromRelation('emails', function (RelatedModelSource $source): void { $source ->event(column: 'sent_at', event: 'email_sent', icon: 'heroicon-o-paper-airplane', color: 'primary') ->title(fn (Email $email): string => $email->subject ?? 'Email') ->description(fn (Email $email): ?string => "to {$email->recipient}") ->causer(fn (Email $email) => $email->sender); }) ->fromRelation('tasks', function (RelatedModelSource $source): void { $source ->event(column: 'completed_at', event: 'task_completed', icon: 'heroicon-o-check-circle', color: 'success') ->title(fn (Task $task): string => $task->title); }); } } ``` -------------------------------- ### Register Activity Log Relation Manager Source: https://github.com/relaticle/activity-log/blob/1.x/docs/content/3.essentials/2.filament-ui.md Add the `ActivityLogRelationManager` to a resource to provide a dedicated 'Activity' tab. This manager automatically displays the activity log for each record. ```php use Relaticle\ActivityLog\Filament\RelationManagers\ActivityLogRelationManager; public static function getRelations(): array { return [ ActivityLogRelationManager::class, ]; } ```