diff --git a/app/Console/Commands/UpdateArticleViewCounts.php b/app/Console/Commands/UpdateArticleViewCounts.php index c75aa59a2..c7771f9f4 100644 --- a/app/Console/Commands/UpdateArticleViewCounts.php +++ b/app/Console/Commands/UpdateArticleViewCounts.php @@ -43,6 +43,26 @@ public function handle() protected function getViewCountFor(Article $article): ?int { + $viewCount = $this->getViewCountForUrl(route('articles.show', $article->slug)); + $canonicalViewCount = ($url = $article->originalUrl()) ? $this->getViewCountForUrl($url) : 0; + + return ($total = $viewCount + $canonicalViewCount) > 0 ? $total : null; + } + + protected function getViewCountForUrl(string $url): int + { + if (! $url = parse_url($url)) { + return 0; + } + + $scheme = $url['scheme'] ?? null; + $host = $url['host'] ?? null; + $path = $url['path'] ?? null; + + if (! $scheme || ! $host || ! $path) { + return 0; + } + $response = Http::withToken($this->token) ->get('https://api.usefathom.com/v1/aggregations', [ 'date_from' => '2021-03-01 00:00:00', // Fathom data aggregations not accurate prior to this date. @@ -54,13 +74,18 @@ protected function getViewCountFor(Article $article): ?int [ 'property' => 'pathname', 'operator' => 'is', - 'value' => "/articles/{$article->slug()}", + 'value' => $path, + ], + [ + 'property' => 'hostname', + 'operator' => 'is', + 'value' => "{$scheme}://{$host}", ], ]), ]); if ($response->failed()) { - return null; + return 0; } return $response->json('0.pageviews'); diff --git a/resources/views/layouts/_fathom.blade.php b/resources/views/layouts/_fathom.blade.php index ab4715acd..09e7850ca 100644 --- a/resources/views/layouts/_fathom.blade.php +++ b/resources/views/layouts/_fathom.blade.php @@ -1,5 +1,5 @@ @production - + -@endproduction +@endproduction \ No newline at end of file diff --git a/tests/Feature/CanonicalUrlTest.php b/tests/Feature/CanonicalUrlTest.php index 9f2bc011e..bc9dd0f10 100644 --- a/tests/Feature/CanonicalUrlTest.php +++ b/tests/Feature/CanonicalUrlTest.php @@ -1,13 +1,22 @@ 'production'); +} + +afterEach(fn () => App::detectEnvironment(fn () => 'testing')); + test('pages without a canonical url explicitly set fall back to the current url', function () { $this->get('/register') ->see(''); @@ -51,3 +60,19 @@ $this->get('?utm_source=twitter&utm_medium=social&utm_term=abc123') ->see(''); }); + +test('canonical tracking is turned off when using external url', function () { + Article::factory()->create(['slug' => 'my-first-article', 'submitted_at' => now(), 'approved_at' => now(), 'original_url' => 'https://example.com/external-path']); + + $this->get('/articles/my-first-article') + ->see('data-canonical="false"'); +})->inProduction(); + +test('canonical tracking is turned on when using external url', function () { + App::detectEnvironment(fn () => 'production'); + + Article::factory()->create(['slug' => 'my-first-article', 'submitted_at' => now(), 'approved_at' => now()]); + + $this->get('/articles/my-first-article') + ->dontSee('data-canonical="false"'); +})->inProduction(); diff --git a/tests/Integration/Commands/UpdateArticleViewCountsTest.php b/tests/Integration/Commands/UpdateArticleViewCountsTest.php index ad6c0e2c1..79c79e2be 100644 --- a/tests/Integration/Commands/UpdateArticleViewCountsTest.php +++ b/tests/Integration/Commands/UpdateArticleViewCountsTest.php @@ -28,6 +28,46 @@ expect($article->fresh()->view_count)->toBe(1234); }); +test('article view counts can be merged with original url', function () { + Http::fake(function () { + return Http::response([[ + 'pageviews' => 1234, + ]]); + }); + + $article = Article::factory()->create([ + 'title' => 'My First Article', + 'slug' => 'my-first-article', + 'original_url' => 'https://example.com/my-first-article', + 'submitted_at' => now(), + 'approved_at' => now(), + ]); + + (new UpdateArticleViewCounts)->handle(); + + expect($article->fresh()->view_count)->toBe(2468); +}); + +test('article view counts are not merged when url is invalid', function () { + Http::fake(function () { + return Http::response([[ + 'pageviews' => 1234, + ]]); + }); + + $article = Article::factory()->create([ + 'title' => 'My First Article', + 'slug' => 'my-first-article', + 'original_url' => 'erhwerhwerh', + 'submitted_at' => now(), + 'approved_at' => now(), + ]); + + (new UpdateArticleViewCounts)->handle(); + + expect($article->fresh()->view_count)->toBe(1234); +}); + test('article view counts are not updated if API call fails', function () { Http::fake(function () { return Http::response('Uh oh', 500);