Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/Filament/Resources/PluginResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,18 @@ public static function form(Schema $schema): Schema
])
->visible(fn (?Plugin $record) => $record?->review_checks !== null),

Schemas\Components\Section::make('Developer Submission Details')
->schema([
Forms\Components\Placeholder::make('support_channel_display')
->label('Support Channel')
->content(fn (?Plugin $record) => $record?->support_channel ?? 'Not provided'),

Forms\Components\Placeholder::make('notes_display')
->label('Notes')
->content(fn (?Plugin $record) => $record?->notes ?? 'Not provided'),
])
->visible(fn (?Plugin $record) => $record !== null),

Schemas\Components\Section::make('Submission Info')
->schema([
Forms\Components\Select::make('user_id')
Expand Down
8 changes: 8 additions & 0 deletions app/Livewire/Customer/Plugins/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class Create extends Component

public string $repository = '';

public string $notes = '';

public string $supportChannel = '';

/** @var array<int, array{id: int, full_name: string, private: bool}> */
public array $repositories = [];

Expand Down Expand Up @@ -90,6 +94,8 @@ function ($attribute, $value, $fail): void {
},
],
'pluginType' => ['required', 'string', 'in:free,paid'],
'notes' => ['nullable', 'string', 'max:5000'],
'supportChannel' => ['nullable', 'string', 'max:255'],
], [
'repository.required' => 'Please select a repository for your plugin.',
'repository.regex' => 'Please enter a valid repository in the format vendor/repo-name.',
Expand All @@ -115,6 +121,8 @@ function ($attribute, $value, $fail): void {
'type' => $this->pluginType,
'status' => PluginStatus::Pending,
'developer_account_id' => $developerAccountId,
'notes' => $this->notes ?: null,
'support_channel' => $this->supportChannel ?: null,
]);

$webhookSecret = $plugin->generateWebhookSecret();
Expand Down
14 changes: 14 additions & 0 deletions database/factories/PluginFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,18 @@ public function withoutDescription(): static
'description' => null,
]);
}

public function withNotes(?string $notes = null): static
{
return $this->state(fn (array $attributes) => [
'notes' => $notes ?? fake()->sentence(),
]);
}

public function withSupportChannel(?string $channel = null): static
{
return $this->state(fn (array $attributes) => [
'support_channel' => $channel ?? fake()->safeEmail(),
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('plugins', function (Blueprint $table) {
$table->text('notes')->nullable()->after('description');
$table->string('support_channel')->nullable()->after('notes');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('plugins', function (Blueprint $table) {
$table->dropColumn(['notes', 'support_channel']);
});
}
};
28 changes: 27 additions & 1 deletion resources/views/livewire/customer/plugins/create.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
<span>Loading repositories...</span>
</div>
@elseif($reposLoaded)
<flux:select wire:model="repository" label="Repository" placeholder="Select a repository...">
<flux:select wire:model.live="repository" label="Repository" placeholder="Select a repository...">
@foreach($repositories as $repo)
<flux:select.option value="{{ $repo['full_name'] }}">
{{ $repo['full_name'] }}{{ $repo['private'] ? ' (private)' : '' }}
Expand Down Expand Up @@ -153,6 +153,32 @@
@endif
@endfeature

@if($repository)
{{-- Support Channel --}}
<flux:card>
<flux:heading size="lg">Support Channel</flux:heading>
<flux:text class="mt-1">
How can users get support for your plugin? Provide an email address or a URL. If you enter a URL, ensure that it clearly details how a visitor goes about getting support for this plugin.
</flux:text>

<div class="mt-6">
<flux:input wire:model="supportChannel" label="Support Channel" placeholder="support@example.com or https://..." />
</div>
</flux:card>

{{-- Notes --}}
<flux:card>
<flux:heading size="lg">Notes</flux:heading>
<flux:text class="mt-1">
Any notes for the review team? Feel free to share links to videos of the plugin working. These won't be displayed on your plugin listing.
</flux:text>

<div class="mt-6">
<flux:textarea wire:model="notes" label="Notes" placeholder="Optional notes for the review team..." rows="4" />
</div>
</flux:card>
@endif

{{-- Submit Button --}}
<div class="flex items-center justify-end gap-4">
<flux:button variant="ghost" href="{{ route('customer.plugins.index') }}">Cancel</flux:button>
Expand Down
212 changes: 212 additions & 0 deletions tests/Feature/PluginSubmissionNotesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
<?php

namespace Tests\Feature;

use App\Livewire\Customer\Plugins\Create;
use App\Models\DeveloperAccount;
use App\Models\Plugin;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Notification;
use Livewire\Livewire;
use Tests\TestCase;

class PluginSubmissionNotesTest extends TestCase
{
use RefreshDatabase;

/**
* @param array<string, mixed> $extraComposerData
*/
private function fakeGitHubForPlugin(string $repoSlug, array $extraComposerData = []): void
{
$base = "https://github.com/ghapi/repos/{$repoSlug}";
$composerJson = json_encode(array_merge([
'name' => $repoSlug,
'description' => "A test plugin: {$repoSlug}",
'require' => [
'php' => '^8.1',
'nativephp/mobile' => '^3.0.0',
],
], $extraComposerData));

Http::fake([
"{$base}/contents/README.md" => Http::response([
'content' => base64_encode("# {$repoSlug}"),
'encoding' => 'base64',
]),
"{$base}/contents/composer.json" => Http::response([
'content' => base64_encode($composerJson),
'encoding' => 'base64',
]),
"{$base}/contents/nativephp.json" => Http::response([], 404),
"{$base}/contents/LICENSE*" => Http::response([], 404),
"{$base}/releases/latest" => Http::response([], 404),
"{$base}/tags*" => Http::response([]),
"https://github.com/ghraw/{$repoSlug}/*" => Http::response('', 404),
$base => Http::response(['default_branch' => 'main']),
"{$base}/git/trees/main*" => Http::response([
'tree' => [
['path' => 'src/ServiceProvider.php', 'type' => 'blob'],
],
]),
"{$base}/readme" => Http::response([
'content' => base64_encode("# {$repoSlug}"),
'encoding' => 'base64',
]),
]);
}

private function createUserWithGitHub(): User
{
$user = User::factory()->create([
'github_id' => '12345',
]);
DeveloperAccount::factory()->withAcceptedTerms()->create([
'user_id' => $user->id,
]);

return $user;
}

/** @test */
public function submitting_a_plugin_saves_notes(): void
{
Notification::fake();
$user = $this->createUserWithGitHub();
$repoSlug = 'acme/notes-plugin';
$this->fakeGitHubForPlugin($repoSlug);

Livewire::actingAs($user)
->test(Create::class)
->set('repository', $repoSlug)
->set('pluginType', 'free')
->set('notes', 'Please review this quickly, we have a launch deadline.')
->call('submitPlugin')
->assertRedirect();

$plugin = $user->plugins()->where('repository_url', "https://github.com/{$repoSlug}")->first();

$this->assertNotNull($plugin);
$this->assertEquals('Please review this quickly, we have a launch deadline.', $plugin->notes);
}

/** @test */
public function submitting_a_plugin_without_notes_stores_null(): void
{
Notification::fake();
$user = $this->createUserWithGitHub();
$repoSlug = 'acme/no-notes-plugin';
$this->fakeGitHubForPlugin($repoSlug);

Livewire::actingAs($user)
->test(Create::class)
->set('repository', $repoSlug)
->set('pluginType', 'free')
->call('submitPlugin')
->assertRedirect();

$plugin = $user->plugins()->where('repository_url', "https://github.com/{$repoSlug}")->first();

$this->assertNotNull($plugin);
$this->assertNull($plugin->notes);
}

/** @test */
public function submitting_a_plugin_saves_support_channel_email(): void
{
Notification::fake();
$user = $this->createUserWithGitHub();
$repoSlug = 'acme/support-email-plugin';
$this->fakeGitHubForPlugin($repoSlug);

Livewire::actingAs($user)
->test(Create::class)
->set('repository', $repoSlug)
->set('pluginType', 'free')
->set('supportChannel', 'help@example.com')
->call('submitPlugin')
->assertRedirect();

$plugin = $user->plugins()->where('repository_url', "https://github.com/{$repoSlug}")->first();

$this->assertNotNull($plugin);
$this->assertEquals('help@example.com', $plugin->support_channel);
}

/** @test */
public function submitting_a_plugin_saves_support_channel_url(): void
{
Notification::fake();
$user = $this->createUserWithGitHub();
$repoSlug = 'acme/support-url-plugin';
$this->fakeGitHubForPlugin($repoSlug);

Livewire::actingAs($user)
->test(Create::class)
->set('repository', $repoSlug)
->set('pluginType', 'free')
->set('supportChannel', 'https://example.com/support')
->call('submitPlugin')
->assertRedirect();

$plugin = $user->plugins()->where('repository_url', "https://github.com/{$repoSlug}")->first();

$this->assertNotNull($plugin);
$this->assertEquals('https://example.com/support', $plugin->support_channel);
}

/** @test */
public function submitting_a_plugin_without_support_channel_stores_null(): void
{
Notification::fake();
$user = $this->createUserWithGitHub();
$repoSlug = 'acme/no-support-plugin';
$this->fakeGitHubForPlugin($repoSlug);

Livewire::actingAs($user)
->test(Create::class)
->set('repository', $repoSlug)
->set('pluginType', 'free')
->call('submitPlugin')
->assertRedirect();

$plugin = $user->plugins()->where('repository_url', "https://github.com/{$repoSlug}")->first();

$this->assertNotNull($plugin);
$this->assertNull($plugin->support_channel);
}

/** @test */
public function notes_and_support_channel_fields_are_hidden_until_repository_selected(): void
{
$user = User::factory()->create([
'github_id' => '12345',
]);

Livewire::actingAs($user)
->test(Create::class)
->assertDontSee('Support Channel')
->assertDontSee('Any notes for the review team')
->set('repository', 'acme/my-plugin')
->assertSee('Support Channel')
->assertSee('Notes');
}

/** @test */
public function plugin_factory_with_notes_state_works(): void
{
$plugin = Plugin::factory()->withNotes('Custom note')->create();

$this->assertEquals('Custom note', $plugin->notes);
}

/** @test */
public function plugin_factory_with_support_channel_state_works(): void
{
$plugin = Plugin::factory()->withSupportChannel('support@myplugin.dev')->create();

$this->assertEquals('support@myplugin.dev', $plugin->support_channel);
}
}
Loading