141 - Add channel deletion to the Channels page
This commit is contained in:
parent
6270c97c29
commit
2b87f1f3a7
3 changed files with 203 additions and 0 deletions
|
|
@ -3,10 +3,14 @@
|
|||
namespace App\Livewire;
|
||||
|
||||
use App\Actions\CreateChannelAction;
|
||||
use App\Enums\LogLevelEnum;
|
||||
use App\Events\ActionPerformed;
|
||||
use App\Models\ArticlePublication;
|
||||
use App\Models\Language;
|
||||
use App\Models\PlatformAccount;
|
||||
use App\Models\PlatformChannel;
|
||||
use App\Models\PlatformInstance;
|
||||
use App\Models\RouteArticle;
|
||||
use App\Services\Platform\CommunityDirectory;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
|
@ -49,6 +53,34 @@ public function toggle(int $channelId): void
|
|||
$channel->save();
|
||||
}
|
||||
|
||||
public function deleteChannel(int $channelId): void
|
||||
{
|
||||
$channel = PlatformChannel::find($channelId);
|
||||
|
||||
if (! $channel instanceof PlatformChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $channel->display_name;
|
||||
|
||||
// Routes, keywords, route articles, publications, account links and synced posts
|
||||
// all cascade at the database level.
|
||||
$channel->delete();
|
||||
|
||||
if ($this->managingChannelId === $channelId) {
|
||||
$this->managingChannelId = null;
|
||||
}
|
||||
|
||||
if ($this->editingChannelId === $channelId) {
|
||||
$this->editingChannelId = null;
|
||||
}
|
||||
|
||||
ActionPerformed::dispatch('Deleted platform channel', LogLevelEnum::WARNING, [
|
||||
'platform_channel_id' => $channelId,
|
||||
'display_name' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
public function openCreateModal(): void
|
||||
{
|
||||
$this->reset(['newCommunityId', 'newPlatformInstanceId', 'newLanguageId', 'newDescription', 'availableCommunities', 'communityLoadError']);
|
||||
|
|
@ -208,6 +240,39 @@ public function detachAccount(int $channelId, int $accountId): void
|
|||
$channel->platformAccounts()->detach($accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Row counts per channel, so the delete confirmation can say what is about to go.
|
||||
*
|
||||
* @return array<int, array{articles: int, publications: int}>
|
||||
*/
|
||||
private function deletionImpact(): array
|
||||
{
|
||||
/** @var array<int, int> $articles */
|
||||
$articles = RouteArticle::query()
|
||||
->selectRaw('platform_channel_id, COUNT(*) as aggregate')
|
||||
->groupBy('platform_channel_id')
|
||||
->pluck('aggregate', 'platform_channel_id')
|
||||
->all();
|
||||
|
||||
/** @var array<int, int> $publications */
|
||||
$publications = ArticlePublication::query()
|
||||
->selectRaw('platform_channel_id, COUNT(*) as aggregate')
|
||||
->groupBy('platform_channel_id')
|
||||
->pluck('aggregate', 'platform_channel_id')
|
||||
->all();
|
||||
|
||||
$impact = [];
|
||||
|
||||
foreach (array_keys($articles + $publications) as $channelId) {
|
||||
$impact[$channelId] = [
|
||||
'articles' => (int) ($articles[$channelId] ?? 0),
|
||||
'publications' => (int) ($publications[$channelId] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
return $impact;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$channels = PlatformChannel::with(['platformInstance', 'platformAccounts'])->orderBy('name')->get();
|
||||
|
|
@ -228,6 +293,7 @@ public function render(): View
|
|||
? PlatformChannel::with('platformInstance')->find($this->editingChannelId)
|
||||
: null,
|
||||
'availableAccounts' => $availableAccounts,
|
||||
'deletionImpact' => $this->deletionImpact(),
|
||||
'platformInstances' => PlatformInstance::where('is_active', true)->orderBy('name')->get(),
|
||||
'languages' => Language::where('is_active', true)->orderBy('name')->get(),
|
||||
])->layout('layouts.app');
|
||||
|
|
|
|||
|
|
@ -56,6 +56,19 @@ class="p-1 rounded-full {{ $channel->is_active ? 'bg-green-100 text-green-600 da
|
|||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@php($impact = $deletionImpact[$channel->id] ?? ['articles' => 0, 'publications' => 0])
|
||||
<button
|
||||
wire:click="deleteChannel({{ $channel->id }})"
|
||||
wire:confirm="Delete "{{ $channel->display_name }}"? This also removes its routes, {{ $impact['articles'] }} article{{ $impact['articles'] === 1 ? '' : 's' }} routed to it and {{ $impact['publications'] }} publication record{{ $impact['publications'] === 1 ? '' : 's' }}. This cannot be undone."
|
||||
class="p-1 rounded-full text-gray-400 hover:bg-red-50 hover:text-red-600 dark:text-gray-500 dark:hover:bg-red-900/20 dark:hover:text-red-400"
|
||||
title="Delete channel"
|
||||
aria-label="Delete {{ $channel->display_name }}"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,14 @@
|
|||
namespace Tests\Feature\Livewire;
|
||||
|
||||
use App\Livewire\Channels;
|
||||
use App\Models\Article;
|
||||
use App\Models\ArticlePublication;
|
||||
use App\Models\Feed;
|
||||
use App\Models\Language;
|
||||
use App\Models\PlatformAccount;
|
||||
use App\Models\PlatformChannel;
|
||||
use App\Models\PlatformInstance;
|
||||
use App\Models\RouteArticle;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Client\Factory;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
|
@ -410,4 +414,124 @@ public function test_update_channel_does_nothing_without_an_open_modal(): void
|
|||
|
||||
$this->assertSame('Original', $channel->fresh()->display_name);
|
||||
}
|
||||
|
||||
public function test_channel_cards_show_a_delete_action(): void
|
||||
{
|
||||
PlatformChannel::factory()->create(['display_name' => 'Tech Community']);
|
||||
|
||||
Livewire::test(Channels::class)
|
||||
->assertSee('Delete Tech Community');
|
||||
}
|
||||
|
||||
public function test_deleting_a_channel_removes_it(): void
|
||||
{
|
||||
$channel = PlatformChannel::factory()->create();
|
||||
$survivor = PlatformChannel::factory()->create();
|
||||
|
||||
Livewire::test(Channels::class)->call('deleteChannel', $channel->id);
|
||||
|
||||
$this->assertDatabaseMissing('platform_channels', ['id' => $channel->id]);
|
||||
$this->assertDatabaseHas('platform_channels', ['id' => $survivor->id]);
|
||||
}
|
||||
|
||||
public function test_deleting_an_unknown_channel_does_nothing(): void
|
||||
{
|
||||
PlatformChannel::factory()->create();
|
||||
|
||||
Livewire::test(Channels::class)
|
||||
->call('deleteChannel', 999999)
|
||||
->assertHasNoErrors();
|
||||
|
||||
$this->assertSame(1, PlatformChannel::count());
|
||||
}
|
||||
|
||||
public function test_deleting_the_channel_being_edited_closes_the_edit_modal(): void
|
||||
{
|
||||
$channel = PlatformChannel::factory()->create();
|
||||
|
||||
Livewire::test(Channels::class)
|
||||
->call('openEditModal', $channel->id)
|
||||
->assertSet('editingChannelId', $channel->id)
|
||||
->call('deleteChannel', $channel->id)
|
||||
->assertSet('editingChannelId', null);
|
||||
}
|
||||
|
||||
public function test_deleting_the_channel_being_managed_closes_the_account_modal(): void
|
||||
{
|
||||
$channel = PlatformChannel::factory()->create();
|
||||
|
||||
Livewire::test(Channels::class)
|
||||
->call('openAccountModal', $channel->id)
|
||||
->assertSet('managingChannelId', $channel->id)
|
||||
->call('deleteChannel', $channel->id)
|
||||
->assertSet('managingChannelId', null);
|
||||
}
|
||||
|
||||
public function test_the_confirmation_states_what_will_be_removed(): void
|
||||
{
|
||||
$feed = Feed::factory()->create();
|
||||
$channel = PlatformChannel::factory()->create(['display_name' => 'Tech Community']);
|
||||
$article = Article::factory()->create(['feed_id' => $feed->id]);
|
||||
|
||||
RouteArticle::factory()->create([
|
||||
'feed_id' => $feed->id,
|
||||
'platform_channel_id' => $channel->id,
|
||||
'article_id' => $article->id,
|
||||
]);
|
||||
|
||||
ArticlePublication::factory()->create([
|
||||
'article_id' => $article->id,
|
||||
'platform_channel_id' => $channel->id,
|
||||
]);
|
||||
|
||||
Livewire::test(Channels::class)
|
||||
->assertSee('1 article routed to it')
|
||||
->assertSee('1 publication record');
|
||||
}
|
||||
|
||||
public function test_each_card_shows_its_own_counts_not_the_totals(): void
|
||||
{
|
||||
$feed = Feed::factory()->create();
|
||||
$busy = PlatformChannel::factory()->create(['name' => 'aaa-busy', 'display_name' => 'Busy Channel']);
|
||||
$quiet = PlatformChannel::factory()->create(['name' => 'zzz-quiet', 'display_name' => 'Quiet Channel']);
|
||||
|
||||
foreach (range(1, 3) as $ignored) {
|
||||
$article = Article::factory()->create(['feed_id' => $feed->id]);
|
||||
RouteArticle::factory()->create([
|
||||
'feed_id' => $feed->id,
|
||||
'platform_channel_id' => $busy->id,
|
||||
'article_id' => $article->id,
|
||||
]);
|
||||
}
|
||||
|
||||
$quietArticle = Article::factory()->create(['feed_id' => $feed->id]);
|
||||
RouteArticle::factory()->create([
|
||||
'feed_id' => $feed->id,
|
||||
'platform_channel_id' => $quiet->id,
|
||||
'article_id' => $quietArticle->id,
|
||||
]);
|
||||
|
||||
Livewire::test(Channels::class)
|
||||
->assertSee('3 articles routed to it')
|
||||
->assertSee('1 article routed to it');
|
||||
}
|
||||
|
||||
public function test_the_confirmation_pluralises_counts(): void
|
||||
{
|
||||
$feed = Feed::factory()->create();
|
||||
$channel = PlatformChannel::factory()->create();
|
||||
|
||||
foreach (range(1, 2) as $ignored) {
|
||||
$article = Article::factory()->create(['feed_id' => $feed->id]);
|
||||
RouteArticle::factory()->create([
|
||||
'feed_id' => $feed->id,
|
||||
'platform_channel_id' => $channel->id,
|
||||
'article_id' => $article->id,
|
||||
]);
|
||||
}
|
||||
|
||||
Livewire::test(Channels::class)
|
||||
->assertSee('2 articles routed to it')
|
||||
->assertSee('0 publication records');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue