Feature: Global Timeline (#2265)

* extended query filter to accept nested tables

* decoupled timeline api from recipe slug

* modified frontend to use simplified events api

* fixed nested loop index ghosting

* updated existing tests

* gave mypy a snack

* added tests for nested queries

* fixed "last made" render error

* decoupled recipe timeline from dialog

* removed unused props

* tweaked recipe get_all to accept ids

* created group global timeline
added new timeline page to sidebar
reformatted the recipe timeline
added vertical option to recipe card mobile

* extracted timeline item into its own component

* fixed apploader centering

* added paginated scrolling to recipe timeline

* added sort direction config
fixed infinite scroll on dialog
fixed hasMore var not resetting during instantiation

* added sort direction to user preferences

* updated API docs with new query filter feature

* better error tracing

* fix for recipe not found response

* simplified recipe crud route for slug/id
added test for fetching by slug/id

* made query filter UUID validation clearer

* moved timeline menu option below shopping lists

---------

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
Michael Genson
2023-04-25 12:46:00 -05:00
committed by GitHub
parent 0e397b34fd
commit fe17922bb8
28 changed files with 871 additions and 506 deletions

View File

@@ -3,7 +3,7 @@ import shutil
from datetime import datetime
from pathlib import Path
from shutil import copytree, rmtree
from uuid import uuid4
from uuid import UUID, uuid4
from zipfile import ZipFile
from fastapi import UploadFile
@@ -42,8 +42,8 @@ class RecipeService(BaseService):
self.group = group
super().__init__()
def _get_recipe(self, slug: str) -> Recipe:
recipe = self.repos.recipes.by_group(self.group.id).get_one(slug)
def _get_recipe(self, data: str | UUID, key: str | None = None) -> Recipe:
recipe = self.repos.recipes.by_group(self.group.id).get_one(data, key)
if recipe is None:
raise exceptions.NoEntryFound("Recipe not found.")
return recipe
@@ -107,6 +107,19 @@ class RecipeService(BaseService):
return Recipe(**additional_attrs)
def get_one_by_slug_or_id(self, slug_or_id: str | UUID) -> Recipe | None:
if isinstance(slug_or_id, str):
try:
slug_or_id = UUID(slug_or_id)
except ValueError:
pass
if isinstance(slug_or_id, UUID):
return self._get_recipe(slug_or_id, "id")
else:
return self._get_recipe(slug_or_id, "slug")
def create_one(self, create_data: Recipe | CreateRecipe) -> Recipe:
if create_data.name is None:
create_data.name = "New Recipe"

View File

@@ -6,10 +6,7 @@ from mealie.db.db_setup import session_context
from mealie.repos.all_repositories import get_repositories
from mealie.schema.meal_plan.new_meal import PlanEntryType
from mealie.schema.recipe.recipe import RecipeSummary
from mealie.schema.recipe.recipe_timeline_events import (
RecipeTimelineEventCreate,
TimelineEventType,
)
from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventCreate, TimelineEventType
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.user.user import DEFAULT_INTEGRATION_ID
from mealie.services.event_bus_service.event_bus_service import EventBusService