diff --git a/.gitignore b/.gitignore index b817577..13eaf37 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ package-lock.json composer.phar composer.lock -phpunit.xml .phpunit.result.cache .DS_Store Thumbs.db diff --git a/README.md b/README.md index 1138170..e235238 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ A Laravel Nova tool that allows users to create and manage webhooks based on Elo A tool for Laravel's Nova administrator panel that enables users to create webhooks that can be customized to fire on specified Eloquent model events (created, updated, etc). This allows applications to communicate with other applications and integrations (Zapier, If This Then That, etc). +This tool also includes a useful logging feature that will log any successful/failed webhooks to help with metrics and debugging. + ## Table of Contents * [Installation](#installation) @@ -26,6 +28,7 @@ A tool for Laravel's Nova administrator panel that enables users to create webho * [Webhook Secret](#webhook-secret) * [Authorization](#authorization) * [Testing Action](#testing-action) + * [Logging](#logging) * [Testing and Development](#testing-and-development) ## Installation @@ -104,7 +107,42 @@ public function tools() Two different configuration files are published with this package; one for this package (`nova-webhooks.php`) and one for the webhook server (`webhook-server.php`) that this package utilizes. -This package relies on [Spatie's webhook server package](https://github.com/spatie/laravel-webhook-server) to dispatch each webhook request. Feel free to configure the server to your needs using the associated documentation. +This package relies on [Spatie's webhook server package](https://github.com/spatie/laravel-webhook-server) to dispatch each webhook request. Feel free to configure the server to your needs using the associated documentation. + +### Available Configuration Settings + +Available configuration settings for this tool: + +```php +return [ + + /** + * Whether webhooks should be sent + */ + 'enabled' => env('NOVA_WEBHOOKS_ENABLED', true), + + /** + * If logging should be enabled for each successful/failed request + */ + 'logging' => [ + 'enabled' => env('NOVA_WEBHOOKS_LOGGING_ENABLED', true), + ], + + /** + * Enter the desired formatting for timestamps that are attached to logging. + * See the official PHP documentation for more information: https://www.php.net/manual/en/datetime.format.php + */ + 'date_format' => 'Y-m-d @ G:i', + + /** + * The Laravel Nova resource that manages your authenticated users. + */ + 'users' => [ + 'resource' => App\Nova\User::class + ] + +]; +``` ## Implementing the Tool @@ -270,6 +308,14 @@ Probably the most important part of any webhook is testing and validation that y When you want execute a test, this package will pull a random entry in the selected model's table in your database and use it as the subject for your webhook. If you don't have any records available yet, the action will throw an error instructing you to add the necessary records before you proceed. +### Logging + +Unless specifically configured (as seen above), this tool will log successful and failed webhook operations. Successful events are simply stored for analytics purposes and are then displayed with some simple accompanying charts. + +![Resource Analytics](https://github.com/dniccum/nova-webhooks/blob/main/assets/resource-analytics.png?raw=true) + +If the desired endpoint that your webhooks are pointed throws an error, this tool will capture the response and log it accordingly. You can then view the associated error message within the Webhook that was created. + ## Testing and Development To perform the necessary PHP Unit tests using the Orchestra Workbench, clone the repository, install the necessary dependencies with `composer install` and run the PHP Unit testing suite: diff --git a/assets/resource-analytics.png b/assets/resource-analytics.png new file mode 100644 index 0000000..70c70a0 Binary files /dev/null and b/assets/resource-analytics.png differ diff --git a/composer.json b/composer.json index 3076915..928e0fe 100644 --- a/composer.json +++ b/composer.json @@ -19,10 +19,15 @@ "require": { "php": "^7.4|^8.0", "bensampo/laravel-enum": "^4.2", + "coroowicaksono/chart-js-integration": "^0.3.5", "owenmelbz/nova-radio-field": "^1.0", "spatie/laravel-webhook-server": "^2.0", "symfony/class-loader": "^3.4" }, + "require-dev": { + "orchestra/testbench": "^6.24", + "phpunit/phpunit": "^9.5" + }, "autoload": { "psr-4": { "Dniccum\\NovaWebhooks\\": "src/", @@ -46,9 +51,5 @@ "sort-packages": true }, "minimum-stability": "dev", - "prefer-stable": true, - "require-dev": { - "orchestra/testbench": "^6.24", - "phpunit/phpunit": "^9.5" - } + "prefer-stable": true } diff --git a/config/nova-webhooks.php b/config/nova-webhooks.php index 47b1f57..1a1f330 100644 --- a/config/nova-webhooks.php +++ b/config/nova-webhooks.php @@ -7,6 +7,20 @@ */ 'enabled' => env('NOVA_WEBHOOKS_ENABLED', true), + /** + * If logging should be enabled for each successful/failed request + */ + 'logging' => [ + + 'enabled' => env('NOVA_WEBHOOKS_LOGGING_ENABLED', true), + ], + + /** + * Enter the desired formatting for timestamps that are attached to logging. + * See the official PHP documentation for more information: https://www.php.net/manual/en/datetime.format.php + */ + 'date_format' => 'Y-m-d @ G:i', + /** * The Laravel Nova resource that manages your authenticated users. */ diff --git a/database/factories/WebhookLogFactory.php b/database/factories/WebhookLogFactory.php new file mode 100644 index 0000000..8c76c4f --- /dev/null +++ b/database/factories/WebhookLogFactory.php @@ -0,0 +1,57 @@ + true, + 'created_at' => now(), + ]; + } + + /** + * @return WebhookLogFactory|\Dniccum\NovaWebhooks\Database\Factories\WebhookLogFactory.state + */ + public function notFound() + { + return $this->state(function(array $attributes) { + return [ + 'successful' => false, + 'error_code' => '404', + 'error_message' => 'Page not found.' + ]; + }); + } + + /** + * @return WebhookLogFactory|\Dniccum\NovaWebhooks\Database\Factories\WebhookLogFactory.state + */ + public function failed() + { + return $this->state(function(array $attributes) { + return [ + 'successful' => false, + 'error_code' => '422', + 'error_message' => $this->faker->text, + ]; + }); + } +} diff --git a/database/migrations/create_webhook_logs_table.php.stub b/database/migrations/create_webhook_logs_table.php.stub new file mode 100644 index 0000000..5a2985b --- /dev/null +++ b/database/migrations/create_webhook_logs_table.php.stub @@ -0,0 +1,33 @@ +id(); + $table->boolean('successful') + ->default(true); + $table->string('error_code') + ->nullable(); + $table->text('error_message') + ->nullable(); + $table->unsignedBigInteger('webhook_id') + ->nullable(); + $table->foreign('webhook_id') + ->references('id') + ->on('webhooks'); + $table->timestamp('created_at') + ->nullable(); + }); + } + + public function down() + { + Schema::dropIfExists('webhook_logs'); + } +} diff --git a/database/migrations/create_webhooks_table.php.stub b/database/migrations/create_webhooks_table.php.stub index 6b8bfb4..92b56af 100644 --- a/database/migrations/create_webhooks_table.php.stub +++ b/database/migrations/create_webhooks_table.php.stub @@ -1,7 +1,5 @@ + + + + src/ + + + + + tests/Feature/ + + + + + + + + + + + + + + + + + diff --git a/resources/lang/en/logging.php b/resources/lang/en/logging.php new file mode 100644 index 0000000..101d11b --- /dev/null +++ b/resources/lang/en/logging.php @@ -0,0 +1,5 @@ + 'The requested endpoint could not be found.' +]; diff --git a/resources/lang/en/nova.php b/resources/lang/en/nova.php index f3272e8..2f3ac60 100644 --- a/resources/lang/en/nova.php +++ b/resources/lang/en/nova.php @@ -3,19 +3,26 @@ return [ 'available_actions' => 'Available Actions', 'available_actions_help' => 'The events and/or actions that this webhook will queue it\'s payload to the desired endpoint.' , + 'created_at' => 'Created At', + 'error_code' => 'Error Code', + 'error_message' => 'Error Message', + 'failed_calls' => 'Failed Calls', + 'last_modified_by' => 'Last Modified By', 'name' => 'Name', 'name_help' => 'Provide a helpful name/label for the webhook you are about to create; like the site and/or application that will consume it.', 'name_placeholder' => 'A helpful title', - 'last_modified_by' => 'Last Modified By', 'no_actions_available' => 'You currently do not have any actions available. Please add the necessary traits to your application\'s models to select actions for this webhook.', 'no_models_available' => 'You do not have any records available to test the requested ":model" model. Please either select a different test or add a record.', + 'resource_validation_error' => 'Please provide either a valid array or an instance of a JsonResource.', 'secret' => 'Secret', 'secret_help' => 'If necessary provide a secret that will be used to validate this hook. If not, one will be generated for you during creation.', 'select_hook_to_test' => 'Select a webhook to test', 'settings' => 'Settings', + 'successful_calls' => 'Successful Calls', 'test' => 'Test', 'test_webhook' => 'Send a Test', 'url' => 'Url', 'url_help' => 'The url or "recipient" of the webhook when it is dispatched by this application.', + 'webhook_failure_logs' => 'Webhook Failure Logs', 'webhook_to_test' => 'Webhook to test', ]; diff --git a/src/Jobs/DispatchWebhook.php b/src/Jobs/DispatchWebhook.php index 978933f..b12de52 100644 --- a/src/Jobs/DispatchWebhook.php +++ b/src/Jobs/DispatchWebhook.php @@ -33,19 +33,26 @@ class DispatchWebhook implements ShouldQueue */ public $payload; + /** + * @var bool + */ + public bool $isTest = false; + /** * Create a new job instance. * * @param Model $model * @param string $action * @param array $payload + * @param bool $isTest * @return void */ - public function __construct($model, string $action = ModelEvents::Created, array $payload = []) + public function __construct($model, string $action = ModelEvents::Created, array $payload = [], bool $isTest = false) { $this->model = $model; $this->action = $action; $this->payload = $payload; + $this->isTest = $isTest; } /** @@ -55,7 +62,7 @@ public function __construct($model, string $action = ModelEvents::Created, array */ public function handle() { - WebhookUtility::processWebhooks($this->model, $this->action, $this->payload); + WebhookUtility::processWebhooks($this->model, $this->action, $this->payload, $this->isTest); } /** diff --git a/src/Library/WebhookUtility.php b/src/Library/WebhookUtility.php index 2fef7da..f9135d3 100644 --- a/src/Library/WebhookUtility.php +++ b/src/Library/WebhookUtility.php @@ -18,10 +18,11 @@ class WebhookUtility * @param Model $model * @param array|JsonResource $payload * @param string $action + * @param bool $isTest If the webhook is running as a test through the testing action * @return void * @throws \Exception */ - public static function executeWebhook($model, string $action = ModelEvents::Created, $payload = []) : void + public static function executeWebhook($model, string $action = ModelEvents::Created, $payload = [], bool $isTest = false) : void { if (!config('nova-webhooks.enabled')) { return; @@ -32,7 +33,7 @@ public static function executeWebhook($model, string $action = ModelEvents::Crea $payload = $payload->toArray($request); } elseif (!is_array($payload)) { throw new \Exception( - 'Please provide either a valid array or an instance of a JsonResource.', // TODO add translation + __('nova-webhooks::nova.resource_validation_error'), 500 ); } @@ -40,9 +41,9 @@ public static function executeWebhook($model, string $action = ModelEvents::Crea $shouldQueue = method_exists($model, 'queueWebhook') && $model::queueWebhook(); if ($shouldQueue) { $jobToUse = $model::$job; - dispatch(new $jobToUse($model, $action, $payload)); + dispatch(new $jobToUse($model, $action, $payload, $isTest)); } else { - self::processWebhooks($model, $action, $payload); + self::processWebhooks($model, $action, $payload, $isTest); } } @@ -50,9 +51,10 @@ public static function executeWebhook($model, string $action = ModelEvents::Crea * @param Model $model * @param string $action * @param array $payload + * @param bool $isTest If the webhook is running as a test through the testing action * @return void */ - public static function processWebhooks($model, $action, array $payload = []) + public static function processWebhooks($model, $action, array $payload = [], bool $isTest = false) { /** * Retrieves the name of the model class with namespacing @@ -61,20 +63,29 @@ public static function processWebhooks($model, $action, array $payload = []) $className = get_class($model); $hooks = self::getWebhooks($className.':'.$action); - $hooks->each(function(Webhook $webhook) use ($model, $payload) { - self::compileWebhook($webhook, $payload); + $hooks->each(function(Webhook $webhook) use ($model, $payload, $isTest) { + self::compileWebhook($webhook, $payload, $isTest); }); } /** * @param Webhook $webhook * @param array $payload + * @param bool $isTest If the webhook is being tested, a successful log entry will not be saved. * @return WebhookCall */ - public static function compileWebhook(Webhook $webhook, array $payload = []) : PendingDispatch + public static function compileWebhook(Webhook $webhook, array $payload = [], bool $isTest = false) : PendingDispatch { return WebhookCall::create() ->url($webhook->url) + ->meta([ + 'webhook_id' => $webhook->id, + 'test' => $isTest, + ]) + ->withTags([ + 'nova-webhooks', + \Str::slug($webhook->name).'-webhook', + ]) ->payload($payload) ->useSecret($webhook->secret) ->dispatch(); diff --git a/src/Listeners/WebhookFailed.php b/src/Listeners/WebhookFailed.php new file mode 100644 index 0000000..2458f5f --- /dev/null +++ b/src/Listeners/WebhookFailed.php @@ -0,0 +1,48 @@ +errorMessage; + $errorCode = optional($event->response)->getStatusCode(); + + if (!$message || strlen($message) === 0) { + if ($errorCode == 404) { + $message = __('nova-webhooks.logging.not_found'); + } + } + + if (config('nova-webhooks.logging.enabled')) { + $meta = $event->meta; + $log = new WebhookLog([ + 'successful' => false, + 'error_code' => $errorCode, + 'error_message' => $message, + 'webhook_id' => isset($meta['webhook_id']) ? $meta['webhook_id'] : null, + ]); + $log->save(); + } + } +} diff --git a/src/Listeners/WebhookSucceeded.php b/src/Listeners/WebhookSucceeded.php new file mode 100644 index 0000000..52463d4 --- /dev/null +++ b/src/Listeners/WebhookSucceeded.php @@ -0,0 +1,39 @@ +meta; + $isATest = isset($meta['test']) ? $meta['test'] : false; + + if (!$isATest) { + $log = new WebhookLog; + $log->webhook_id = isset($meta['webhook_id']) ? $meta['webhook_id'] : null; + $log->save(); + } + } + } +} diff --git a/src/Models/Webhook.php b/src/Models/Webhook.php index d418acf..12ca905 100644 --- a/src/Models/Webhook.php +++ b/src/Models/Webhook.php @@ -2,7 +2,6 @@ namespace Dniccum\NovaWebhooks\Models; -use App\Models\User; use Dniccum\NovaWebhooks\Database\Factories\WebhookFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -33,6 +32,11 @@ protected static function booted() : void static::updating(function(\Dniccum\NovaWebhooks\Models\Webhook $webhook) { $webhook->modified_by = \Auth::id(); }); + static::deleting(function(\Dniccum\NovaWebhooks\Models\Webhook $webhook) { + \DB::table('webhook_logs') + ->where('webhook_id', $webhook->id) + ->delete(); + }); } /** @@ -43,6 +47,14 @@ public function modifiedBy() return $this->belongsTo(config('auth.providers.users.model'), 'modified_by', 'id'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function logs() + { + return $this->hasMany(WebhookLog::class, 'webhook_id', 'id'); + } + /** * @return string */ diff --git a/src/Models/WebhookLog.php b/src/Models/WebhookLog.php new file mode 100644 index 0000000..859d8aa --- /dev/null +++ b/src/Models/WebhookLog.php @@ -0,0 +1,53 @@ + 'boolean', + ]; + + protected $attributes = [ + 'successful' => true, + ]; + + /** + * @inheritDoc + */ + protected static function booted() : void + { + parent::boot(); + + static::creating(function(WebhookLog $model) { + if (!$model->created_at) { + $model->created_at = now(); + } + }); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function webhook() + { + return $this->belongsTo(Webhook::class, 'webhook_id', 'id'); + } + + protected static function newFactory() + { + return WebhookLogFactory::new(); + } +} diff --git a/src/Nova/Actions/WebhookTestAction.php b/src/Nova/Actions/WebhookTestAction.php index e545e11..4ebb38a 100644 --- a/src/Nova/Actions/WebhookTestAction.php +++ b/src/Nova/Actions/WebhookTestAction.php @@ -84,11 +84,11 @@ public function handle(ActionFields $fields, Collection $models) } if ($actionName->is(ModelEvents::Created)) { - $class::createdWebhook($webhookModel); + $class::createdWebhook($webhookModel, true); } elseif ($actionName->is(ModelEvents::Updated)) { - $class::updatedWebhook($webhookModel); + $class::updatedWebhook($webhookModel, true); } elseif ($actionName->is(ModelEvents::Deleted)) { - $class::deletedWebhook($webhookModel); + $class::deletedWebhook($webhookModel, true); } } } diff --git a/src/Nova/UsesWebhookResource.php b/src/Nova/UsesWebhookResource.php index 4dc0562..cc93c53 100644 --- a/src/Nova/UsesWebhookResource.php +++ b/src/Nova/UsesWebhookResource.php @@ -12,6 +12,7 @@ protected function resources() Nova::resources([ Webhook::class, + WebhookLog::class, ]); } } diff --git a/src/Nova/Webhook.php b/src/Nova/Webhook.php index 6aba6af..cdaec84 100644 --- a/src/Nova/Webhook.php +++ b/src/Nova/Webhook.php @@ -2,11 +2,16 @@ namespace Dniccum\NovaWebhooks\Nova; +use Coroowicaksono\ChartJsIntegration\StackedChart; +use Dniccum\NovaWebhooks\Models\WebhookLog; +use Dniccum\NovaWebhooks\Nova\WebhookLog as WebhookLogResource; use Dniccum\NovaWebhooks\Nova\Actions\WebhookTestAction; use Illuminate\Http\Request; use Laravel\Nova\Fields\BelongsTo; +use Laravel\Nova\Fields\HasMany; use Laravel\Nova\Fields\ID; use Laravel\Nova\Fields\Text; +use Laravel\Nova\Http\Requests\CardRequest; class Webhook extends WebhookResource { @@ -46,6 +51,9 @@ public function fields(Request $request) BelongsTo::make(__('nova-webhooks::nova.last_modified_by'), 'modifiedBy', config('nova-webhooks.users.resource')) ->exceptOnForms() ->readonly(), + + HasMany::make(__('nova-webhooks::nova.webhook_failure_logs'), 'logs', WebhookLogResource::class) + ->readonly(), ]; } @@ -62,4 +70,84 @@ public function actions(Request $request) ->confirmButtonText(__('nova-webhooks::nova.test')), ]; } + + /** + * @param Request|CardRequest $request + * @return array + */ + public function cards(Request $request) + { + $resourceId = $request->get('resourceId'); + $dataSeries = [ + [ + 'label' => __('nova-webhooks::nova.successful_calls'), + 'backgroundColor' => '#2BAE68', + 'filter' => [ + 'key' => 'successful', + 'operator' => '=', + 'value' => 1 + ], + ], + [ + 'label' => __('nova-webhooks::nova.failed_calls'), + 'backgroundColor' => '#FF4D4F', + 'filter' => [ + 'key' => 'error_code', + 'operator' => 'IS NOT NULL', + ], + ] + ]; + + if ($resourceId) { + return [ + (new StackedChart()) + ->title('Webhook Activity') + ->model(WebhookLog::class) + ->series($dataSeries) + ->options([ + 'uom' => 'month', // available in 'day', 'week', 'month', 'hour' + 'showTotal' => false, + 'queryFilter' => [ + [ + 'key' => 'created_at', + 'operator' => '>=', + 'value' => now() + ->startOfDay() + ->subMonths(12) + ->format('Y-m-d'), + ], + [ + 'key' => 'webhook_id', + 'operator' => '=', + 'value' => $resourceId, + ] + ], + ]) + ->onlyOnDetail() + ->width('2/3'), + ]; + } + + return [ + (new StackedChart()) + ->title('Webhook Activity') + ->model(WebhookLog::class) + ->series($dataSeries) + ->options([ + 'uom' => 'month', // available in 'day', 'week', 'month', 'hour' + 'showTotal' => false, + 'queryFilter' => [ + [ + 'key' => 'created_at', + 'operator' => '>=', + 'value' => now() + ->startOfDay() + ->subMonths(12) + ->format('Y-m-d'), + ] + ], + ]) + ->width('2/3') + ]; + } } diff --git a/src/Nova/WebhookLog.php b/src/Nova/WebhookLog.php new file mode 100644 index 0000000..75bf9d4 --- /dev/null +++ b/src/Nova/WebhookLog.php @@ -0,0 +1,87 @@ +sortable(), + + Number::make(__('nova-webhooks::nova.error_code'), 'error_code') + ->sortable() + ->readonly(), + + Textarea::make(__('nova-webhooks::nova.error_message'), 'error_message') + ->alwaysShow() + ->readonly(), + + Date::make(__('nova-webhooks::nova.created_at'), 'created_at') + ->sortable() + ->readonly() + ->displayUsing(function ($date) { + return \Carbon\Carbon::make($date)->format(config('nova-webhooks.date_format')); + }), + ]; + } + + /** + * Build a "relatable" query for the given resource. + * + * This query determines which instances of the model may be attached to other resources. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function indexQuery(NovaRequest $request, $query) + { + return $query->where('successful', false); + } +} diff --git a/src/Policies/WebhookLogPolicy.php b/src/Policies/WebhookLogPolicy.php new file mode 100644 index 0000000..c3b40c9 --- /dev/null +++ b/src/Policies/WebhookLogPolicy.php @@ -0,0 +1,32 @@ + [ + WebhookSucceeded::class + ], + FinalWebhookCallFailedEvent::class => [ + WebhookFailed::class + ] + ]; + + /** + * Register any events for your application. + * + * @return void + */ + public function boot() + { + parent::boot(); + } +} diff --git a/src/ToolServiceProvider.php b/src/ToolServiceProvider.php index ce16f45..37a5904 100644 --- a/src/ToolServiceProvider.php +++ b/src/ToolServiceProvider.php @@ -2,8 +2,10 @@ namespace Dniccum\NovaWebhooks; +use Coroowicaksono\ChartJsIntegration\CardServiceProvider; use Dniccum\NovaWebhooks\Library\ModelUtility; use Dniccum\NovaWebhooks\Library\WebhookUtility; +use Dniccum\NovaWebhooks\Providers\ToolEventServiceProvider; use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; use Laravel\Nova\Events\ServingNova; @@ -57,6 +59,11 @@ protected function migrations() __DIR__ . '/../database/migrations/create_webhooks_table.php.stub' => database_path('migrations/' . date('Y_m_d_His', time()) . '_create_webhooks_table.php') ], 'nova-webhooks'); } + if (! class_exists('CreateWebhookLogsTable')) { + $this->publishes([ + __DIR__ . '/../database/migrations/create_webhook_logs_table.php.stub' => database_path('migrations/' . date('Y_m_d_His', time()) . '_create_webhook_logs_table.php') + ], 'nova-webhooks'); + } } return $this; @@ -68,7 +75,7 @@ protected function novaResources() $this->publishes([ __DIR__ . '/../resources/lang' => resource_path('lang/vendor/nova-webhooks'), - ], 'nova-webhooks'); + ], 'nova-webhooks-translations'); return $this; } @@ -88,6 +95,8 @@ public function register() $this->app->bind('webhook-models', function() { return new ModelUtility; }); + $this->app->register(ToolEventServiceProvider::class); + $this->app->resolveProvider(CardServiceProvider::class); } protected function registerAddonConfig() : ToolServiceProvider diff --git a/src/Traits/CreatedWebhook.php b/src/Traits/CreatedWebhook.php index 6aedd06..d02e2b9 100644 --- a/src/Traits/CreatedWebhook.php +++ b/src/Traits/CreatedWebhook.php @@ -26,13 +26,14 @@ public static function bootCreatedWebhook() : void /** * @param \Illuminate\Database\Eloquent\Model $model + * @param boolean $isTest If the webhook is running as a test through the testing action * @return void * @throws \Exception */ - public static function createdWebhook($model) + public static function createdWebhook($model, bool $isTest = false) { $payload = self::createdWebhookPayload($model); - WebhookUtility::executeWebhook($model, ModelEvents::Created, $payload); + WebhookUtility::executeWebhook($model, ModelEvents::Created, $payload, $isTest); } /** diff --git a/src/Traits/DeletedWebhook.php b/src/Traits/DeletedWebhook.php index 27c69de..9d4317a 100644 --- a/src/Traits/DeletedWebhook.php +++ b/src/Traits/DeletedWebhook.php @@ -26,16 +26,17 @@ public static function bootDeletedWebhook() : void /** * @param \Illuminate\Database\Eloquent\Model $model + * @param boolean $isTest If the webhook is running as a test through the testing action * @return void * @throws \Exception */ - public static function deletedWebhook($model) + public static function deletedWebhook($model, bool $isTest = false) { /** * @param \Illuminate\Database\Eloquent\Model $model */ $payload = self::deletedWebhookPayload($model); - WebhookUtility::executeWebhook($model, ModelEvents::Deleted, $payload); + WebhookUtility::executeWebhook($model, ModelEvents::Deleted, $payload, $isTest); } /** diff --git a/src/Traits/UpdatedWebhook.php b/src/Traits/UpdatedWebhook.php index 05ad00c..ff12640 100644 --- a/src/Traits/UpdatedWebhook.php +++ b/src/Traits/UpdatedWebhook.php @@ -26,13 +26,14 @@ public static function bootUpdatedWebhook() : void /** * @param \Illuminate\Database\Eloquent\Model $model + * @param boolean $isTest If the webhook is running as a test through the testing action * @return void * @throws \Exception */ - public static function updatedWebhook($model) + public static function updatedWebhook($model, bool $isTest = false) { $payload = self::updatedWebhookPayload($model); - WebhookUtility::executeWebhook($model, ModelEvents::Updated, $payload); + WebhookUtility::executeWebhook($model, ModelEvents::Updated, $payload, $isTest); } /** diff --git a/tests/Feature/EventLoggingTest.php b/tests/Feature/EventLoggingTest.php new file mode 100644 index 0000000..1841166 --- /dev/null +++ b/tests/Feature/EventLoggingTest.php @@ -0,0 +1,67 @@ +create([ + 'url' => 'https://webhook.site/022b21d3-fce9-43e8-bc89-ba122fcc124d', + 'settings' => [ + PageLike::class.':'.ModelEvents::Created, + PageLike::class.':'.ModelEvents::Deleted, + ], + 'secret' => Webhook::newSecret(), + ]); + } + + /** + * @test + * @covers \Dniccum\NovaWebhooks\Listeners\WebhookSucceeded + */ + public function custom_event_listener_is_listening_to_the_call_succeeded_event() + { + \Event::fake(); + + $like = PageLike::factory() + ->create(); + + $like->delete(); + + \Event::assertListening(WebhookCallSucceededEvent::class, WebhookSucceeded::class); + } + + /** + * @test + * @covers \Dniccum\NovaWebhooks\Listeners\WebhookSucceeded + */ + public function custom_event_listener_is_listening_to_the_call_failed_event() + { + \Event::fake(); + + $like = PageLike::factory() + ->create(); + + $like->delete(); + + \Event::assertListening(FinalWebhookCallFailedEvent::class, WebhookFailed::class); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index b4af901..a1dfc3c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,7 +4,8 @@ use Dniccum\NovaWebhooks\Database\Migrations\CreatePageLikesTable; use Dniccum\NovaWebhooks\Database\Migrations\CreatePageViewsTable; -use Dniccum\NovaWebhooks\Database\Migrations\CreateWebhooksTable; +use CreateWebhookLogsTable; +use CreateWebhooksTable; use Dniccum\NovaWebhooks\Models\Webhook; use Dniccum\NovaWebhooks\Tests\Models\Api\PageLike; use Dniccum\NovaWebhooks\Tests\Models\PageView; @@ -36,11 +37,13 @@ protected function defineDatabaseMigrations(): void $this->artisan('migrate', ['--database' => 'testing']); include_once __DIR__ . '/../database/migrations/create_webhooks_table.php.stub'; + include_once __DIR__ . '/../database/migrations/create_webhook_logs_table.php.stub'; include_once __DIR__ . '/database/migrations/create_page_views_table.php.stub'; include_once __DIR__ . '/database/migrations/create_page_likes_table.php.stub'; (new CreatePageViewsTable())->up(); (new CreatePageLikesTable())->up(); (new CreateWebhooksTable())->up(); + (new CreateWebhookLogsTable())->up(); } /**