Skip to content

Commit 4e6d16c

Browse files
Implement Component Manager (#101)
Co-authored-by: James Brooks <[email protected]>
1 parent a1ad31e commit 4e6d16c

File tree

6 files changed

+251
-7
lines changed

6 files changed

+251
-7
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"orchestra/testbench": "^9.5.1",
4040
"pestphp/pest": "^3.2",
4141
"pestphp/pest-plugin-laravel": "^3.0",
42+
"pestphp/pest-plugin-livewire": "*",
4243
"spatie/laravel-ray": "^1.32"
4344
},
4445
"autoload": {

database/factories/ComponentFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function definition(): array
2525
'description' => fake()->paragraph,
2626
'status' => ComponentStatusEnum::performance_issues->value,
2727
'order' => 0,
28-
'component_group_id' => 0,
28+
'component_group_id' => null,
2929
'enabled' => true,
3030
];
3131
}
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<x-filament-widgets::widget>
2-
<x-filament::section>
3-
<div class="flex flex-col items-center justify-center">
4-
<h1 class="text-lg font-semibold">Component Manager</h1>
5-
</div>
6-
</x-filament::section>
2+
<form class="w-full">
3+
{{ $this->form }}
4+
</form>
75
</x-filament-widgets::widget>

src/Filament/Widgets/Components.php

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,91 @@
22

33
namespace Cachet\Filament\Widgets;
44

5+
use Cachet\Enums\ComponentStatusEnum;
6+
use Cachet\Models\Component;
7+
use Cachet\Models\ComponentGroup;
8+
use Filament\Forms\Components\Component as FilamentFormComponent;
9+
use Filament\Forms\Components\Group;
10+
use Filament\Forms\Components\ToggleButtons;
11+
use Filament\Forms\Components\Section;
12+
use Filament\Forms\Concerns\InteractsWithForms;
13+
use Filament\Forms\Contracts\HasForms;
14+
use Filament\Forms\Form;
515
use Filament\Widgets\Widget;
16+
use Illuminate\Support\Collection;
617

7-
class Components extends Widget
18+
class Components extends Widget implements HasForms
819
{
20+
use InteractsWithForms;
21+
922
protected static string $view = 'cachet::filament.widgets.components';
1023

1124
protected int|string|array $columnSpan = 'full';
25+
26+
public Collection $formData;
27+
28+
public Collection $components;
29+
30+
public function mount(): void
31+
{
32+
$this->components = $components = Component::query()
33+
->select(['id', 'component_group_id', 'name', 'status', 'enabled'])
34+
->enabled()
35+
->get();
36+
37+
$this->formData = $components->mapWithKeys(function (Component $component) {
38+
return [$component->id => ['status' => $component->status]];
39+
});
40+
}
41+
42+
public function form(Form $form): Form
43+
{
44+
$componentGroupSchema = $this->loadVisibleComponentGroups()
45+
->filter(fn (ComponentGroup $componentGroup) => $this->components->pluck('component_group_id')->contains($componentGroup->id))
46+
->map(function (ComponentGroup $componentGroup): FilamentFormComponent {
47+
return Section::make($componentGroup->name)
48+
->schema(function () use ($componentGroup) {
49+
return $this->components
50+
->filter(fn (Component $component) => $componentGroup->is($component->group))
51+
->map(fn (Component $component) => Group::make([$this->buildToggleButton($component)]))
52+
->toArray();
53+
})
54+
->collapsed($componentGroup->isCollapsible())
55+
->persistCollapsed();
56+
});
57+
58+
$ungroupedComponentSchema = $this->components
59+
->filter(fn(Component $component) => is_null($component->component_group_id))
60+
->map(function (Component $component): FilamentFormComponent {
61+
return Section::make($component->name)
62+
->schema(fn () => [$this->buildToggleButton($component)])
63+
->collapsible()
64+
->persistCollapsed();
65+
});
66+
67+
$schema = $componentGroupSchema->merge($ungroupedComponentSchema)->toArray();
68+
69+
return $form->schema($schema)->statePath('formData');
70+
}
71+
72+
protected function buildToggleButton(Component $component): ToggleButtons
73+
{
74+
return ToggleButtons::make($component->id . '.status')
75+
->label($component->name)
76+
->hiddenLabel(is_null($component->component_group_id))
77+
->inline()
78+
->live()
79+
->options(ComponentStatusEnum::class)
80+
->afterStateUpdated(function (ComponentStatusEnum $state) use ($component) {
81+
return $component->update(['status' => $state]);
82+
});
83+
}
84+
85+
protected function loadVisibleComponentGroups(): Collection
86+
{
87+
return ComponentGroup::query()
88+
->select(['id', 'name', 'collapsed', 'visible'])
89+
->where('visible', '=', true)
90+
->get();
91+
}
1292
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
namespace Tests\Feature\Filament\Widgets;
4+
5+
use Cachet\Enums\ComponentStatusEnum;
6+
use Cachet\Filament\Widgets\Components;
7+
use Cachet\Models\Component;
8+
use Cachet\Models\ComponentGroup;
9+
use function Pest\Livewire\livewire;
10+
use function PHPUnit\Framework\assertCount;
11+
use function PHPUnit\Framework\assertEquals;
12+
13+
it('component smoke test', function () {
14+
$component = livewire(Components::class);
15+
16+
$component->assertSuccessful();
17+
});
18+
19+
it('will only show visible component groups', function () {
20+
$componentGroup = ComponentGroup::factory()->create([
21+
'name' => 'Test Component Group 1',
22+
'visible' => true,
23+
]);
24+
25+
Component::factory()->create([
26+
'component_group_id' => $componentGroup->id,
27+
'enabled' => true,
28+
]);
29+
30+
ComponentGroup::factory()->create([
31+
'name' => 'Test Component Group 2',
32+
'visible' => false,
33+
]);
34+
35+
$component = livewire(Components::class);
36+
37+
$component->assertSuccessful();
38+
39+
$component->assertSee('Test Component Group 1');
40+
$component->assertDontSee('Test Component Group 2');
41+
$component->assertDontSee('Other Components');
42+
});
43+
44+
it('will only show enabled components', function () {
45+
$componentGroup = ComponentGroup::factory()->create([
46+
'name' => 'Laravel',
47+
'visible' => true,
48+
]);
49+
50+
Component::factory()->create([
51+
'name' => 'Forge',
52+
'component_group_id' => $componentGroup->id,
53+
'enabled' => true,
54+
]);
55+
56+
Component::factory()->create([
57+
'name' => 'Cloud',
58+
'component_group_id' => $componentGroup->id,
59+
'enabled' => false,
60+
]);
61+
62+
$component = livewire(Components::class);
63+
64+
$component->assertSuccessful();
65+
66+
assertCount(1, $component->components);
67+
68+
$component->assertSee('Forge');
69+
$component->assertDontSee('Cloud');
70+
});
71+
72+
it('will not show component groups without components', function () {
73+
ComponentGroup::factory()->create([
74+
'name' => 'Laravel',
75+
'visible' => true,
76+
]);
77+
78+
$component = livewire(Components::class);
79+
80+
$component->assertSuccessful();
81+
82+
$component->assertDontSee('Laravel');
83+
});
84+
85+
it('will show enabled components without group', function () {
86+
Component::factory()->create([
87+
'name' => 'Github',
88+
'enabled' => true,
89+
]);
90+
91+
Component::factory()->create([
92+
'name' => 'Bitbucket',
93+
'enabled' => false,
94+
]);
95+
96+
$component = livewire(Components::class);
97+
98+
$component->assertSuccessful();
99+
100+
$component->assertSee('Github');
101+
$component->assertDontSee('Bitbucket');
102+
});
103+
104+
it('can save status of component within component group to have major outage', function () {
105+
$componentGroup = ComponentGroup::factory()->create([
106+
'name' => 'Laravel',
107+
'visible' => true,
108+
]);
109+
110+
$component = Component::factory()->create([
111+
'name' => 'Forge',
112+
'component_group_id' => $componentGroup->id,
113+
'status' => ComponentStatusEnum::operational,
114+
]);
115+
116+
$livewireComponent = livewire(Components::class);
117+
118+
$livewireComponent->assertSuccessful();
119+
120+
$livewireComponent->set(
121+
'formData.' . $component->id . '.status',
122+
ComponentStatusEnum::major_outage->value
123+
);
124+
125+
assertEquals(ComponentStatusEnum::major_outage, $component->fresh()->status);
126+
});
127+
128+
it('can save status of component outside a component group to have major outage', function () {
129+
$component = Component::factory()->create([
130+
'name' => 'Github',
131+
'status' => ComponentStatusEnum::operational,
132+
'enabled' => true,
133+
]);
134+
135+
$livewireComponent = livewire(Components::class);
136+
137+
$livewireComponent->assertSuccessful();
138+
139+
$livewireComponent->set(
140+
'formData.' . $component->id . '.status',
141+
ComponentStatusEnum::major_outage->value
142+
);
143+
144+
assertEquals(ComponentStatusEnum::major_outage, $component->fresh()->status);
145+
});

tests/TestCase.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22

33
namespace Cachet\Tests;
44

5+
use BladeUI\Heroicons\BladeHeroiconsServiceProvider;
6+
use BladeUI\Icons\BladeIconsServiceProvider;
7+
use Filament\FilamentServiceProvider;
8+
use Filament\Forms\FormsServiceProvider;
9+
use Filament\Support\SupportServiceProvider;
10+
use Filament\Widgets\WidgetsServiceProvider;
511
use Illuminate\Database\Eloquent\Factories\Factory;
612
use Illuminate\Foundation\Testing\RefreshDatabase;
13+
use Livewire\LivewireServiceProvider;
714
use Orchestra\Testbench\Concerns\WithWorkbench;
815
use Orchestra\Testbench\TestCase as Orchestra;
916

@@ -20,6 +27,19 @@ protected function setUp(): void
2027
);
2128
}
2229

30+
protected function getPackageProviders($app)
31+
{
32+
return array_merge(parent::getPackageProviders($app), [
33+
LivewireServiceProvider::class,
34+
FilamentServiceProvider::class,
35+
FormsServiceProvider::class,
36+
SupportServiceProvider::class,
37+
BladeIconsServiceProvider::class,
38+
BladeHeroiconsServiceProvider::class,
39+
WidgetsServiceProvider::class,
40+
]);
41+
}
42+
2343
/**
2444
* @param \Illuminate\Foundation\Application $app
2545
*/

0 commit comments

Comments
 (0)