diff --git a/app/Filament/App/Resources/MailchimpCampaignResource/Pages/ViewABTestResults.php b/app/Filament/App/Resources/MailchimpCampaignResource/Pages/ViewABTestResults.php index 34adc5d..31a27fa 100644 --- a/app/Filament/App/Resources/MailchimpCampaignResource/Pages/ViewABTestResults.php +++ b/app/Filament/App/Resources/MailchimpCampaignResource/Pages/ViewABTestResults.php @@ -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) . '%', + ], ]); } diff --git a/app/Filament/App/Resources/MailchimpCampaignResource/Pages/ViewCampaignPerformance.php b/app/Filament/App/Resources/MailchimpCampaignResource/Pages/ViewCampaignPerformance.php new file mode 100644 index 0000000..c0b7c23 --- /dev/null +++ b/app/Filament/App/Resources/MailchimpCampaignResource/Pages/ViewCampaignPerformance.php @@ -0,0 +1,65 @@ +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) . '%', + ], + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 330b73a..958c34b 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -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')); + } } \ No newline at end of file diff --git a/app/Services/MailChimpService.php b/app/Services/MailChimpService.php index b466ffb..cac54ca 100644 --- a/app/Services/MailChimpService.php +++ b/app/Services/MailChimpService.php @@ -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) @@ -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}"); + } } \ No newline at end of file diff --git a/tests/Feature/CampaignPerformanceReportTest.php b/tests/Feature/CampaignPerformanceReportTest.php new file mode 100644 index 0000000..a7f4213 --- /dev/null +++ b/tests/Feature/CampaignPerformanceReportTest.php @@ -0,0 +1,94 @@ +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(); + } +} \ No newline at end of file diff --git a/tests/Feature/EmailTrackingIntegrationTest.php b/tests/Feature/EmailTrackingIntegrationTest.php index 2b48281..3f56b0c 100644 --- a/tests/Feature/EmailTrackingIntegrationTest.php +++ b/tests/Feature/EmailTrackingIntegrationTest.php @@ -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; @@ -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() @@ -66,6 +70,54 @@ public function testEmailTrackingIntegration() $response->assertSee('recipient@example.com'); } + 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); diff --git a/tests/Unit/EmailTrackingTest.php b/tests/Unit/EmailTrackingTest.php index d3482fe..902c95b 100644 --- a/tests/Unit/EmailTrackingTest.php +++ b/tests/Unit/EmailTrackingTest.php @@ -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() @@ -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);