Skip to content

Commit

Permalink
Merge pull request #29 from mailcarrierapp/feat/enhance-send-test
Browse files Browse the repository at this point in the history
[2.x] Enhance send test feature
  • Loading branch information
danilopolani authored Mar 16, 2024
2 parents 561f698 + 2b7db90 commit 7b54121
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 51 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"scripts": {
"dev": "mix watch",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"watch-poll": "mix watch -- --watch-options-poll=3000",
"hot": "mix watch --hot",
"build": "mix --production"
},
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ parameters:
reportUnmatchedIgnoredErrors: false
ignoreErrors:
- '#Method MailCarrier\\Resources\\(.*)\\Pages\\(.*)::getRecord\(\)(.*)#'
- '#Unsafe usage of new static(.*)#'
2 changes: 1 addition & 1 deletion resources/dist/css/theme.css

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions src/Helpers/TemplateManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace MailCarrier\Helpers;

use MailCarrier\Models\Template;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
use Twig\Source;

class TemplateManager
{
public function __construct(protected Template $template)
{
//
}

public static function make(Template $template): static
{
return new static($template);
}

public function extractVariableNames(): array
{
$source = $this->template->layout?->content . $this->template->content;

$twig = new Environment(new ArrayLoader());
$nodes = $twig->parse(
$twig->tokenize(new Source($source, ''))
)->getNode('body')->getNode('0');

preg_match_all("|Twig\\\Node\\\Expression\\\NameExpression\(name\: '(.*)'|mi", (string) $nodes, $matches);

return array_values(array_unique($matches[1]));
}

protected function getLoader(): ArrayLoader
{
$mainFileName = sprintf('main-%d.html', $this->template->id);
$layoutFileName = !$this->template->layout_id ? null : sprintf('layout-%d.html', $this->template->layout_id);
$mainFileContent = !$this->template->layout_id ? $this->template->content : sprintf(
'{%% extends "%s" %%}{%% block content %%}%s{%% endblock %%}',
$layoutFileName,
$this->template->content,
);

return new ArrayLoader([
$mainFileName => $mainFileContent,
$layoutFileName => $this->template->layout?->content,
]);
}
}
90 changes: 90 additions & 0 deletions src/Resources/TemplateResource/Actions/SendTestAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace MailCarrier\Resources\TemplateResource\Actions;

use Filament\Actions\Action;
use Filament\Forms;
use Filament\Notifications\Notification;
use Filament\Support\Enums\Alignment;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\HtmlString;
use MailCarrier\Actions\SendMail;
use MailCarrier\Dto\SendMailDto;
use MailCarrier\Helpers\TemplateManager;

class SendTestAction extends Action
{
public static function getDefaultName(): ?string
{
return 'send_test';
}

protected function setUp(): void
{
parent::setUp();

$this->label('Send test');
$this->icon('heroicon-o-paper-airplane');
$this->modalHeading('Send test email');
$this->modalSubmitActionLabel('Send');
$this->modalFooterActionsAlignment(Alignment::End);
$this->extraAttributes([
'class' => 'button-send-test !bg-purple-500',
]);

$this->form([
Forms\Components\TextInput::make('email')
->email()
->required(),
Forms\Components\KeyValue::make('variables')
->keyLabel('Variable name')
->valueLabel('Variable value')
->valuePlaceholder('Fill or delete')
->default(
Arr::mapWithKeys(
// @phpstan-ignore-next-line
TemplateManager::make($this->getRecord())->extractVariableNames(),
fn (string $value) => [$value => null]
)
),
Forms\Components\Checkbox::make('enqueue'),
]);

$this->action(function (array $data, Forms\Form $form): void {
// Reset error box if any
$this->modalContent(null);

try {
SendMail::resolve()
->withoutLogging()
->run(
new SendMailDto(
template: $this->getRecord()->slug, // @phpstan-ignore-line
subject: 'Test from ' . Config::get('app.name'),
recipient: $data['email'],
enqueue: $data['enqueue'],
variables: Arr::undot($data['variables'] ?: []),
)
);
} catch (\Exception $e) {
$this->modalContent(new HtmlString(<<<HTML
<div class="bg-danger-100 dark:bg-danger-500/20 border border-danger-300 dark:border-danger-600 rounded py-3 px-4 text-sm">
<p class="font-bold">🤕 Something went wrong</p>
<p class="mt-2 text-xs">{$e->getMessage()}</p>
</div>
HTML));

$this->halt();

return;
}

Notification::make()
->title('Test email sent correctly')
->icon('heroicon-o-envelope')
->success()
->send();
});
}
}
51 changes: 2 additions & 49 deletions src/Resources/TemplateResource/Pages/EditTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,10 @@
namespace MailCarrier\Resources\TemplateResource\Pages;

use Filament\Actions;
use Filament\Forms;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use MailCarrier\Actions\SendMail;
use MailCarrier\Dto\SendMailDto;
use MailCarrier\Models\Template;
use MailCarrier\Resources\TemplateResource;
use MailCarrier\Resources\TemplateResource\Actions\SendTestAction;

class EditTemplate extends EditRecord
{
Expand All @@ -28,25 +23,7 @@ public function getRecord(): Template
protected function getActions(): array
{
return [
Actions\Action::make('send_test')
->label('Send test')
->icon('heroicon-o-paper-airplane')
->extraAttributes([
'class' => 'button-send-test !bg-purple-500',
])
// Build the modal
->action($this->sendTestMail(...))
->modalSubmitActionLabel('Send')
->form([
Forms\Components\TextInput::make('email')
->email()
->required(),
Forms\Components\KeyValue::make('variables')
->keyLabel('Variable name')
->valueLabel('Variable value'),
Forms\Components\Checkbox::make('enqueue'),
]),

SendTestAction::make(),
Actions\Action::make('save')
->label(__('Save changes'))
->action('save'),
Expand All @@ -66,28 +43,4 @@ protected function getFormActions(): array
->disabled($this->getRecord()->is_locked || !TemplateResource::canDelete($this->record)),
];
}

/**
* Send a test mail.
*/
protected function sendTestMail(array $data): void
{
SendMail::resolve()
->withoutLogging()
->run(
new SendMailDto(
template: $this->getRecord()->slug, // @phpstan-ignore-line
subject: 'Test from ' . Config::get('app.name'),
recipient: $data['email'],
enqueue: $data['enqueue'],
variables: Arr::undot($data['variables'] ?: []),
)
);

Notification::make()
->title('Test email sent correctly')
->icon('heroicon-o-envelope')
->success()
->send();
}
}
69 changes: 69 additions & 0 deletions tests/Unit/TemplateManager/ExtractVariableNamesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

use MailCarrier\Helpers\TemplateManager;
use MailCarrier\Models\Layout;
use MailCarrier\Models\Template;

it('returns empty array if template has no variables', function () {
$template = new Template([
'content' => 'Hello',
]);

$output = TemplateManager::make($template)->extractVariableNames();

expect($output)->toBe([]);
});

it('returns all the variables of the template', function () {
$template = new Template([
'content' => <<<'TWIG'
Hello {{ name }},
<a href="{{ ctaUrl }}">
Sign in
</a>
{% if isPremium|default(false) %}
{{ tierLevel|title }}
{% endif %}
TWIG,
]);

$output = TemplateManager::make($template)->extractVariableNames();

expect($output)->toBe(['name', 'ctaUrl', 'isPremium', 'tierLevel']);
});

it('returns all the variables of the template along with its layout', function () {
$template = new Template([
'content' => <<<'TWIG'
Hello {{ name }},
<a href="{{ ctaUrl }}">
Sign in
</a>
{% if isPremium|default(false) %}
{{ tierLevel|title }}
{% endif %}
TWIG,
]);

$template->setRelation('layout', new Layout([
'content' => <<<'TWIG'
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>{{ headline }}</h1>
{% block content %}{% endblock %}
</body>
</html>
TWIG,
]));

$output = TemplateManager::make($template)->extractVariableNames();

expect($output)->toBe(['headline', 'name', 'ctaUrl', 'isPremium', 'tierLevel']);
});

0 comments on commit 7b54121

Please sign in to comment.