Skip to content

Commit

Permalink
Merge pull request #212 from liberu-crm/sweep/Implement-Campaign-Perf…
Browse files Browse the repository at this point in the history
…ormance-Reporting-and-Email-Tracking

Implement Campaign Performance Reporting and Email Tracking
  • Loading branch information
curtisdelicata authored Oct 16, 2024
2 parents 60a8e05 + bbda70a commit d7ee45d
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,21 @@ public function table(Table $table): Table
'version_a' => $results['opens_a'],
'version_b' => $results['opens_b'],
],
[
'metric' => 'Open Rate',
'version_a' => number_format($results['opens_a'] / $results['emails_sent'] * 100, 2) . '%',
'version_b' => number_format($results['opens_b'] / $results['emails_sent'] * 100, 2) . '%',
],
[
'metric' => 'Clicks',
'version_a' => $results['clicks_a'],
'version_b' => $results['clicks_b'],
],
[
'metric' => 'Click Rate',
'version_a' => number_format($results['clicks_a'] / $results['opens_a'] * 100, 2) . '%',
'version_b' => number_format($results['clicks_b'] / $results['opens_b'] * 100, 2) . '%',
],
]);
}

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

namespace App\Filament\App\Resources\MailchimpCampaignResource\Pages;

use App\Filament\App\Resources\MailchimpCampaignResource;
use App\Services\MailChimpService;
use Filament\Resources\Pages\Page;
use Filament\Tables;
use Filament\Tables\Table;

class ViewCampaignPerformance extends Page
{
protected static string $resource = MailchimpCampaignResource::class;

protected static string $view = 'filament.app.resources.mailchimp-campaign-resource.pages.view-campaign-performance';

public function getTitle(): string
{
return "Campaign Performance: {$this->record->name}";
}

public function table(Table $table): Table
{
$mailChimpService = app(MailChimpService::class);
$report = $mailChimpService->getCampaignReport($this->record->id);

return $table
->columns([
Tables\Columns\TextColumn::make('metric')
->label('Metric'),
Tables\Columns\TextColumn::make('value')
->label('Value'),
])
->contents([
[
'metric' => 'Emails Sent',
'value' => $report['emails_sent'],
],
[
'metric' => 'Unique Opens',
'value' => $report['unique_opens'],
],
[
'metric' => 'Open Rate',
'value' => number_format($report['open_rate'] * 100, 2) . '%',
],
[
'metric' => 'Clicks',
'value' => $report['clicks'],
],
[
'metric' => 'Click Rate',
'value' => number_format($report['click_rate'] * 100, 2) . '%',
],
[
'metric' => 'Unsubscribes',
'value' => $report['unsubscribes'],
],
[
'metric' => 'Bounce Rate',
'value' => number_format($report['bounce_rate'] * 100, 2) . '%',
],
]);
}
}
7 changes: 7 additions & 0 deletions app/Http/Controllers/ReportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ public function generateABTestResultsReport(Request $request)
$data = $this->mailChimpService->getABTestResults($campaignId);
return view('reports.ab-test-results', compact('data'));
}

public function generateEmailCampaignReport(Request $request)
{
$campaignId = $request->input('campaign_id');
$data = $this->mailChimpService->getCampaignReport($campaignId);
return view('reports.email-campaign-performance', compact('data'));
}
}
31 changes: 30 additions & 1 deletion app/Services/MailChimpService.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,18 @@ public function getCampaigns()

public function getCampaignReport($campaign_id)
{
return $this->client->reports->getCampaignReport($campaign_id);
$report = $this->client->reports->getCampaignReport($campaign_id);

return [
'campaign_id' => $campaign_id,
'emails_sent' => $report->emails_sent,
'unique_opens' => $report->opens->unique_opens,
'open_rate' => $report->opens->open_rate,
'clicks' => $report->clicks->clicks_total,
'click_rate' => $report->clicks->click_rate,
'unsubscribes' => $report->unsubscribed,
'bounce_rate' => $report->bounces->hard_bounces + $report->bounces->soft_bounces,
];
}

public function getABTestResults($campaign_id)
Expand All @@ -150,4 +161,22 @@ public function getABTestResults($campaign_id)
'winning_metric_value' => $abResults->winning_metric_value,
];
}

public function trackEmailOpen($campaign_id, $email_id)
{
// Implement tracking for email opens
// This method would typically be called when an email is opened
// You might need to set up a webhook or use a tracking pixel for this
// For now, we'll just log the open event
\Log::info("Email opened: Campaign ID {$campaign_id}, Email ID {$email_id}");
}

public function trackEmailClick($campaign_id, $email_id, $url)
{
// Implement tracking for email clicks
// This method would typically be called when a link in an email is clicked
// You might need to set up a webhook or use tracked links for this
// For now, we'll just log the click event
\Log::info("Email link clicked: Campaign ID {$campaign_id}, Email ID {$email_id}, URL: {$url}");
}
}
94 changes: 94 additions & 0 deletions tests/Feature/CampaignPerformanceReportTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Services\MailChimpService;
use Mockery;
use Illuminate\Foundation\Testing\RefreshDatabase;

class CampaignPerformanceReportTest extends TestCase
{
use RefreshDatabase;

protected $mailChimpService;

protected function setUp(): void
{
parent::setUp();
$this->mailChimpService = Mockery::mock(MailChimpService::class)->makePartial();
$this->app->instance(MailChimpService::class, $this->mailChimpService);
}

public function testGenerateCampaignPerformanceReport()
{
$mockReport = [
'campaign_id' => 'campaign_123',
'emails_sent' => 1000,
'unique_opens' => 500,
'open_rate' => 0.5,
'clicks' => 200,
'click_rate' => 0.2,
'unsubscribes' => 10,
'bounce_rate' => 0.02,
];

$this->mailChimpService->shouldReceive('getCampaignReport')
->once()
->with('campaign_123')
->andReturn($mockReport);

$response = $this->get('/reports/campaign-performance/campaign_123');

$response->assertStatus(200);
$response->assertViewIs('reports.email-campaign-performance');
$response->assertViewHas('data', $mockReport);
$response->assertSee('Campaign Performance: campaign_123');
$response->assertSee('Emails Sent: 1,000');
$response->assertSee('Open Rate: 50.00%');
$response->assertSee('Click Rate: 20.00%');
}

public function testGenerateABTestResultsReport()
{
$mockResults = [
'campaign_id' => 'campaign_123',
'subject_a' => 'Subject A',
'subject_b' => 'Subject B',
'opens_a' => 300,
'opens_b' => 200,
'clicks_a' => 150,
'clicks_b' => 100,
'winner' => 'a',
'winning_metric' => 'opens',
'winning_metric_value' => 300,
];

$this->mailChimpService->shouldReceive('getABTestResults')
->once()
->with('campaign_123')
->andReturn($mockResults);

$response = $this->get('/reports/ab-test-results/campaign_123');

$response->assertStatus(200);
$response->assertViewIs('reports.ab-test-results');
$response->assertViewHas('data', $mockResults);
$response->assertSee('A/B Test Results for Campaign: campaign_123');
$response->assertSee('Subject A: Subject A');

$response->assertSee('Subject B: Subject B');
$response->assertSee('Opens A: 300');
$response->assertSee('Opens B: 200');
$response->assertSee('Clicks A: 150');
$response->assertSee('Clicks B: 100');
$response->assertSee('Winner: Version A');
$response->assertSee('Winning Metric: Opens');
}

protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
}
52 changes: 52 additions & 0 deletions tests/Feature/EmailTrackingIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Tests\TestCase;
use App\Services\GmailService;
use App\Services\MailChimpService;
use App\Models\Email;
use Mockery;
use Google_Service_Gmail_Message;
Expand All @@ -16,12 +17,15 @@ class EmailTrackingIntegrationTest extends TestCase
use RefreshDatabase;

protected $gmailService;
protected $mailChimpService;

protected function setUp(): void
{
parent::setUp();
$this->gmailService = Mockery::mock(GmailService::class)->makePartial();
$this->mailChimpService = Mockery::mock(MailChimpService::class)->makePartial();
$this->app->instance(GmailService::class, $this->gmailService);
$this->app->instance(MailChimpService::class, $this->mailChimpService);
}

public function testEmailTrackingIntegration()
Expand Down Expand Up @@ -66,6 +70,54 @@ public function testEmailTrackingIntegration()
$response->assertSee('[email protected]');
}

public function testEmailOpenTracking()
{
$this->mailChimpService->shouldReceive('trackEmailOpen')
->once()
->with('campaign_123', 'email_456')
->andReturn(true);

$response = $this->get('/track-open/campaign_123/email_456');
$response->assertStatus(200);
}

public function testEmailClickTracking()
{
$this->mailChimpService->shouldReceive('trackEmailClick')
->once()
->with('campaign_123', 'email_456', 'https://example.com')
->andReturn(true);

$response = $this->get('/track-click/campaign_123/email_456?url=https://example.com');
$response->assertStatus(200);
}

public function testCampaignPerformanceReport()
{
$mockReport = [
'campaign_id' => 'campaign_123',
'emails_sent' => 1000,
'unique_opens' => 500,
'open_rate' => 0.5,
'clicks' => 200,
'click_rate' => 0.2,
'unsubscribes' => 10,
'bounce_rate' => 0.02,
];

$this->mailChimpService->shouldReceive('getCampaignReport')
->once()
->with('campaign_123')
->andReturn($mockReport);

$response = $this->get('/reports/campaign-performance/campaign_123');
$response->assertStatus(200);
$response->assertSee('Campaign Performance: campaign_123');
$response->assertSee('Emails Sent: 1000');
$response->assertSee('Open Rate: 50%');
$response->assertSee('Click Rate: 20%');
}

protected function createMockMessage()
{
$message = Mockery::mock(Google_Service_Gmail_Message::class);
Expand Down
45 changes: 45 additions & 0 deletions tests/Unit/EmailTrackingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@

use Tests\TestCase;
use App\Services\GmailService;
use App\Services\MailChimpService;
use App\Models\Email;
use Mockery;
use Google_Service_Gmail_Message;
use Google_Service_Gmail_MessagePart;
use Google_Service_Gmail_MessagePartHeader;
use Illuminate\Support\Facades\Log;

class EmailTrackingTest extends TestCase
{
protected $gmailService;
protected $mailChimpService;

protected function setUp(): void
{
parent::setUp();
$this->gmailService = Mockery::mock(GmailService::class)->makePartial();
$this->mailChimpService = Mockery::mock(MailChimpService::class)->makePartial();
}

public function testTrackEmail()
Expand Down Expand Up @@ -50,6 +54,47 @@ public function testTrackSentEmail()
]);
}

public function testTrackEmailOpen()
{
Log::shouldReceive('info')
->once()
->with("Email opened: Campaign ID campaign_123, Email ID email_456");

$this->mailChimpService->trackEmailOpen('campaign_123', 'email_456');
}

public function testTrackEmailClick()
{
Log::shouldReceive('info')
->once()
->with("Email link clicked: Campaign ID campaign_123, Email ID email_456, URL: https://example.com");

$this->mailChimpService->trackEmailClick('campaign_123', 'email_456', 'https://example.com');
}

public function testGetCampaignReport()
{
$mockReport = [
'campaign_id' => 'campaign_123',
'emails_sent' => 1000,
'unique_opens' => 500,
'open_rate' => 0.5,
'clicks' => 200,
'click_rate' => 0.2,
'unsubscribes' => 10,
'bounce_rate' => 0.02,
];

$this->mailChimpService->shouldReceive('getCampaignReport')
->once()
->with('campaign_123')
->andReturn($mockReport);

$report = $this->mailChimpService->getCampaignReport('campaign_123');

$this->assertEquals($mockReport, $report);
}

protected function createMockMessage()
{
$message = Mockery::mock(Google_Service_Gmail_Message::class);
Expand Down

0 comments on commit d7ee45d

Please sign in to comment.