Skip to content

Commit

Permalink
feat: Added support for additional notification stream f
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislarsen committed Aug 3, 2024
1 parent 8ae69a8 commit 4664d75
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ public function mount(): void
/**
* Handle changes to the notification stream type.
*
* Resets validation for the value field when the type changes.
* Resets validation for the value field and additional fields when the type changes.
*/
public function updatedFormType(): void
{
$this->resetValidation('form.value');
$this->resetValidation(['form.value', 'form.additional_field_one', 'form.additional_field_two']);
}

/**
Expand All @@ -54,14 +54,24 @@ public function submit(): RedirectResponse|Redirector
/** @var User $user */
$user = Auth::user();

NotificationStream::create([
$data = [
'label' => $this->form->label,
'type' => $this->form->type,
'value' => $this->form->value,
'receive_successful_backup_notifications' => $this->form->success_notification ? now() : null,
'receive_failed_backup_notifications' => $this->form->failed_notification ? now() : null,
'user_id' => $user->getAttribute('id'),
]);
];

// Add additional fields if they are set and not null
if (! is_null($this->form->additional_field_one)) {
$data['additional_field_one'] = $this->form->additional_field_one;
}
if (! is_null($this->form->additional_field_two)) {
$data['additional_field_two'] = $this->form->additional_field_two;
}

NotificationStream::create($data);

Toaster::success('Notification stream has been added.');

Expand Down
95 changes: 87 additions & 8 deletions app/Livewire/NotificationStreams/Forms/NotificationStreamForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,53 @@ class NotificationStreamForm extends Form
public string $value = '';
public bool $success_notification = false;
public bool $failed_notification = true;
public ?string $additional_field_one = null;
public ?string $additional_field_two = null;

/** @var Collection<string, string> */
public Collection $availableTypes;

/** @var array<string, array<string, mixed>> */
protected array $additionalFields = [
NotificationStream::TYPE_PUSHOVER => [
'additional_field_one' => [
'label' => 'User Key',
'rules' => ['required', 'string'],
'error_message' => 'Please enter a valid Pushover User Key.',
],
],
];

/** @var array<string, array<string, string>> */
protected array $typeConfig = [
NotificationStream::TYPE_DISCORD => [
'label' => 'Webhook URL',
'input_type' => 'url',
],
NotificationStream::TYPE_SLACK => [
'label' => 'Webhook URL',
'input_type' => 'url',
],
NotificationStream::TYPE_TEAMS => [
'label' => 'Webhook URL',
'input_type' => 'url',
],
NotificationStream::TYPE_EMAIL => [
'label' => 'Email Address',
'input_type' => 'email',
],
NotificationStream::TYPE_PUSHOVER => [
'label' => 'API Token',
'input_type' => 'text',
],
];

/**
* @return array<string, mixed>
*/
public function rules(): array
{
return [
$rules = [
'label' => ['required', 'string', 'max:255'],
'type' => ['required', 'string', Rule::in($this->availableTypes->keys())],
'success_notification' => ['nullable', 'boolean'],
Expand All @@ -49,14 +86,26 @@ function ($attribute, $value, $fail): void {
},
],
];

// Add dynamic rules for additional fields
if (isset($this->additionalFields[$this->type])) {
foreach ($this->additionalFields[$this->type] as $field => $config) {
$rules[$field] = $config['rules'];
}
} else {
$rules['additional_field_one'] = ['nullable'];
$rules['additional_field_two'] = ['nullable'];
}

return $rules;
}

/**
* @return array<string, string>
*/
public function messages(): array
{
return [
$messages = [
'label.required' => __('Please enter a label.'),
'label.max' => __('The label must not exceed 255 characters.'),
'type.required' => __('Please select a notification type.'),
Expand All @@ -65,6 +114,14 @@ public function messages(): array
'success_notification.boolean' => __('The notification status must be true or false.'),
'failed_notification.boolean' => __('The notification status must be true or false.'),
];

if (isset($this->additionalFields[$this->type])) {
foreach ($this->additionalFields[$this->type] as $field => $config) {
$messages[$field . '.required'] = __($config['error_message']);
}
}

return $messages;
}

public function initialize(): void
Expand All @@ -85,6 +142,28 @@ public function setNotificationStream(NotificationStream $notificationStream): v
$this->value = $notificationStream->getAttribute('value');
$this->success_notification = (bool) $notificationStream->getAttribute('receive_successful_backup_notifications');
$this->failed_notification = (bool) $notificationStream->getAttribute('receive_failed_backup_notifications');
$this->additional_field_one = $notificationStream->getAttribute('additional_field_one');
$this->additional_field_two = $notificationStream->getAttribute('additional_field_two');
}

/**
* Get the configuration for additional fields based on the current type.
*
* @return array<string, array<string, mixed>>
*/
public function getAdditionalFieldsConfig(): array
{
return $this->additionalFields[$this->type] ?? [];
}

public function getValueLabel(): string
{
return $this->typeConfig[$this->type]['label'] ?? __('Value');
}

public function getValueInputType(): string
{
return $this->typeConfig[$this->type]['input_type'] ?? 'text';
}

/**
Expand All @@ -96,7 +175,7 @@ protected function getValueValidationRule(): array|string
NotificationStream::TYPE_DISCORD => ['url', 'regex:/^https:\/\/discord\.com\/api\/webhooks\//'],
NotificationStream::TYPE_SLACK => ['url', 'regex:/^https:\/\/hooks\.slack\.com\/services\//'],
NotificationStream::TYPE_TEAMS => ['url', 'regex:/^https:\/\/.*\.webhook\.office\.com\/webhookb2\/.+/i'],
NotificationStream::TYPE_PUSHOVER => ['string'],
NotificationStream::TYPE_PUSHOVER => ['required', 'string'],
NotificationStream::TYPE_EMAIL => ['email'],
default => 'string',
};
Expand All @@ -105,11 +184,11 @@ protected function getValueValidationRule(): array|string
protected function getValueErrorMessage(): string
{
return match ($this->type) {
NotificationStream::TYPE_DISCORD => __('Please enter a Discord webhook URL.'),
NotificationStream::TYPE_SLACK => __('Please enter a Slack webhook URL.'),
NotificationStream::TYPE_TEAMS => __('Please enter a Microsoft Teams Webhook URL.'),
NotificationStream::TYPE_EMAIL => __('Please enter an email address.'),
NotificationStream::TYPE_PUSHOVER => __('Please enter a Pushover token.'),
NotificationStream::TYPE_DISCORD => __('Please enter a valid Discord webhook URL.'),
NotificationStream::TYPE_SLACK => __('Please enter a valid Slack webhook URL.'),
NotificationStream::TYPE_TEAMS => __('Please enter a valid Microsoft Teams Webhook URL.'),
NotificationStream::TYPE_EMAIL => __('Please enter a valid email address.'),
NotificationStream::TYPE_PUSHOVER => __('Please enter a valid Pushover API Token.'),
default => __('Please enter a valid value for the selected notification type.'),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ public function mount(NotificationStream $notificationStream): void
/**
* Handle changes to the notification stream type.
*
* Resets validation for the value field when the type changes.
* Resets validation for the value field and additional fields when the type changes.
*/
public function updatedFormType(): void
{
$this->resetValidation('form.value');
$this->resetValidation(['form.value', 'form.additional_field_one', 'form.additional_field_two']);
}

/**
Expand All @@ -61,13 +61,25 @@ public function submit(): RedirectResponse|Redirector

$this->form->validate();

$this->notificationStream->update([
$data = [
'label' => $this->form->label,
'type' => $this->form->type,
'value' => $this->form->value,
'receive_successful_backup_notifications' => $this->form->success_notification ? now() : null,
'receive_failed_backup_notifications' => $this->form->failed_notification ? now() : null,
]);
];

// Handle additional fields
if ($this->form->getAdditionalFieldsConfig()) {
$data['additional_field_one'] = $this->form->additional_field_one;
$data['additional_field_two'] = $this->form->additional_field_two;
} else {
// If the type doesn't use additional fields, set them to null
$data['additional_field_one'] = null;
$data['additional_field_two'] = null;
}

$this->notificationStream->update($data);

Toaster::success('Notification stream has been updated.');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

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

return new class extends Migration
{
public function up(): void
{
Schema::table('notification_streams', function (Blueprint $table) {
$table->text('additional_field_one')->nullable();
$table->text('additional_field_two')->nullable();
});
}
};
13 changes: 11 additions & 2 deletions resources/views/components/notification-stream-form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,19 @@
</div>
</div>
<div class="mt-4">
<x-input-label for="form.value" :value="__($form->availableTypes[$form->type] ?? 'Value')"/>
<x-text-input id="form.value" class="block mt-1 w-full" type="{{ $form->type === 'email' ? 'email' : 'text' }}" wire:model.defer="form.value" name="form.value"/>
<x-input-label for="form.value" :value="__($form->getValueLabel())"/>
<x-text-input id="form.value" class="block mt-1 w-full" type="{{ $form->getValueInputType() }}" wire:model.defer="form.value" name="form.value"/>
@error('form.value') <x-input-error :messages="$message" class="mt-2"/> @enderror
</div>

@foreach ($form->getAdditionalFieldsConfig() as $field => $config)
<div class="mt-4">
<x-input-label for="form.{{ $field }}" :value="__($config['label'])"/>
<x-text-input id="form.{{ $field }}" class="block mt-1 w-full" type="text" wire:model.defer="form.{{ $field }}" name="form.{{ $field }}"/>
@error('form.' . $field) <x-input-error :messages="$message" class="mt-2"/> @enderror
</div>
@endforeach

<div class="mt-6">
<x-form-section>
{{ __('Notifications') }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,49 @@

Carbon::setTestNow();
});

it('submits successfully with Pushover and additional fields', function (): void {
$testData = [
'label' => 'Test Pushover Notification',
'type' => 'pushover',
'value' => 'azGDORePK8gMaC0QOYAMyEEuzJnyUi', // Example Pushover API Token
'additional_field_one' => 'uQiRzpo4DXghDmr9QzzfQu27cmVRsG', // Example User Key
'success_notification' => true,
'failed_notification' => true,
];

Livewire::actingAs($this->user)
->test(CreateNotificationStream::class)
->set('form.label', $testData['label'])
->set('form.type', $testData['type'])
->set('form.value', $testData['value'])
->set('form.additional_field_one', $testData['additional_field_one'])
->set('form.success_notification', $testData['success_notification'])
->set('form.failed_notification', $testData['failed_notification'])
->call('submit')
->assertHasNoErrors()
->assertRedirect(route('notification-streams.index'));

$this->assertDatabaseHas('notification_streams', [
'user_id' => $this->user->id,
'label' => $testData['label'],
'type' => $testData['type'],
'value' => $testData['value'],
'additional_field_one' => $testData['additional_field_one'],
]);

$notificationStream = $this->user->notificationStreams()->latest()->first();
$this->assertNotNull($notificationStream->receive_successful_backup_notifications);
$this->assertNotNull($notificationStream->receive_failed_backup_notifications);
});

it('validates Pushover additional fields', function (): void {
Livewire::actingAs($this->user)
->test(CreateNotificationStream::class)
->set('form.label', 'Test Pushover')
->set('form.type', 'pushover')
->set('form.value', 'validApiToken')
->set('form.additional_field_one', '') // Empty User Key
->call('submit')
->assertHasErrors(['form.additional_field_one']);
});
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,64 @@
->test(UpdateNotificationStream::class, ['notificationStream' => $notificationStream])
->assertStatus(403);
});

it('updates successfully with Pushover and additional fields', function (): void {
$user = User::factory()->create();
$notificationStream = NotificationStream::factory()->create([
'user_id' => $user->id,
'type' => 'pushover',
'value' => 'oldApiToken',
'additional_field_one' => 'oldUserKey',
'receive_successful_backup_notifications' => null,
'receive_failed_backup_notifications' => now(),
]);

$newData = [
'label' => 'Updated Pushover Notification',
'type' => 'pushover',
'value' => 'newApiToken',
'additional_field_one' => 'newUserKey',
'success_notification' => true,
'failed_notification' => false,
];

Livewire::actingAs($user)
->test(UpdateNotificationStream::class, ['notificationStream' => $notificationStream])
->set('form.label', $newData['label'])
->set('form.type', $newData['type'])
->set('form.value', $newData['value'])
->set('form.additional_field_one', $newData['additional_field_one'])
->set('form.success_notification', $newData['success_notification'])
->set('form.failed_notification', $newData['failed_notification'])
->call('submit')
->assertHasNoErrors()
->assertRedirect(route('notification-streams.index'));

$updatedStream = $notificationStream->fresh();
expect($updatedStream)->toMatchArray([
'id' => $notificationStream->id,
'user_id' => $user->id,
'label' => $newData['label'],
'type' => $newData['type'],
'value' => $newData['value'],
'additional_field_one' => $newData['additional_field_one'],
])
->and($updatedStream->receive_successful_backup_notifications)->not->toBeNull()
->and($updatedStream->receive_failed_backup_notifications)->toBeNull();
});

it('validates Pushover additional fields during update', function (): void {
$user = User::factory()->create();
$notificationStream = NotificationStream::factory()->create([
'user_id' => $user->id,
'type' => 'pushover',
'value' => 'validApiToken',
'additional_field_one' => 'validUserKey',
]);

Livewire::actingAs($user)
->test(UpdateNotificationStream::class, ['notificationStream' => $notificationStream])
->set('form.additional_field_one', '') // Empty User Key
->call('submit')
->assertHasErrors(['form.additional_field_one']);
});
Loading

0 comments on commit 4664d75

Please sign in to comment.