Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/Http/Resources/Api/RecipeCollectionResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function toArray(Request $request): array
return [
'id' => $this->id,
'canonical_id' => $this->canonical_id,
'variant' => $this->variant,
'url' => config('app.url') . '/' . $locale . '-' . resolve('current.country')->code .
'/recipes/' . slugify($this->getTranslationWithAnyFallback('name', $locale)) . '-' . $this->id,
'name' => $this->getTranslationWithAnyFallback('name', $locale),
Expand Down
1 change: 1 addition & 0 deletions app/Http/Resources/Api/RecipeResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public function toArray(Request $request): array
return [
'id' => $this->id,
'canonical_id' => $this->canonical_id,
'variant' => $this->variant,
'url' => config('app.url') . '/' . $locale . '-' . resolve('current.country')->code .
'/recipes/' . slugify($this->getTranslationWithAnyFallback('name', $locale)) . '-' . $this->id,
'name' => $this->getTranslationWithAnyFallback('name', $locale),
Expand Down
1 change: 1 addition & 0 deletions app/Livewire/Portal/Docs/RecipesIndexDoc.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ protected function responseFields(): array
return [
['name' => 'id', 'type' => 'integer', 'description' => 'Recipe ID'],
['name' => 'canonical_id', 'type' => 'integer|null', 'description' => 'ID of the original recipe if this is a variant'],
['name' => 'variant', 'type' => 'boolean', 'description' => 'Whether this recipe is a variant of another recipe'],
['name' => 'url', 'type' => 'string', 'description' => 'URL to recipe on website'],
['name' => 'name', 'type' => 'string', 'description' => 'Recipe name (localized)'],
['name' => 'headline', 'type' => 'string', 'description' => 'Short description (localized)'],
Expand Down
1 change: 1 addition & 0 deletions app/Livewire/Portal/Docs/RecipesShowDoc.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ protected function responseFields(): array
return [
['name' => 'id', 'type' => 'integer', 'description' => 'Recipe ID'],
['name' => 'canonical_id', 'type' => 'integer|null', 'description' => 'ID of the original recipe if this is a variant'],
['name' => 'variant', 'type' => 'boolean', 'description' => 'Whether this recipe is a variant of another recipe'],
['name' => 'url', 'type' => 'string', 'description' => 'URL to recipe on website'],
['name' => 'name', 'type' => 'string', 'description' => 'Recipe name (localized)'],
['name' => 'headline', 'type' => 'string', 'description' => 'Short description (localized)'],
Expand Down
8 changes: 4 additions & 4 deletions app/Livewire/Portal/Stats/RecipeStats.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,14 @@ public function dataHealth(): array
}

/**
* Get canonical recipe statistics.
* Get variant recipe statistics.
*
* @return array{total_canonical: int, recipes_with_canonical: int, unique_canonical_parents: int, canonical_percentage: float}
* @return array{total_variants: int, unique_canonical_parents: int, variant_percentage: float}
*/
#[Computed]
public function canonicalStats(): array
public function variantStats(): array
{
return $this->statistics()->canonicalStats();
return $this->statistics()->variantStats();
}

public function render(): View
Expand Down
2 changes: 2 additions & 0 deletions app/Livewire/Web/Auth/ResetPassword.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password as PasswordRule;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Layout;

#[Layout('web::components.layouts.localized')]
class ResetPassword extends AbstractComponent
{
use WithLocalizedContextTrait;
Expand Down
2 changes: 1 addition & 1 deletion app/Livewire/Web/Recipes/RecipeIndex.php
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ public function recipes(): LengthAwarePaginator
->when($menuRecipeIds !== [], fn (Builder $query) => $query->whereIn('id', $menuRecipeIds))
->when($this->search !== '', fn (Builder $query): Builder => $this->applySearchFilter($query))
->when($this->filterHasPdf, fn (Builder $query) => $query->where('has_pdf', true))
->when(! $this->filterShowCanonical, fn (Builder $query) => $query->whereNull('canonical_id'))
->when(! $this->filterShowCanonical, fn (Builder $query) => $query->where('variant', false))
->when($this->excludedAllergenIds !== [], fn (Builder $query) => $query->whereDoesntHave(
'allergens',
fn (Builder $allergenQuery) => $allergenQuery->whereIn('allergens.id', $this->excludedAllergenIds)
Expand Down
2 changes: 1 addition & 1 deletion app/Livewire/Web/Recipes/RecipeRandom.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function randomRecipes(): Collection
return Recipe::where('country_id', $this->countryId)
->when($this->search !== '', fn (Builder $query): Builder => $this->applySearchFilter($query))
->when($this->filterHasPdf, fn (Builder $query) => $query->where('has_pdf', true))
->unless($this->filterShowCanonical, fn (Builder $query) => $query->whereNull('canonical_id'))
->unless($this->filterShowCanonical, fn (Builder $query) => $query->where('variant', false))
->when($this->excludedAllergenIds !== [], fn (Builder $query) => $query->whereDoesntHave(
'allergens',
fn (Builder $allergenQuery) => $allergenQuery->whereIn('allergens.id', $this->excludedAllergenIds)
Expand Down
12 changes: 2 additions & 10 deletions app/Models/Recipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,21 +246,13 @@ protected function hellofreshUrl(): Attribute
return Attribute::get(fn (): ?string => $this->buildHellofreshUrl());
}

/**
* Check if this recipe is a canonical recipe (has a parent).
*/
public function isCanonical(): bool
{
return $this->canonical_id !== null;
}

/**
* Build the HelloFresh URL.
*/
protected function buildHellofreshUrl(): ?string
{
// Canonical recipes often lead to 404 pages on HelloFresh
if ($this->isCanonical()) {
// Variant recipes often lead to 404 pages on HelloFresh
if ($this->variant) {
return null;
}

Expand Down
23 changes: 11 additions & 12 deletions app/Services/Portal/StatisticsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class StatisticsService
'portal_recipes_per_month',
'portal_avg_prep_times',
'portal_data_health',
'portal_canonical_stats',
'portal_variant_stats',
];

/**
Expand Down Expand Up @@ -72,7 +72,7 @@ public function warmCache(): void
$this->recipesPerMonth();
$this->avgPrepTimesByCountry();
$this->dataHealth();
$this->canonicalStats();
$this->variantStats();
}

/**
Expand Down Expand Up @@ -104,7 +104,7 @@ public function countryStats(): Collection
/** @var Collection<int, Country> */
return Cache::remember('portal_country_stats', $this->cacheTtl, static fn (): Collection => Country::where('active', true)
->withCount('menus')
->withCount(['recipes as variants_count' => fn (Builder $query) => $query->whereNotNull('canonical_id')])
->withCount(['recipes as variants_count' => fn (Builder $query) => $query->where('variant', true)])
->get());
}

Expand Down Expand Up @@ -316,25 +316,24 @@ public function dataHealth(): array
}

/**
* Get canonical recipe statistics.
* Get variant recipe statistics.
*
* @return array{total_canonical: int, recipes_with_canonical: int, unique_canonical_parents: int, canonical_percentage: float}
* @return array{total_variants: int, unique_canonical_parents: int, variant_percentage: float}
*/
public function canonicalStats(): array
public function variantStats(): array
{
/** @var array{total_canonical: int, recipes_with_canonical: int, unique_canonical_parents: int, canonical_percentage: float} */
return Cache::remember('portal_canonical_stats', $this->cacheTtl, static function (): array {
/** @var array{total_variants: int, unique_canonical_parents: int, variant_percentage: float} */
return Cache::remember('portal_variant_stats', $this->cacheTtl, static function (): array {
$total = Recipe::count();
$recipesWithCanonical = Recipe::whereNotNull('canonical_id')->count();
$totalVariants = Recipe::where('variant', true)->count();
$uniqueCanonicalParents = Recipe::whereNotNull('canonical_id')
->distinct('canonical_id')
->count('canonical_id');

return [
'total_canonical' => $recipesWithCanonical,
'recipes_with_canonical' => $recipesWithCanonical,
'total_variants' => $totalVariants,
'unique_canonical_parents' => $uniqueCanonicalParents,
'canonical_percentage' => $total > 0 ? round(($recipesWithCanonical / $total) * 100, 1) : 0.0,
'variant_percentage' => $total > 0 ? round(($totalVariants / $total) * 100, 1) : 0.0,
];
});
}
Expand Down
8 changes: 4 additions & 4 deletions resources/views/portal/livewire/stats/recipe-stats.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@
<flux:icon.git-branch class="size-6 text-indigo-600 dark:text-indigo-400" />
</div>
<div class="flex-1">
<flux:text class="text-sm text-zinc-500">Canonical Recipes</flux:text>
<flux:heading size="xl">{{ Number::format($this->canonicalStats['total_canonical']) }}</flux:heading>
<flux:text class="text-sm text-zinc-500">Variant Recipes</flux:text>
<flux:heading size="xl">{{ Number::format($this->variantStats['total_variants']) }}</flux:heading>
</div>
<div class="text-right">
<flux:text class="text-sm text-zinc-500">{{ $this->canonicalStats['canonical_percentage'] }}% of all recipes</flux:text>
<flux:text class="text-sm text-zinc-500">{{ Number::format($this->canonicalStats['unique_canonical_parents']) }} unique parents</flux:text>
<flux:text class="text-sm text-zinc-500">{{ $this->variantStats['variant_percentage'] }}% of all recipes</flux:text>
<flux:text class="text-sm text-zinc-500">{{ Number::format($this->variantStats['unique_canonical_parents']) }} unique parents</flux:text>
</div>
</div>
</flux:card>
Expand Down
11 changes: 0 additions & 11 deletions tests/Feature/Livewire/Recipes/RecipeIndexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Tests\Feature\Livewire\Recipes;

use App\Enums\IngredientMatchModeEnum;
use App\Enums\RecipeSortEnum;
use App\Enums\ViewModeEnum;
use App\Livewire\Web\Recipes\RecipeIndex;
use App\Models\Allergen;
Expand Down Expand Up @@ -324,16 +323,6 @@ public function it_persists_view_mode_to_session(): void
$this->assertSame(ViewModeEnum::List->value, session('view_mode'));
}

#[Test]
public function it_persists_sort_to_session(): void
{
Livewire::test(RecipeIndex::class)
->set('sortBy', RecipeSortEnum::OldestFirst->value);

$key = sprintf('recipe_filter_%d_sort', $this->country->id);
$this->assertSame(RecipeSortEnum::OldestFirst->value, session($key));
}

#[Test]
public function it_can_filter_by_menu(): void
{
Expand Down
1 change: 1 addition & 0 deletions tests/Unit/Jobs/ImportRecipeJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ protected function createValidRecipeData(): array
'cuisines' => [],
'utensils' => [],
'label' => null,
'canonical' => '',
'createdAt' => now()->toIso8601String(),
'updatedAt' => now()->toIso8601String(),
];
Expand Down