Improved page loading time of /recipes and /mealplan when having a big meal plan (closes #695)

This commit is contained in:
Bernd Bestel 2021-07-10 22:56:39 +02:00
parent 6660e1ff73
commit 7ee15946c7
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
5 changed files with 97 additions and 24 deletions

View File

@ -57,6 +57,7 @@
- Recipe printing improvements (thanks @Ape)
- Calories are now always displayed per single serving (on the recipe and meal plan page)
- The note of an ingredient will now also be added to the corresponding shopping list item when using "Put missing products on the shopping list"
- Fixed that the recipe page was slow when there were a lot meal plan recipe entries
- Fixed that "Only check if any amount is in stock" (recipe ingredient option) didn't work for stock amounts < 1
- Fixed that when adding missing items to the shopping list, on the popup deselected items got also added
- Fixed that the amount of self produced products with tare weight handling enabled was wrong ("Produces product" recipe option)
@ -64,6 +65,7 @@
- Fixed that the ingredient amount was wrong when using a to the product indirectly related quantity unit
### Meal plan fixes
- Improved the meal plan page loading time (drastically when having a big history of meal plan entries)
- Fixed that stock fulfillment checking used the desired servings of the recipe (those selected on the recipes page, not them from the meal plan entry)
### Chores fixes

View File

@ -8,11 +8,22 @@ class RecipesController extends BaseController
{
public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
// Load +- 8 days to always include week boundaries
if (isset($request->getQueryParams()['week']) && IsIsoDate($request->getQueryParams()['week']))
{
$week = $request->getQueryParams()['week'];
$mealPlanWhereTimespan = "day BETWEEN DATE('$week', '-8 day') AND DATE('$week', '+8 day')";
}
else
{
$mealPlanWhereTimespan = "day BETWEEN DATE(DATE('now', 'localtime'), '-8 day') AND DATE(DATE('now', 'localtime'), '+8 day')";
}
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
$events = [];
foreach ($this->getDatabase()->meal_plan() as $mealPlanEntry)
foreach ($this->getDatabase()->meal_plan()->where($mealPlanWhereTimespan) as $mealPlanEntry)
{
$recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']);
$title = '';
@ -23,7 +34,6 @@ class RecipesController extends BaseController
}
$productDetails = null;
if ($mealPlanEntry['product_id'] !== null)
{
$productDetails = $this->getStockService()->GetProductDetails($mealPlanEntry['product_id']);
@ -44,8 +54,8 @@ class RecipesController extends BaseController
return $this->renderPage($response, 'mealplan', [
'fullcalendarEventSources' => $events,
'recipes' => $recipes,
'internalRecipes' => $this->getDatabase()->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(),
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(),
'internalRecipes' => $this->getDatabase()->recipes()->where("id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)")->fetchAll(),
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved2("recipe_id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)"),
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
@ -55,7 +65,7 @@ class RecipesController extends BaseController
public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE');
$recipesResolved = $this->getRecipesService()->GetRecipesResolved();
$recipesResolved = $this->getRecipesService()->GetRecipesResolved('recipe_id > 0');
$selectedRecipe = null;

43
migrations/0141.sql Normal file
View File

@ -0,0 +1,43 @@
CREATE VIEW meal_plan_internal_recipe_relation
AS
-- Relation between a meal plan (day) and the corresponding internal recipe(s)
SELECT mp.day, r.id AS recipe_id
FROM meal_plan mp
JOIN recipes r
ON r.name = CAST(mp.day AS TEXT)
AND r.type = 'mealplan-day'
UNION
SELECT mp.day, r.id AS recipe_id
FROM meal_plan mp
JOIN recipes r
ON r.name = STRFTIME('%Y-%W', mp.day)
AND r.type = 'mealplan-week'
UNION
SELECT mp.day, r.id AS recipe_id
FROM meal_plan mp
JOIN recipes r
ON r.name = CAST(mp.day AS TEXT) || '#' || CAST(mp.id AS TEXT)
AND r.type = 'mealplan-shadow';
CREATE VIEW recipes_resolved2
AS
-- The same as recipes_resolved but without the column "missing_products_count" to improve performance when this is not needed
SELECT
1 AS id, -- Dummy, LessQL needs an id column
r.id AS recipe_id,
IFNULL(MIN(rpr.need_fulfilled), 1) AS need_fulfilled,
IFNULL(MIN(rpr.need_fulfilled_with_shopping_list), 1) AS need_fulfilled_with_shopping_list,
IFNULL(SUM(rpr.costs), 0) AS costs,
IFNULL(SUM(rpr.calories), 0) AS calories
FROM recipes r
LEFT JOIN recipes_pos_resolved rpr
ON r.id = rpr.recipe_id
GROUP BY r.id;

View File

@ -25,17 +25,9 @@ var calendar = $("#calendar").fullCalendar({
"defaultView": ($(window).width() < 768) ? "basicDay" : "basicWeek",
"firstDay": firstDay,
"height": "auto",
"defaultDate": GetUriParam("week"),
"viewRender": function(view)
{
if (firstRender)
{
firstRender = false
}
else
{
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
}
$(".fc-day-header").prepend('\
<div class="btn-group mr-2 my-1"> \
<button type="button" class="btn btn-outline-dark btn-xs add-recipe-button""><i class="fas fa-plus"></i></a></button> \
@ -273,15 +265,21 @@ var calendar = $("#calendar").fullCalendar({
},
"eventAfterAllRender": function(view)
{
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
if (firstRender)
{
firstRender = false
}
else
{
window.location.reload();
}
RefreshLocaleNumberDisplay();
LoadImagesLazy();
$('[data-toggle="tooltip"]').tooltip();
if (GetUriParam("week") !== undefined)
{
$("#calendar").fullCalendar("gotoDate", GetUriParam("week"));
}
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK)
{
$(".recipe-order-missing-button").addClass("d-none");
@ -498,7 +496,7 @@ $('#save-add-product-button').on('click', function(e)
delete jsonData.display_amount;
jsonData.product_amount = jsonData.amount;
delete jsonData.amount;
jsonData.product_qu_id = jsonData.qu_id;
jsonData.product_qu_id = $("#qu_id").val();;
delete jsonData.qu_id;
if (Grocy.IsMealPlanEntryEditAction)
@ -803,10 +801,10 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
function(productDetails)
{
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id);
$('#display_amount').val(1);
RefreshLocaleNumberInput();
$(".input-group-productamountpicker").trigger("change");
$('#display_amount').focus();
$('#display_amount').select();
$(".input-group-productamountpicker").trigger("change");

View File

@ -87,10 +87,30 @@ class RecipesService extends BaseService
return $this->getDataBaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
public function GetRecipesResolved(): Result
public function GetRecipesResolved($customWhere = null): Result
{
if ($customWhere == null)
{
return $this->getDatabase()->recipes_resolved();
}
else
{
return $this->getDatabase()->recipes_resolved()->where($customWhere);
}
}
// The same as GetRecipesResolved but without the column "missing_products_count" to improve performance when this is not needed
public function GetRecipesResolved2($customWhere = null): Result
{
if ($customWhere == null)
{
return $this->getDatabase()->recipes_resolved2();
}
else
{
return $this->getDatabase()->recipes_resolved2()->where($customWhere);
}
}
public function __construct()
{