From 1b9ff454fbe6fb4a8a14960c353b441489cab016 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Wed, 30 Nov 2022 23:59:30 -0600 Subject: [PATCH] feat: additional recipe sort behavior (#1858) * changed default sort direction for certain attrs * added workaround for filtering out null datetimes * filtered out null-valued results for certain sorts * removed unecessary parse * used minyear instead of 1900 --- .../Domain/Recipe/RecipeCardSection.vue | 30 ++++++++++++------- frontend/composables/recipes/use-recipes.ts | 4 ++- frontend/composables/use-users/preferences.ts | 2 ++ mealie/schema/response/query_filter.py | 20 +++++++++---- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/frontend/components/Domain/Recipe/RecipeCardSection.vue b/frontend/components/Domain/Recipe/RecipeCardSection.vue index 08aa7198..58fb1c04 100644 --- a/frontend/components/Domain/Recipe/RecipeCardSection.vue +++ b/frontend/components/Domain/Recipe/RecipeCardSection.vue @@ -243,7 +243,10 @@ export default defineComponent({ cookbook.value, category.value, tag.value, - tool.value + tool.value, + + // filter out recipes that have a null value for the property we're sorting by + preferences.value.filterNull && preferences.value.orderBy ? `${preferences.value.orderBy} <> null` : null ); // since we doubled the first call, we also need to advance the page @@ -270,7 +273,10 @@ export default defineComponent({ cookbook.value, category.value, tag.value, - tool.value + tool.value, + + // filter out recipes that have a null value for the property we're sorting by + preferences.value.filterNull && preferences.value.orderBy ? `${preferences.value.orderBy} <> null` : null ); if (!newRecipes.length) { hasMore.value = false; @@ -287,10 +293,11 @@ export default defineComponent({ return; } - function setter(orderBy: string, ascIcon: string, descIcon: string) { + function setter(orderBy: string, ascIcon: string, descIcon: string, defaultOrderDirection = "asc", filterNull = false) { if (preferences.value.orderBy !== orderBy) { preferences.value.orderBy = orderBy; - preferences.value.orderDirection = "asc"; + preferences.value.orderDirection = defaultOrderDirection; + preferences.value.filterNull = filterNull; } else { preferences.value.orderDirection = preferences.value.orderDirection === "asc" ? "desc" : "asc"; } @@ -299,19 +306,19 @@ export default defineComponent({ switch (sortType) { case EVENTS.az: - setter("name", $globals.icons.sortAlphabeticalAscending, $globals.icons.sortAlphabeticalDescending); + setter("name", $globals.icons.sortAlphabeticalAscending, $globals.icons.sortAlphabeticalDescending, "asc", false); break; case EVENTS.rating: - setter("rating", $globals.icons.sortAscending, $globals.icons.sortDescending); + setter("rating", $globals.icons.sortAscending, $globals.icons.sortDescending, "desc", true); break; case EVENTS.created: - setter("created_at", $globals.icons.sortCalendarAscending, $globals.icons.sortCalendarDescending); + setter("created_at", $globals.icons.sortCalendarAscending, $globals.icons.sortCalendarDescending, "desc", false); break; case EVENTS.updated: - setter("update_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending); + setter("update_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending, "desc", false); break; case EVENTS.lastMade: - setter("last_made", $globals.icons.sortCalendarAscending, $globals.icons.sortCalendarDescending); + setter("last_made", $globals.icons.sortCalendarAscending, $globals.icons.sortCalendarDescending, "desc", true); break; default: console.log("Unknown Event", sortType); @@ -335,7 +342,10 @@ export default defineComponent({ cookbook.value, category.value, tag.value, - tool.value + tool.value, + + // filter out recipes that have a null value for the property we're sorting by + preferences.value.filterNull && preferences.value.orderBy ? `${preferences.value.orderBy} <> null` : null ); context.emit(REPLACE_RECIPES_EVENT, newRecipes); diff --git a/frontend/composables/recipes/use-recipes.ts b/frontend/composables/recipes/use-recipes.ts index b172f4d4..476adca0 100644 --- a/frontend/composables/recipes/use-recipes.ts +++ b/frontend/composables/recipes/use-recipes.ts @@ -19,7 +19,8 @@ export const useLazyRecipes = function () { cookbook: string | null = null, category: string | null = null, tag: string | null = null, - tool: string | null = null + tool: string | null = null, + queryFilter: string | null = null, ) { const { data } = await api.recipes.getAll(page, perPage, { orderBy, @@ -28,6 +29,7 @@ export const useLazyRecipes = function () { categories: category, tags: tag, tools: tool, + queryFilter, }); return data ? data.items : []; } diff --git a/frontend/composables/use-users/preferences.ts b/frontend/composables/use-users/preferences.ts index 0059977a..5e85a934 100644 --- a/frontend/composables/use-users/preferences.ts +++ b/frontend/composables/use-users/preferences.ts @@ -4,6 +4,7 @@ import { useLocalStorage } from "@vueuse/core"; export interface UserRecipePreferences { orderBy: string; orderDirection: string; + filterNull: boolean; sortIcon: string; useMobileCards: boolean; } @@ -16,6 +17,7 @@ export function useUserSortPreferences(): Ref { { orderBy: "name", orderDirection: "asc", + filterNull: false, sortIcon: $globals.icons.sortAlphabeticalAscending, useMobileCards: false, }, diff --git a/mealie/schema/response/query_filter.py b/mealie/schema/response/query_filter.py index ee6cf5d9..3ed23811 100644 --- a/mealie/schema/response/query_filter.py +++ b/mealie/schema/response/query_filter.py @@ -1,5 +1,6 @@ from __future__ import annotations +import datetime import re from enum import Enum from typing import Any, TypeVar, cast @@ -96,13 +97,20 @@ class QueryFilter: value: Any = component.value if isinstance(attr.type, (sqltypes.Date, sqltypes.DateTime)): - try: - value = date_parser.parse(component.value) + # TODO: add support for IS NULL and IS NOT NULL + # in the meantime, this will work for the specific usecase of non-null dates/datetimes + if value in ["none", "null"] and component.relational_operator == RelationalOperator.NOTEQ: + component.relational_operator = RelationalOperator.GTE + value = datetime.datetime(datetime.MINYEAR, 1, 1) - except ParserError as e: - raise ValueError( - f"invalid query string: unknown date or datetime format '{component.value}'" - ) from e + else: + try: + value = date_parser.parse(component.value) + + except ParserError as e: + raise ValueError( + f"invalid query string: unknown date or datetime format '{component.value}'" + ) from e if isinstance(attr.type, sqltypes.Boolean): try: