Skip to content

Commit f0f427c

Browse files
authored
Add activity feed foundation (#1)
1 parent 3354d69 commit f0f427c

24 files changed

Lines changed: 679 additions & 0 deletions

.github/workflows/tests.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
tests:
9+
runs-on: ubuntu-latest
10+
strategy:
11+
fail-fast: false
12+
matrix:
13+
php: [8.3, 8.4]
14+
laravel: [11.*, 12.*]
15+
include:
16+
- laravel: 11.*
17+
testbench: 9.*
18+
- laravel: 12.*
19+
testbench: 10.*
20+
21+
name: PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }}
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- uses: shivammathur/setup-php@v2
27+
with:
28+
php-version: ${{ matrix.php }}
29+
coverage: none
30+
31+
- uses: ramsey/composer-install@v3
32+
with:
33+
dependency-versions: highest
34+
composer-options: --with="illuminate/contracts:${{ matrix.laravel }}" --with="orchestra/testbench:${{ matrix.testbench }}"
35+
36+
- run: composer test
37+
- run: composer analyse

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/vendor
2+
composer.lock
3+
.phpunit.cache
4+
/.idea

composer.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "tapp/filament-activity",
3+
"description": "Activity feed primitives for Filament apps.",
4+
"type": "library",
5+
"license": "MIT",
6+
"autoload": {
7+
"psr-4": {
8+
"Tapp\\FilamentActivity\\": "src/"
9+
}
10+
},
11+
"autoload-dev": {
12+
"psr-4": {
13+
"Tapp\\FilamentActivity\\Tests\\": "tests/"
14+
}
15+
},
16+
"require": {
17+
"php": ">=8.3",
18+
"illuminate/contracts": "^10.0||^11.0||^12.0",
19+
"filament/filament": "^5.0|^4.0",
20+
"spatie/laravel-package-tools": "^1.0"
21+
},
22+
"require-dev": {
23+
"laravel/pint": "^1.14",
24+
"nunomaduro/collision": "^8.1.1||^7.10.0",
25+
"larastan/larastan": "^2.9||^3.0",
26+
"orchestra/testbench": "^10.0.0||^9.0.0||^8.22.0",
27+
"pestphp/pest": "^2.0||^3.0",
28+
"pestphp/pest-plugin-arch": "^2.5||^3.0",
29+
"pestphp/pest-plugin-laravel": "^2.0||^3.0",
30+
"phpstan/extension-installer": "^1.3",
31+
"phpstan/phpstan-deprecation-rules": "^1.1||^2.0",
32+
"phpstan/phpstan-phpunit": "^1.3||^2.0"
33+
},
34+
"extra": {
35+
"laravel": {
36+
"providers": [
37+
"Tapp\\FilamentActivity\\FilamentActivityServiceProvider"
38+
]
39+
}
40+
},
41+
"minimum-stability": "dev",
42+
"prefer-stable": true,
43+
"scripts": {
44+
"post-autoload-dump": "@composer run prepare",
45+
"prepare": "@php vendor/bin/testbench package:discover --ansi",
46+
"analyse": "vendor/bin/phpstan analyse",
47+
"test": "vendor/bin/pest",
48+
"test-coverage": "vendor/bin/pest --coverage",
49+
"format": "vendor/bin/pint"
50+
},
51+
"config": {
52+
"sort-packages": true,
53+
"allow-plugins": {
54+
"pestphp/pest-plugin": true,
55+
"phpstan/extension-installer": true
56+
}
57+
}
58+
}

config/filament-activity.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
use Tapp\FilamentActivity\Models\Activity;
4+
5+
return [
6+
'model' => Activity::class,
7+
8+
'tenant' => [
9+
'enabled' => false,
10+
'model' => null,
11+
'foreign_key' => 'tenant_id',
12+
],
13+
14+
'user' => [
15+
'model' => config('auth.providers.users.model'),
16+
],
17+
];
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('activities', function (Blueprint $table): void {
12+
$table->id();
13+
$table->nullableMorphs('actor');
14+
$table->morphs('subject');
15+
$table->nullableMorphs('parent');
16+
$table->string('event');
17+
$table->string('title')->nullable();
18+
$table->text('summary')->nullable();
19+
$table->json('metadata')->nullable();
20+
$table->foreignId('tenant_id')->nullable()->index();
21+
$table->timestamp('occurred_at')->index();
22+
$table->timestamps();
23+
24+
$table->index(['tenant_id', 'occurred_at']);
25+
$table->index(['event', 'occurred_at']);
26+
});
27+
}
28+
29+
public function down(): void
30+
{
31+
Schema::dropIfExists('activities');
32+
}
33+
};

phpstan.neon.dist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
parameters:
2+
paths:
3+
- src
4+
- tests
5+
level: 5

phpunit.xml.dist

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
5+
backupGlobals="false"
6+
backupStaticProperties="false"
7+
bootstrap="vendor/autoload.php"
8+
colors="true"
9+
processIsolation="false"
10+
stopOnFailure="false"
11+
>
12+
<testsuites>
13+
<testsuite name="Filament Activity Test Suite">
14+
<directory suffix=".php">./tests</directory>
15+
</testsuite>
16+
</testsuites>
17+
<source>
18+
<include>
19+
<directory suffix=".php">./src</directory>
20+
</include>
21+
</source>
22+
<php>
23+
<env name="APP_ENV" value="testing"/>
24+
<env name="DB_CONNECTION" value="testing"/>
25+
</php>
26+
</phpunit>

pint.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"preset": "laravel"
3+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tapp\FilamentActivity\Filament\Resources;
6+
7+
use BackedEnum;
8+
use Filament\Infolists\Components\TextEntry;
9+
use Filament\Resources\Resource;
10+
use Filament\Schemas\Components\Grid;
11+
use Filament\Schemas\Components\Section;
12+
use Filament\Schemas\Schema;
13+
use Filament\Tables\Columns\TextColumn;
14+
use Filament\Tables\Table;
15+
use Tapp\FilamentActivity\Filament\Resources\ActivityResource\Pages\ListActivities;
16+
use Tapp\FilamentActivity\Filament\Resources\ActivityResource\Pages\ViewActivity;
17+
use Tapp\FilamentActivity\Models\Activity;
18+
19+
class ActivityResource extends Resource
20+
{
21+
protected static ?string $model = Activity::class;
22+
23+
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-bolt';
24+
25+
protected static ?int $navigationSort = 40;
26+
27+
public static function getNavigationLabel(): string
28+
{
29+
return __('Activity');
30+
}
31+
32+
public static function getNavigationGroup(): ?string
33+
{
34+
return __('Community');
35+
}
36+
37+
public static function isScopedToTenant(): bool
38+
{
39+
return config('filament-activity.tenant.enabled', false);
40+
}
41+
42+
public static function getTenantOwnershipRelationshipName(): string
43+
{
44+
return 'tenant';
45+
}
46+
47+
public static function table(Table $table): Table
48+
{
49+
return $table
50+
->defaultSort('occurred_at', 'desc')
51+
->columns([
52+
TextColumn::make('event')
53+
->label(__('Event'))
54+
->badge()
55+
->sortable()
56+
->searchable(),
57+
TextColumn::make('title')
58+
->label(__('Title'))
59+
->searchable()
60+
->limit(60),
61+
TextColumn::make('actor.name')
62+
->label(__('Actor'))
63+
->placeholder(__('System'))
64+
->sortable(),
65+
TextColumn::make('subject_type')
66+
->label(__('Subject'))
67+
->formatStateUsing(fn (?string $state): string => $state ? class_basename($state) : '')
68+
->sortable(),
69+
TextColumn::make('occurred_at')
70+
->label(__('Occurred'))
71+
->since()
72+
->sortable(),
73+
]);
74+
}
75+
76+
public static function infolist(Schema $schema): Schema
77+
{
78+
return $schema
79+
->components([
80+
Grid::make([
81+
'default' => 1,
82+
'md' => 3,
83+
])
84+
->columnSpanFull()
85+
->schema([
86+
Section::make(__('Activity'))
87+
->columnSpan(2)
88+
->schema([
89+
TextEntry::make('event')->label(__('Event'))->badge(),
90+
TextEntry::make('title')->label(__('Title'))->placeholder('-'),
91+
TextEntry::make('summary')->label(__('Summary'))->placeholder('-')->columnSpanFull(),
92+
TextEntry::make('metadata')
93+
->label(__('Metadata'))
94+
->formatStateUsing(fn (?array $state): string => $state ? json_encode($state, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) : '-')
95+
->columnSpanFull(),
96+
]),
97+
Section::make(__('Context'))
98+
->columnSpan(1)
99+
->schema([
100+
TextEntry::make('actor.name')->label(__('Actor'))->placeholder(__('System')),
101+
TextEntry::make('subject_type')->label(__('Subject'))->formatStateUsing(fn (?string $state): string => $state ? class_basename($state) : '-'),
102+
TextEntry::make('parent_type')->label(__('Parent'))->formatStateUsing(fn (?string $state): string => $state ? class_basename($state) : '-'),
103+
TextEntry::make('occurred_at')->label(__('Occurred'))->dateTime(),
104+
]),
105+
]),
106+
]);
107+
}
108+
109+
public static function getPages(): array
110+
{
111+
return [
112+
'index' => ListActivities::route('/'),
113+
'view' => ViewActivity::route('/{record}'),
114+
];
115+
}
116+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tapp\FilamentActivity\Filament\Resources\ActivityResource\Pages;
6+
7+
use Filament\Resources\Pages\ListRecords;
8+
use Tapp\FilamentActivity\Filament\Resources\ActivityResource;
9+
10+
class ListActivities extends ListRecords
11+
{
12+
protected static string $resource = ActivityResource::class;
13+
}

0 commit comments

Comments
 (0)