diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4207bc..e7672ce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changes - run local test browser in headed mode with higher resolution +- prepare reflex upgrade to 0.7.3 ### Deprecated diff --git a/mex/editor/components.py b/mex/editor/components.py index 414f5aa9..854ceb61 100644 --- a/mex/editor/components.py +++ b/mex/editor/components.py @@ -1,3 +1,5 @@ +from typing import Any + import reflex as rx from mex.editor.ingest.state import IngestState @@ -10,11 +12,11 @@ def render_title(title: EditorValue) -> rx.Component: """Render one title in a container with hidden overflow.""" return rx.box( render_value(title), - style={ - "fontWeight": "var(--font-weight-bold)", - "overflow": "hidden", - "whiteSpace": "nowrap", - }, + style=rx.Style( + fontWeight="var(--font-weight-bold)", + overflow="hidden", + whiteSpace="nowrap", + ), ) @@ -24,7 +26,10 @@ def render_additional_titles(titles: list[EditorValue]) -> rx.Component: titles, rx.hover_card.root( rx.hover_card.trigger( - rx.badge("+ additional titles", style=rx.Style(margin="auto 0")), + rx.badge( + "+ additional titles", + style=rx.Style(margin="auto 0"), + ), custom_attrs={"data-testid": "tooltip-additional-titles-trigger"}, ), rx.hover_card.content( @@ -42,14 +47,14 @@ def render_search_preview(values: list[EditorValue]) -> rx.Component: values, render_value, ), - style={ - "color": "var(--gray-12)", - "fontWeight": "var(--font-weight-light)", - "whiteSpace": "nowrap", - "overflow": "hidden", - "textOverflow": "ellipsis", - "maxWidth": "100%", - }, + style=rx.Style( + color="var(--gray-12)", + fontWeight="var(--font-weight-light)", + whiteSpace="nowrap", + overflow="hidden", + textOverflow="ellipsis", + maxWidth="100%", + ), ) @@ -62,7 +67,7 @@ def render_identifier(value: EditorValue) -> rx.Component: value.text, "Loading ...", ), - on_click=State.navigate(value.href), + on_click=State.navigate(value.href), # type: ignore[misc] high_contrast=True, role="link", class_name="truncate", @@ -85,7 +90,7 @@ def render_external_link(value: EditorValue) -> rx.Component: value.text, value.href, ), - href=f"{value.href}", + href=value.href, high_contrast=True, is_external=True, title=value.text, @@ -136,7 +141,7 @@ def render_badge(text: str | None) -> rx.Component: radius="large", variant="soft", color_scheme="gray", - style={"margin": "auto 0"}, + style=rx.Style(margin="auto 0"), ) @@ -170,7 +175,7 @@ def pagination(state: type[IngestState | SearchState]) -> rx.Component: disabled=state.disable_previous_page, variant="surface", custom_attrs={"data-testid": "pagination-previous-button"}, - style={"minWidth": "10%"}, + style=rx.Style(minWidth="10%"), ), rx.select( state.page_selection, @@ -195,36 +200,73 @@ def pagination(state: type[IngestState | SearchState]) -> rx.Component: disabled=state.disable_next_page, variant="surface", custom_attrs={"data-testid": "pagination-next-button"}, - style={"minWidth": "10%"}, + style=rx.Style(minWidth="10%"), ), spacing="4", - style={"width": "100%"}, + style=rx.Style(width="100%"), ) def icon_by_stem_type( - stem_type: str | None, - **props: int | str | rx.Color, -) -> rx.Component: + stem_type: str | None = None, + size: int | None = None, + style: rx.Style | None = None, +) -> rx.Component | rx.Var[Any]: """Render an icon for the given stem type.""" # Sigh, https://reflex.dev/docs/library/data-display/icon#using-dynamic-icon-tags - return rx.box( - rx.match( - stem_type, - ("AccessPlatform", rx.icon("app_window", **props)), - ("Activity", rx.icon("circle_gauge", **props)), - ("BibliographicResource", rx.icon("book_marked", **props)), - ("Consent", rx.icon("badge_check", **props)), - ("ContactPoint", rx.icon("inbox", **props)), - ("Distribution", rx.icon("container", **props)), - ("Organization", rx.icon("building", **props)), - ("OrganizationalUnit", rx.icon("door_open", **props)), - ("Person", rx.icon("circle_user_round", **props)), - ("PrimarySource", rx.icon("hard_drive", **props)), - ("Resource", rx.icon("archive", **props)), - ("Variable", rx.icon("box", **props)), - ("VariableGroup", rx.icon("boxes", **props)), - rx.icon("file_question", **props), - ), - title=stem_type, + return rx.match( + stem_type, + ( + "AccessPlatform", + rx.icon("app_window", size=size, style=style, title=stem_type), + ), + ( + "Activity", + rx.icon("circle_gauge", size=size, style=style, title=stem_type), + ), + ( + "BibliographicResource", + rx.icon("book_marked", size=size, style=style, title=stem_type), + ), + ( + "Consent", + rx.icon("badge_check", size=size, style=style, title=stem_type), + ), + ( + "ContactPoint", + rx.icon("inbox", size=size, style=style, title=stem_type), + ), + ( + "Distribution", + rx.icon("container", size=size, style=style, title=stem_type), + ), + ( + "Organization", + rx.icon("building", size=size, style=style, title=stem_type), + ), + ( + "OrganizationalUnit", + rx.icon("door_open", size=size, style=style, title=stem_type), + ), + ( + "Person", + rx.icon("circle_user_round", size=size, style=style, title=stem_type), + ), + ( + "PrimarySource", + rx.icon("hard_drive", size=size, style=style, title=stem_type), + ), + ( + "Resource", + rx.icon("archive", size=size, style=style, title=stem_type), + ), + ( + "Variable", + rx.icon("box", size=size, style=style, title=stem_type), + ), + ( + "VariableGroup", + rx.icon("boxes", size=size, style=style, title=stem_type), + ), + rx.icon("file_question", size=size, style=style), ) diff --git a/mex/editor/consent/state.py b/mex/editor/consent/state.py index 2281968a..fd848902 100644 --- a/mex/editor/consent/state.py +++ b/mex/editor/consent/state.py @@ -1,3 +1,5 @@ +from collections.abc import Generator + import reflex as rx from reflex.event import EventSpec @@ -11,12 +13,12 @@ class ConsentState(State): display_name: str | None = None @rx.event - def load_user(self) -> EventSpec | None: + def load_user(self) -> Generator[EventSpec, None, None]: """Set the stem type to a default.""" connector = LDAPConnector.get() if not self.user_ldap: self.target_path_after_login = self.router.page.raw_path - return rx.redirect("/login-ldap") - person = connector.get_person(sam_account_name=self.user_ldap.name) - self.display_name = person.displayName - return None + yield rx.redirect("/login-ldap") + else: + person = connector.get_person(sam_account_name=self.user_ldap.name) + self.display_name = person.displayName diff --git a/mex/editor/create/main.py b/mex/editor/create/main.py index 36d1e6f4..d42e17d2 100644 --- a/mex/editor/create/main.py +++ b/mex/editor/create/main.py @@ -26,15 +26,15 @@ def editor_field( field.name, primary_source, ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ), ), - style={"width": "100%"}, + style=rx.Style(width="100%"), + ), + style=rx.Style( + width="100%", + margin="var(--space-3) 0", ), - style={ - "width": "100%", - "margin": "var(--space-3) 0", - }, custom_attrs={"data-testid": f"field-{field.name}"}, role="row", ) @@ -45,7 +45,7 @@ def create_title() -> rx.Component: return rx.hstack( rx.heading( "Create new", - style={"userSelect": "none"}, + style=rx.Style(userSelect="none"), ), rx.select( CreateState.available_stem_types, @@ -72,9 +72,9 @@ def index() -> rx.Component: editor_field, ), validation_errors(), - style={ - "width": "100%", - "marginTop": "calc(2 * var(--space-6))", - }, + style=rx.Style( + width="100%", + marginTop="calc(2 * var(--space-6))", + ), ), ) diff --git a/mex/editor/edit/main.py b/mex/editor/edit/main.py index 1b04a5f3..a55ba12f 100644 --- a/mex/editor/edit/main.py +++ b/mex/editor/edit/main.py @@ -19,10 +19,9 @@ def edit_title() -> rx.Component: EditState.item_title, render_value, ), - as_child=True, ), custom_attrs={"data-testid": "edit-heading"}, - style={"userSelect": "none"}, + style=rx.Style(userSelect="none"), ) @@ -42,7 +41,7 @@ def deactivate_all_switch() -> rx.Component: color_scheme="jade", custom_attrs={"data-testid": "deactivate-all-switch"}, ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -59,9 +58,9 @@ def index() -> rx.Component: editor_field, ), validation_errors(), - style={ - "width": "100%", - "marginTop": "calc(2 * var(--space-6))", - }, + style=rx.Style( + width="100%", + marginTop="calc(2 * var(--space-6))", + ), ), ) diff --git a/mex/editor/edit/state.py b/mex/editor/edit/state.py index d38d4b20..e6565835 100644 --- a/mex/editor/edit/state.py +++ b/mex/editor/edit/state.py @@ -14,10 +14,11 @@ class EditState(RuleState): def show_submit_success_toast_on_redirect(self) -> Generator[EventSpec, None, None]: """Show a success toast when the saved param is set.""" if "saved" in self.router.page.params: - yield self.show_submit_success_toast() + yield EditState.show_submit_success_toast params = self.router.page.params.copy() params.pop("saved") - yield self.push_url_params(params) + if event := self.push_url_params(params): + yield event @rx.var def any_primary_source_or_editor_value_enabled(self) -> bool: @@ -40,4 +41,4 @@ def disable_all_primary_source_and_editor_values(self) -> EventSpec: ps.enabled = False for value in ps.editor_values: value.enabled = False - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] diff --git a/mex/editor/ingest/main.py b/mex/editor/ingest/main.py index 1f62da3b..95b2fb24 100644 --- a/mex/editor/ingest/main.py +++ b/mex/editor/ingest/main.py @@ -1,3 +1,5 @@ +from typing import Any + import reflex as rx from mex.editor.components import ( @@ -21,7 +23,7 @@ def expand_properties_button(result: IngestResult, index: int) -> rx.Component: rx.icon("minimize-2", size=15), rx.icon("maximize-2", size=15), ), - on_click=IngestState.toggle_show_properties(index), + on_click=IngestState.toggle_show_properties(index), # type: ignore[misc] align="end", color_scheme="gray", variant="surface", @@ -38,7 +40,7 @@ def ingest_button(result: IngestResult, index: int) -> rx.Component: align="end", color_scheme="jade", variant="surface", - on_click=IngestState.ingest_result(index), + on_click=IngestState.ingest_result(index), # type: ignore[misc] width="calc(8em * var(--scaling))", custom_attrs={"data-testid": f"ingest-button-{index}"}, ), @@ -62,11 +64,11 @@ def render_all_properties(result: IngestResult) -> rx.Component: result.all_properties, render_value, ), - style={ - "fontWeight": "var(--font-weight-light)", - "flexWrap": "wrap", - "alignItems": "start", - }, + style=rx.Style( + fontWeight="var(--font-weight-light)", + flexWrap="wrap", + alignItems="start", + ), ), custom_attrs={"data-testid": "all-properties-display"}, ) @@ -86,18 +88,18 @@ def ingest_result(result: IngestResult, index: int) -> rx.Component: rx.spacer(), expand_properties_button(result, index), ingest_button(result, index), - style={"width": "100%"}, + style=rx.Style(width="100%"), ), rx.cond( result.show_properties, render_all_properties(result), render_search_preview(result.preview), ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ), class_name="search-result-card", custom_attrs={"data-testid": f"result-{result.identifier}"}, - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -111,17 +113,19 @@ def search_input() -> rx.Component: max_length=100, name="query_string", placeholder="Search here...", - style={ - "--text-field-selection-color": "", - "--text-field-focus-color": "transparent", - "--text-field-border-width": "1px", - "backgroundClip": "content-box", - "backgroundColor": "transparent", - "boxShadow": ( - "inset 0 0 0 var(--text-field-border-width) transparent" - ), - "color": "", - }, + style=rx.Style( + { + "--text-field-selection-color": "", + "--text-field-focus-color": "transparent", + "--text-field-border-width": "1px", + "backgroundClip": "content-box", + "backgroundColor": "transparent", + "boxShadow": ( + "inset 0 0 0 var(--text-field-border-width) transparent" + ), + "color": "", + } + ), disabled=IngestState.is_loading, type="text", ), @@ -140,7 +144,7 @@ def search_input() -> rx.Component: IngestState.refresh, IngestState.resolve_identifiers, ], - style={"margin": "1em 0 1em"}, + style=rx.Style(margin="1em 0"), justify="center", align="center", ) @@ -153,15 +157,15 @@ def results_summary() -> rx.Component: rx.text( f"Showing {IngestState.current_results_length} " f"of {IngestState.total} items", - style={ - "color": "var(--gray-12)", - "fontWeight": "var(--font-weight-bold)", - "margin": "var(--space-4)", - "userSelect": "none", - }, + style=rx.Style( + color="var(--gray-12)", + fontWeight="var(--font-weight-bold)", + margin="var(--space-4)", + userSelect="none", + ), custom_attrs={"data-testid": "search-results-summary"}, ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -171,10 +175,10 @@ def search_results() -> rx.Component: IngestState.is_loading, rx.center( rx.spinner(size="3"), - style={ - "marginTop": "var(--space-6)", - "width": "100%", - }, + style=rx.Style( + marginTop="var(--space-6)", + width="100%", + ), ), rx.vstack( results_summary(), @@ -185,15 +189,15 @@ def search_results() -> rx.Component: pagination(IngestState), spacing="4", custom_attrs={"data-testid": "search-results-section"}, - style={ - "minWidth": "0", - "width": "100%", - }, + style=rx.Style( + minWidth="0", + width="100%", + ), ), ) -def search_infobox() -> rx.Component: +def search_infobox() -> rx.Component | rx.Var[Any]: """Render information about the specific search provider query format.""" return rx.match( IngestState.current_aux_provider, @@ -228,7 +232,7 @@ def tab_list() -> rx.Component: ), ), rx.spacer(), - style={"width": "calc(24em * var(--scaling))"}, + style=rx.Style(width="calc(24em * var(--scaling))"), ) ) @@ -258,17 +262,18 @@ def index() -> rx.Component: tab_list(), rx.spacer(), tab_content(), - default_value=IngestState.current_aux_provider, + default_value=f"{IngestState.current_aux_provider}", on_change=[ IngestState.set_current_aux_provider, + IngestState.reset_query_string, IngestState.go_to_first_page, IngestState.refresh, IngestState.resolve_identifiers, ], custom_attrs={"data-testid": "aux-tab-section"}, - style={ - "width": "100%", - "padding": "0 calc(var(--space-8) * var(--scaling))", - }, + style=rx.Style( + width="100%", + padding="0 calc(var(--space-8) * var(--scaling))", + ), ) ) diff --git a/mex/editor/ingest/state.py b/mex/editor/ingest/state.py index 11781a17..9bb37d5d 100644 --- a/mex/editor/ingest/state.py +++ b/mex/editor/ingest/state.py @@ -1,21 +1,15 @@ import math from collections.abc import Generator -from typing import Annotated +from typing import Any import reflex as rx -from pydantic import Field from reflex.event import EventSpec -from reflex.state import serialize_mutable_proxy from requests import HTTPError from mex.common.backend_api.connector import BackendApiConnector from mex.common.models import AnyExtractedModel, PaginatedItemsContainer from mex.editor.exceptions import escalate_error -from mex.editor.ingest.models import ( - ALL_AUX_PROVIDERS, - AuxProvider, - IngestResult, -) +from mex.editor.ingest.models import ALL_AUX_PROVIDERS, AuxProvider, IngestResult from mex.editor.ingest.transform import transform_models_to_results from mex.editor.state import State from mex.editor.utils import resolve_editor_value @@ -26,10 +20,10 @@ class IngestState(State): results_transformed: list[IngestResult] = [] results_extracted: list[AnyExtractedModel] = [] - total: Annotated[int, Field(ge=0)] = 0 - query_string: Annotated[str, Field(max_length=1000)] = "" - current_page: Annotated[int, Field(ge=1)] = 1 - limit: Annotated[int, Field(ge=1, le=100)] = 50 + total: int = 0 + limit: int = 50 + query_string: str = "" + current_page: int = 1 current_aux_provider: AuxProvider = AuxProvider.LDAP aux_providers: list[AuxProvider] = ALL_AUX_PROVIDERS is_loading: bool = True @@ -68,9 +62,9 @@ def toggle_show_properties(self, index: int) -> None: ].show_properties @rx.event - def set_current_aux_provider(self, value: AuxProvider) -> None: + def set_current_aux_provider(self, value: str) -> None: """Change the current aux provider.""" - self.current_aux_provider = value + self.current_aux_provider = AuxProvider(value) @rx.event def set_page(self, page_number: str | int) -> None: @@ -78,10 +72,15 @@ def set_page(self, page_number: str | int) -> None: self.current_page = int(page_number) @rx.event - def handle_submit(self, form_data: dict) -> None: + def handle_submit(self, form_data: dict[str, Any]) -> None: """Handle the form submit.""" self.query_string = form_data["query_string"] + @rx.event + def reset_query_string(self) -> None: + """Reset the query string.""" + self.query_string = "" + @rx.event def go_to_first_page(self) -> None: """Navigate to the first page.""" @@ -98,13 +97,12 @@ def go_to_next_page(self) -> None: self.current_page = self.current_page + 1 @rx.event - def ingest_result(self, index: int) -> Generator[EventSpec | None, None, None]: + def ingest_result(self, index: int) -> Generator[EventSpec, None, None]: """Ingest the selected result to MEx backend.""" connector = BackendApiConnector.get() + model = self.get_value(self.results_extracted[index]) # type: ignore[arg-type] try: - model: AnyExtractedModel = serialize_mutable_proxy( - self.results_extracted[index] - ) + # TODO(ND): use the user auth for backend requests (stop-gap MX-1616) connector.ingest([model]) except HTTPError as exc: yield from escalate_error( @@ -122,7 +120,7 @@ def ingest_result(self, index: int) -> Generator[EventSpec | None, None, None]: ) @rx.event - def scroll_to_top(self) -> Generator[EventSpec | None, None, None]: + def scroll_to_top(self) -> Generator[EventSpec, None, None]: """Scroll the page to the top.""" yield rx.call_script("window.scrollTo({top: 0, behavior: 'smooth'});") diff --git a/mex/editor/layout.py b/mex/editor/layout.py index 28540475..d9627992 100644 --- a/mex/editor/layout.py +++ b/mex/editor/layout.py @@ -16,11 +16,11 @@ def user_button() -> rx.Component: return rx.button( rx.cond( cast("User", State.user_mex).write_access, - rx.icon(tag="user_round_cog"), - rx.icon(tag="user_round"), + rx.icon("user_round_cog"), + rx.icon("user_round"), ), variant="ghost", - style={"marginTop": "0"}, + style=rx.Style(marginTop="0"), ) @@ -44,12 +44,38 @@ def user_menu() -> rx.Component: ) +def language_switcher() -> rx.Component: + """Render a language switcher.""" + return rx.menu.root( + rx.menu.trigger( + rx.button( + State.current_locale, + style=rx.Style(fontWeight="var(--font-weight-medium)"), + variant="ghost", + ), + custom_attrs={"data-testid": "language-switcher"}, + ), + rx.menu.content( + rx.foreach( + locale_service.get_available_locales(), + lambda locale: rx.menu.item( + rx.text(locale.label), + on_click=State.change_locale(locale.id), # type: ignore[misc] + custom_attrs={ + "data-testid": f"language-switcher-menu-item-{locale.id}" + }, + ), + ) + ), + ) + + def nav_link(item: NavItem) -> rx.Component: """Return a link component for the given navigation item.""" return rx.link( rx.text(item.title, size="4", weight="medium"), - on_click=State.navigate(item.raw_path), - underline=item.underline, + on_click=State.navigate(item.raw_path), # type: ignore[misc] + underline=item.underline, # type: ignore[arg-type] class_name="nav-item", custom_attrs={"data-href": item.raw_path}, ) @@ -60,14 +86,11 @@ def app_logo() -> rx.Component: return rx.hover_card.root( rx.hover_card.trigger( rx.hstack( - rx.icon( - tag="circuit-board", - size=28, - ), + rx.icon("circuit-board", size=28), rx.heading( "MEx Editor", weight="medium", - style={"userSelect": "none"}, + style=rx.Style(userSelect="none"), ), custom_attrs={"data-testid": "app-logo"}, ) @@ -82,45 +105,15 @@ def app_logo() -> rx.Component: ) -def language_switcher() -> rx.Component: - """Render a language switcher.""" - return rx.menu.root( - rx.menu.trigger( - rx.button( - State.current_locale, - style={ - "background": "transparent", - "color": "inherit", - "z_index": "20", - ":hover": {"cursor": "pointer"}, - }, - ), - custom_attrs={"data-testid": "language-switcher"}, - ), - rx.menu.content( - *[ - rx.menu.item( - rx.text(locale.label), - on_click=State.change_locale(locale.id), - custom_attrs={ - "data-testid": f"language-switcher-menu-item-{locale.id}" - }, - ) - for locale in locale_service.get_available_locales() - ] - ), - ) - - def nav_bar() -> rx.Component: """Return a navigation bar component.""" return rx.vstack( rx.box( - style={ - "height": "var(--space-6)", - "width": "100%", - "backdropFilter": " var(--backdrop-filter-panel)", - }, + style=rx.Style( + height="var(--space-6)", + width="100%", + backdropFilter="var(--backdrop-filter-panel)", + ), ), rx.card( rx.hstack( @@ -141,20 +134,20 @@ def nav_bar() -> rx.Component: ), size="2", custom_attrs={"data-testid": "nav-bar"}, - style={ - "width": "100%", - "marginTop": "calc(-1 * var(--base-card-border-width))", - }, + style=rx.Style( + width="100%", + marginTop="calc(-1 * var(--base-card-border-width))", + ), ), spacing="0", - style={ - "maxWidth": "var(--app-max-width)", - "minWidth": "var(--app-min-width)", - "position": "fixed", - "top": "0", - "width": "100%", - "zIndex": "1000", - }, + style=rx.Style( + maxWidth="var(--app-max-width)", + minWidth="var(--app-min-width)", + position="fixed", + top="0", + width="100%", + zIndex="1000", + ), ) @@ -173,20 +166,26 @@ def navigate_away_dialog() -> rx.Component: ), rx.flex( rx.alert_dialog.cancel( - rx.button("Cancel", on_click=State.close_navigate_dialog) + rx.button( + "Stay here", + color_scheme="gray", + on_click=State.close_navigate_dialog, + ) ), rx.alert_dialog.action( rx.button( - "Discard changes", - color_scheme="red", + "Navigate away", + color_scheme="tomato", on_click=[ State.close_navigate_dialog, - State.set_current_page_has_changes(False), - State.navigate(State.navigate_target), + State.set_current_page_has_changes(False), # type: ignore[misc] + State.navigate(State.navigate_target), # type: ignore[misc] ], ) ), spacing="3", + style=rx.Style(marginTop="1rem"), + justify="end", ), ), open=State.navigate_dialog_open, @@ -211,23 +210,25 @@ def page(*children: rx.Component) -> rx.Component: nav_bar(), rx.hstack( *children, - style={ - "maxWidth": "var(--app-max-width)", - "minWidth": "var(--app-min-width)", - "padding": "calc(var(--space-6) * 4) var(--space-6) var(--space-6)", - "width": "100%", - }, + style=rx.Style( + maxWidth="var(--app-max-width)", + minWidth="var(--app-min-width)", + padding="calc(var(--space-6) * 4) var(--space-6) var(--space-6)", + width="100%", + ), custom_attrs={"data-testid": "page-body"}, ), navigate_away_dialog(), page_leave_js(), - style={ - "--app-max-width": "calc(1480px * var(--scaling))", - "--app-min-width": "calc(800px * var(--scaling))", - }, + style=rx.Style( + { + "--app-max-width": "calc(1480px * var(--scaling))", + "--app-min-width": "calc(800px * var(--scaling))", + } + ), ), rx.center( rx.spinner(size="3"), - style={"marginTop": "40vh"}, + style=rx.Style(marginTop="40vh"), ), ) diff --git a/mex/editor/login/main.py b/mex/editor/login/main.py index dfed2f39..9c2f3787 100644 --- a/mex/editor/login/main.py +++ b/mex/editor/login/main.py @@ -15,9 +15,9 @@ def login_user(state: type[LoginLdapState | LoginMExState]) -> rx.Component: placeholder="Username", size="3", tab_index=1, - style={"width": "100%"}, + style=rx.Style(width="100%"), ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -32,9 +32,9 @@ def login_password(state: type[LoginLdapState | LoginMExState]) -> rx.Component: size="3", tab_index=2, type="password", - style={"width": "100%"}, + style=rx.Style(width="100%"), ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -46,14 +46,14 @@ def login_button() -> rx.Component: "Login", size="3", tab_index=3, - style={ - "padding": "0 var(--space-6)", - "marginTop": "var(--space-4)", - }, + style=rx.Style( + padding="0 var(--space-6)", + marginTop="var(--space-4)", + ), custom_attrs={"data-testid": "login-button"}, type="submit", ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -66,7 +66,7 @@ def login_form(state: type[LoginLdapState | LoginMExState]) -> rx.Component: app_logo(), rx.spacer(spacing="4"), rx.color_mode.button(), - style={"width": "100%"}, + style=rx.Style(width="100%"), ), rx.divider(size="4"), rx.form( @@ -74,17 +74,17 @@ def login_form(state: type[LoginLdapState | LoginMExState]) -> rx.Component: login_user(state), login_password(state), login_button(), - style={"width": "100%"}, + style=rx.Style(width="100%"), ), on_submit=state.login, spacing="4", ), ), - style={ - "width": "calc(340px * var(--scaling))", - "padding": "var(--space-4)", - "top": "20vh", - }, + style=rx.Style( + width="calc(340px * var(--scaling))", + padding="var(--space-4)", + top="20vh", + ), custom_attrs={"data-testid": "login-card"}, ) ) diff --git a/mex/editor/login/state.py b/mex/editor/login/state.py index 231df961..90e323cc 100644 --- a/mex/editor/login/state.py +++ b/mex/editor/login/state.py @@ -1,4 +1,5 @@ from base64 import b64encode +from collections.abc import Generator import reflex as rx from reflex.event import EventSpec @@ -28,7 +29,7 @@ def set_password(self, password: str) -> None: self.password = password @rx.event - def login(self) -> EventSpec: + def login(self) -> Generator[EventSpec, None, None]: """Login a user.""" write_access = has_write_access_ldap(self.username, self.password) if write_access: @@ -43,8 +44,9 @@ def login(self) -> EventSpec: else: target_path_after_login = "/" self.reset() # reset username/password - return rx.redirect(target_path_after_login) - return rx.window_alert("Invalid credentials.") + yield rx.redirect(target_path_after_login) + else: + yield rx.window_alert("Invalid credentials.") class LoginMExState(State): @@ -64,7 +66,7 @@ def set_password(self, password: str) -> None: self.password = password @rx.event - def login(self) -> EventSpec: + def login(self) -> Generator[EventSpec, None, None]: """Login a user.""" read_access = has_read_access_mex(self.username, self.password) write_access = has_write_access_mex(self.username, self.password) @@ -80,5 +82,6 @@ def login(self) -> EventSpec: else: target_path_after_login = "/" self.reset() # reset username/password - return rx.redirect(target_path_after_login) - return rx.window_alert("Invalid credentials.") + yield rx.redirect(target_path_after_login) + else: + yield rx.window_alert("Invalid credentials.") diff --git a/mex/editor/merge/main.py b/mex/editor/merge/main.py index 1e9fce20..45f3e79d 100644 --- a/mex/editor/merge/main.py +++ b/mex/editor/merge/main.py @@ -24,7 +24,7 @@ def search_result( rx.hstack( rx.checkbox( checked=MergeState.selected_items[category] == index, - on_change=MergeState.select_item(category, index), + on_change=MergeState.select_item(category, index), # type:ignore[misc] ), icon_by_stem_type( result.stem_type, @@ -37,7 +37,7 @@ def search_result( ), class_name="search-result-card", custom_attrs={"data-testid": f"result-{category}-{result.identifier}"}, - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -47,14 +47,14 @@ def results_summary(category: Literal["merged", "extracted"]) -> rx.Component: rx.text( f"Showing {MergeState.results_count[category]} " f"of {MergeState.total_count[category]} items", - style={ - "color": "var(--gray-12)", - "fontWeight": "var(--font-weight-bold)", - "margin": "var(--space-4)", - "userSelect": "none", - }, + style=rx.Style( + color="var(--gray-12)", + fontWeight="var(--font-weight-bold)", + margin="var(--space-4)", + userSelect="none", + ), ), - style={"width": "100%"}, + style=rx.Style(width="100%"), custom_attrs={"data-testid": f"{category}-results-summary"}, ) @@ -65,8 +65,8 @@ def entity_type_choice_merged(choice: tuple[str, bool]) -> rx.Component: choice[0], checked=choice[1], on_change=[ - MergeState.set_entity_type_merged(choice[0]), - MergeState.refresh(["merged"]), + MergeState.set_entity_type_merged(choice[0]), # type: ignore[misc] + MergeState.refresh(["merged"]), # type: ignore[misc] MergeState.resolve_identifiers, ], disabled=MergeState.is_loading, @@ -79,8 +79,8 @@ def entity_type_choice_extracted(choice: tuple[str, bool]) -> rx.Component: choice[0], checked=choice[1], on_change=[ - MergeState.set_entity_type_extracted(choice[0]), - MergeState.refresh(["extracted"]), + MergeState.set_entity_type_extracted(choice[0]), # type: ignore[misc] + MergeState.refresh(["extracted"]), # type: ignore[misc] MergeState.resolve_identifiers, ], disabled=MergeState.is_loading, @@ -113,7 +113,7 @@ def entity_type_filter(category: Literal["merged", "extracted"]) -> rx.Component ), type="always", scrollbars="vertical", - style={"height": 90}, + style=rx.Style(height=90), ), ) @@ -125,20 +125,22 @@ def search_input(category: Literal["merged", "extracted"]) -> rx.Component: rx.card( rx.input( autofocus=True, - default_value=MergeState.query_strings[category], value=MergeState.query_strings[category], + default_value=MergeState.query_strings[category], max_length=100, name=f"query_string_{category}", - on_change=MergeState.handle_submit(category), + on_change=MergeState.handle_submit(category), # type: ignore[misc] placeholder="Search here...", - style={ - "--text-field-selection-color": "", - "--text-field-focus-color": "transparent", - "--text-field-border-width": "calc(1px * var(--scaling))", - "boxShadow": ( - "inset 0 0 0 var(--text-field-border-width) transparent" - ), - }, + style=rx.Style( + { + "--text-field-selection-color": "", + "--text-field-focus-color": "transparent", + "--text-field-border-width": "calc(1px * var(--scaling))", + "boxShadow": ( + "inset 0 0 0 var(--text-field-border-width) transparent" + ), + } + ), tab_index=1, type="text", custom_attrs={"data-testid": f"search-input-{category}"}, @@ -151,7 +153,7 @@ def search_input(category: Literal["merged", "extracted"]) -> rx.Component: "Clear", variant="surface", disabled=MergeState.is_loading, - on_click=MergeState.clear_input(category), + on_click=MergeState.clear_input(category), # type: ignore[misc] custom_attrs={"data-testid": f"clear-button-{category}"}, ), rx.button( @@ -160,8 +162,8 @@ def search_input(category: Literal["merged", "extracted"]) -> rx.Component: variant="surface", disabled=MergeState.is_loading, on_click=[ - MergeState.refresh([category]), - MergeState.resolve_identifiers, + MergeState.refresh([category]), # type: ignore[misc] + MergeState.resolve_identifiers, # type: ignore[misc] ], custom_attrs={"data-testid": f"search-button-{category}"}, ), @@ -171,7 +173,11 @@ def search_input(category: Literal["merged", "extracted"]) -> rx.Component: margin="var(--space-4)", ), ), - style={"width": "100%", "margin-bottom": "var(--space-4)", "align": "center"}, + style=rx.Style( + width="100%", + marginBottom="var(--space-4)", + align="center", + ), ) @@ -182,7 +188,7 @@ def submit_button() -> rx.Component: color_scheme="jade", size="3", on_click=MergeState.submit_merge_items, - style={"margin": "var(--line-height-1) 0"}, + style=rx.Style(margin="var(--line-height-1) 0"), custom_attrs={"data-testid": "submit-button"}, ) @@ -192,11 +198,11 @@ def search_panel(category: Literal["merged", "extracted"]) -> rx.Component: return rx.vstack( rx.heading( f"Search {category} items", - style={ - "whiteSpace": "nowrap", - "overflow": "hidden", - "width": "100%", - }, + style=rx.Style( + whiteSpace="nowrap", + overflow="hidden", + width="100%", + ), custom_attrs={"data-testid": f"create-heading-{category}"}, ), search_input(category), @@ -214,7 +220,11 @@ def search_panel(category: Literal["merged", "extracted"]) -> rx.Component: ), ), ), - style={"width": "50%", "margin": "var(--space-4)", "align": "center"}, + style=rx.Style( + width="50%", + margin="var(--space-4)", + align="center", + ), ) @@ -225,21 +235,25 @@ def index() -> rx.Component: rx.hstack( search_panel(category="merged"), search_panel(category="extracted"), - style={"width": "100%", "align": "center", "justify": "center"}, + style=rx.Style( + width="100%", + align="center", + justify="center", + ), ), rx.box( submit_button(), - style={ - "justifyContent": "center", - "display": "flex", - "width": "100%", - }, + style=rx.Style( + justifyContent="center", + display="flex", + width="100%", + ), + ), + style=rx.Style( + width="100%", + align="center", + justify="center", + flexGrow="1", ), - style={ - "width": "100%", - "align": "center", - "justify": "center", - "flex-grow": "1", - }, ), ) diff --git a/mex/editor/merge/state.py b/mex/editor/merge/state.py index ee84740e..326ffd54 100644 --- a/mex/editor/merge/state.py +++ b/mex/editor/merge/state.py @@ -1,8 +1,7 @@ from collections.abc import Generator, Iterable -from typing import Annotated, Literal +from typing import Literal import reflex as rx -from pydantic import Field from reflex.event import EventSpec from requests import HTTPError @@ -27,8 +26,8 @@ class MergeState(State): entity_types_extracted: dict[str, bool] = { k.stemType: False for k in MERGED_MODEL_CLASSES } - limit: Annotated[int, Field(ge=1, le=100)] = 50 is_loading: bool = True + limit: int = 50 query_strings: dict[Literal["merged", "extracted"], str] = { "merged": "", "extracted": "", @@ -180,7 +179,7 @@ def _refresh_extracted(self) -> Generator[EventSpec | None, None, None]: self.total_count["extracted"] = response.total @rx.event - def submit_merge_items(self) -> Generator[EventSpec | None, None, None]: + def submit_merge_items(self) -> Generator[EventSpec, None, None]: """Submit merging of the items.""" yield rx.toast.error( title="Not Implemented", diff --git a/mex/editor/models.py b/mex/editor/models.py index 56d151a9..a07bfb1b 100644 --- a/mex/editor/models.py +++ b/mex/editor/models.py @@ -1,5 +1,4 @@ from importlib.resources import files -from typing import Literal import reflex as rx import yaml @@ -34,7 +33,7 @@ class NavItem(rx.Base): title: str = "" path: str = "/" raw_path: str = "/" - underline: Literal["always", "none"] = "none" + underline: str = "none" class ModelConfig(BaseModel): diff --git a/mex/editor/rules/main.py b/mex/editor/rules/main.py index 69ca952c..17500e60 100644 --- a/mex/editor/rules/main.py +++ b/mex/editor/rules/main.py @@ -24,7 +24,7 @@ def editor_value_switch( """Return a switch for toggling subtractive rules.""" return rx.switch( checked=value.enabled, - on_change=RuleState.toggle_field_value(field_name, value), + on_change=RuleState.toggle_field_value(field_name, value), # type: ignore[misc] custom_attrs={ "data-testid": f"switch-{field_name}-{primary_source.identifier}-{index}" }, @@ -56,8 +56,8 @@ def editor_edit_button( variant="soft", size="1", on_click=[ - RuleState.toggle_field_value_editing(field_name, index), - RuleState.resolve_identifiers, + RuleState.toggle_field_value_editing(field_name, index), # type: ignore[misc] + RuleState.resolve_identifiers, # type: ignore[misc] ], custom_attrs={ "data-testid": ( @@ -127,7 +127,7 @@ def remove_additive_button( """Render a button to remove an additive value.""" return rx.button( rx.icon( - tag="circle-minus", + "circle-minus", height="1rem", width="1rem", ), @@ -135,7 +135,7 @@ def remove_additive_button( color_scheme="tomato", variant="soft", size="1", - on_click=RuleState.remove_additive_value(field_name, index), + on_click=RuleState.remove_additive_value(field_name, index), # type: ignore[misc] custom_attrs={ "data-testid": f"additive-rule-{field_name}-{index}-remove-button" }, @@ -151,11 +151,11 @@ def href_input( return rx.input( placeholder="URL", value=href, - on_change=RuleState.set_href_value(field_name, index), - style={ - "margin": "calc(-1 * var(--space-1))", - "width": "100%", - }, + on_change=RuleState.set_href_value(field_name, index), # type: ignore[misc] + style=rx.Style( + margin="calc(-1 * var(--space-1))", + width="100%", + ), custom_attrs={"data-testid": f"additive-rule-{field_name}-{index}-href"}, ) @@ -169,12 +169,50 @@ def text_input( return rx.input( placeholder="Text", value=text, - on_change=RuleState.set_text_value(field_name, index), - style={ - "margin": "calc(-1 * var(--space-1))", - "width": "100%", - }, + on_change=RuleState.set_text_value(field_name, index), # type: ignore[misc] + style=rx.Style( + margin="calc(-1 * var(--space-1))", + width="100%", + ), + custom_attrs={"data-testid": f"additive-rule-{field_name}-{index}-text"}, + ) + + +def textarea_input( + field_name: str, + index: int, + text: str | None, +) -> rx.Component: + """Render a textarea component for editing a textarea attribute.""" + return rx.text_area( + placeholder="Text", + value=text, + on_change=RuleState.set_text_value(field_name, index), # type: ignore[misc] + style=rx.Style( + margin="calc(-1 * var(--space-1))", + width="100%", + ), custom_attrs={"data-testid": f"additive-rule-{field_name}-{index}-text"}, + rows="5", + resize="vertical", + ) + + +def identifier_input( + field_name: str, + index: int, + identifier: str | None, +) -> rx.Component: + """Render an input component for editing identifiers.""" + return rx.input( + placeholder="Identifier", + value=identifier, + on_change=RuleState.set_identifier_value(field_name, index), # type: ignore[misc] + style=rx.Style( + margin="calc(-1 * var(--space-1))", + width="100%", + ), + custom_attrs={"data-testid": f"additive-rule-{field_name}-{index}-identifier"}, ) @@ -202,7 +240,7 @@ def badge_input( variant="soft", radius="large", color_scheme="gray", - on_change=RuleState.set_badge_value(field_name, index), + on_change=RuleState.set_badge_value(field_name, index), # type: ignore[misc] custom_attrs={ "data-testid": f"additive-rule-{field_name}-{index}-badge" }, @@ -221,92 +259,23 @@ def additive_rule_input( return rx.hstack( rx.cond( input_config.editable_href, - rx.input( - placeholder="URL", - value=value.href, - on_change=RuleState.set_href_value(field_name, index), - style={ - "margin": "calc(-1 * var(--space-1))", - "width": "100%", - }, - custom_attrs={ - "data-testid": f"additive-rule-{field_name}-{index}-href" - }, - ), + href_input(field_name, index, value.href), ), rx.cond( input_config.editable_text, rx.cond( input_config.render_textarea, - rx.text_area( - placeholder="Text", - value=value.text, - on_change=RuleState.set_text_value(field_name, index), - style={ - "margin": "calc(-1 * var(--space-1))", - "width": "100%", - }, - custom_attrs={ - "data-testid": f"additive-rule-{field_name}-{index}-text" - }, - rows="5", - resize="vertical", - ), - rx.input( - placeholder="Text", - value=value.text, - on_change=RuleState.set_text_value(field_name, index), - style={ - "margin": "calc(-1 * var(--space-1))", - "width": "100%", - }, - custom_attrs={ - "data-testid": f"additive-rule-{field_name}-{index}-text" - }, - ), + textarea_input(field_name, index, value.text), + text_input(field_name, index, value.text), ), ), rx.cond( input_config.editable_identifier, - rx.input( - placeholder="Identifier", - value=value.identifier, - on_change=RuleState.set_identifier_value(field_name, index), - style={ - "margin": "calc(-1 * var(--space-1))", - "width": "100%", - }, - custom_attrs={ - "data-testid": f"additive-rule-{field_name}-{index}-identifier" - }, - ), + identifier_input(field_name, index, value.identifier), ), rx.cond( input_config.editable_badge, - rx.fragment( - rx.foreach( - input_config.badge_titles, - render_span, - ), - rx.box( - rx.select( - input_config.badge_options, - value=rx.cond( - value.badge, - value.badge, - input_config.badge_default, - ), - size="1", - variant="soft", - radius="large", - color_scheme="gray", - on_change=RuleState.set_badge_value(field_name, index), - custom_attrs={ - "data-testid": f"additive-rule-{field_name}-{index}-badge" - }, - ), - ), - ), + badge_input(field_name, index, input_config, value.badge), ), width="100%", ) @@ -338,7 +307,7 @@ def editor_value_card( background=rx.cond( primary_source.enabled & value.enabled, "inherit", "var(--gray-a4)" ), - style={"width": "100%"}, + style=rx.Style(width="100%"), custom_attrs={ "data-testid": f"value-{field_name}-{primary_source.identifier}-{index}" }, @@ -352,7 +321,7 @@ def primary_source_switch( """Return a switch for toggling preventive rules.""" return rx.switch( checked=primary_source.enabled, - on_change=RuleState.toggle_primary_source(field_name, primary_source.name.href), + on_change=RuleState.toggle_primary_source(field_name, primary_source.name.href), # type: ignore[misc] custom_attrs={ "data-testid": f"switch-{field_name}-{primary_source.identifier}" }, @@ -379,7 +348,7 @@ def primary_source_name( wrap="wrap", ), background=rx.cond(primary_source.enabled, "inherit", "var(--gray-a4)"), - style={"width": "33%"}, + style=rx.Style(width="33%"), custom_attrs={ "data-testid": ( f"primary-source-{field_name}-{primary_source.identifier}-name" @@ -396,7 +365,7 @@ def new_additive_button( return rx.card( rx.button( rx.icon( - tag="circle-plus", + "circle-plus", height="1rem", width="1rem", ), @@ -404,12 +373,12 @@ def new_additive_button( color_scheme="jade", variant="soft", size="1", - on_click=RuleState.add_additive_value(field_name), + on_click=RuleState.add_additive_value(field_name), # type: ignore[misc] custom_attrs={ "data-testid": f"new-additive-{field_name}-{primary_source_identifier}" }, ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -435,7 +404,7 @@ def editor_primary_source_stack( primary_source.identifier, ), ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -453,7 +422,7 @@ def editor_primary_source( field_name, primary_source, ), - style={"width": "100%"}, + style=rx.Style(width="100%"), custom_attrs={ "data-testid": f"primary-source-{field_name}-{primary_source.identifier}", }, @@ -470,12 +439,12 @@ def field_name( rx.text(field_translation.label), rx.cond( field.is_required, - rx.text("*", style={"color": "red"}), + rx.text("*", style=rx.Style(color="tomato")), ), spacing="1", ), title=field_translation.description, - style={"width": "25%"}, + style=rx.Style(width="25%"), custom_attrs={"data-testid": f"field-{field.name}-name"}, ) @@ -494,12 +463,12 @@ def editor_field( field.name, primary_source ), ), - style={"width": "100%"}, + style=rx.Style(width="100%"), + ), + style=rx.Style( + width="100%", + margin="var(--space-3) 0", ), - style={ - "width": "100%", - "margin": "var(--space-3) 0", - }, custom_attrs={"data-testid": f"field-{field.name}"}, role="row", ) @@ -514,7 +483,7 @@ def validation_message(error: ValidationMessage) -> rx.Component: render_span(" (Input: "), rx.code(error.input), render_span(")"), - style={"display": "inline"}, + style=rx.Style(display="inline"), ), ) @@ -536,10 +505,10 @@ def validation_errors() -> rx.Component: rx.button( "Close", on_click=RuleState.clear_validation_messages, - style={"margin": "var(--line-height-1) 0"}, + style=rx.Style(margin="var(--line-height-1) 0"), custom_attrs={"data-testid": "close-validation-errors-button"}, ), - style={"justifyContent": "flex-end"}, + style=rx.Style(justifyContent="flex-end"), ), ), open=cast("rx.vars.ArrayVar", RuleState.validation_messages).bool(), @@ -557,13 +526,13 @@ def submit_button() -> rx.Component: size="3", color_scheme="jade", on_click=[ - RuleState.set_is_submitting(True), + RuleState.set_is_submitting(True), # type: ignore[misc] RuleState.submit_rule_set, RuleState.resolve_identifiers, - RuleState.set_is_submitting(False), + RuleState.set_is_submitting(False), # type: ignore[misc] ], disabled=RuleState.is_submitting, - style={"margin": "var(--line-height-1) 0"}, + style=rx.Style(margin="var(--line-height-1) 0"), custom_attrs={"data-testid": "submit-button"}, ) @@ -574,7 +543,7 @@ def rule_page_header(title: rx.Component) -> rx.Component: icon_by_stem_type( RuleState.stem_type, size=32, - margin="auto 0", + style=rx.Style(margin="auto 0"), ), title, rx.spacer(), @@ -582,17 +551,17 @@ def rule_page_header(title: rx.Component) -> rx.Component: RuleState.stem_type, submit_button(), ), - style={ - "alignItems": "baseline", - "backdropFilter": " var(--backdrop-filter-panel)", - "marginTop": "calc(-1 * var(--space-1))", - "maxHeight": "6rem", - "maxWidth": "calc(var(--app-max-width) - var(--space-6) * 2)", - "overflow": "hidden", - "padding": "var(--space-4) 0", - "position": "fixed", - "top": "calc(var(--space-6) * 3)", - "width": "100%", - "zIndex": "999", - }, + style=rx.Style( + alignItems="baseline", + backdropFilter="var(--backdrop-filter-panel)", + marginTop="calc(-1 * var(--space-1))", + maxHeight="6rem", + maxWidth="calc(var(--app-max-width) - var(--space-6) * 2)", + overflow="hidden", + padding="var(--space-4) 0", + position="fixed", + top="calc(var(--space-6) * 3)", + width="100%", + zIndex="999", + ), ) diff --git a/mex/editor/rules/state.py b/mex/editor/rules/state.py index c149c9f9..3657161e 100644 --- a/mex/editor/rules/state.py +++ b/mex/editor/rules/state.py @@ -125,7 +125,7 @@ def _get_rule_set(self) -> AnyRuleSetRequest | AnyRuleSetResponse: return rule_set_request_class() @rx.event - def refresh(self) -> Generator[EventSpec | None, None, None]: + def refresh(self) -> Generator[EventSpec, None, None]: """Refresh the edit or create page.""" self.fields.clear() self.validation_messages.clear() @@ -174,7 +174,7 @@ def _send_rule_set_request(self, rule_set: AnyRuleSetRequest) -> AnyRuleSetRespo return connector.create_rule_set(rule_set) @rx.event - def submit_rule_set(self) -> Generator[EventSpec | None, None, None]: + def submit_rule_set(self) -> Generator[EventSpec, None, None]: """Convert the fields to a rule set and submit it to the backend.""" if self.stem_type is None: self.reset() @@ -193,15 +193,16 @@ def submit_rule_set(self) -> Generator[EventSpec | None, None, None]: ) return - yield State.set_current_page_has_changes(False) + yield State.set_current_page_has_changes(False) # type: ignore[misc] # clear cache to show edits in the UI resolve_identifier.cache_clear() # trigger redirect to edit page or refresh state if rule_set_response.stableTargetId != self.item_id: yield rx.redirect(f"/item/{rule_set_response.stableTargetId}/?saved") else: - yield from self.refresh() - yield self.show_submit_success_toast() + yield RuleState.refresh + yield RuleState.show_submit_success_toast + yield RuleState.resolve_identifiers @rx.event def set_is_submitting(self, value: bool) -> None: # noqa: FBT001 @@ -213,9 +214,9 @@ def set_is_submitting(self, value: bool) -> None: # noqa: FBT001 self.is_submitting = value @rx.event - def show_submit_success_toast(self) -> EventSpec: + def show_submit_success_toast(self) -> Generator[EventSpec, None, None]: """Show a toast for a successfully submitted rule-set.""" - return rx.toast.success( + yield rx.toast.success( title="Saved", description=f"{self.stem_type} was saved successfully.", class_name="editor-toast", @@ -260,7 +261,7 @@ def toggle_primary_source( for primary_source in self._get_primary_sources_by_field_name(field_name): if primary_source.name.href == href: primary_source.enabled = enabled - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] return None @rx.event @@ -275,7 +276,7 @@ def toggle_field_value( for editor_value in primary_source.editor_values: if editor_value == value: editor_value.enabled = enabled - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] return None @@ -296,21 +297,21 @@ def add_additive_value(self, field_name: str) -> EventSpec: """Add an additive rule to the given field.""" primary_source = self._get_editable_primary_source_by_field_name(field_name) primary_source.editor_values.append(EditorValue(being_edited=True)) - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] @rx.event def remove_additive_value(self, field_name: str, index: int) -> EventSpec: """Remove an additive rule from the given field.""" primary_source = self._get_editable_primary_source_by_field_name(field_name) primary_source.editor_values.pop(index) - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] @rx.event def set_text_value(self, field_name: str, index: int, value: str) -> EventSpec: """Set the text attribute on an additive editor value.""" primary_source = self._get_editable_primary_source_by_field_name(field_name) primary_source.editor_values[index].text = value - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] @rx.event def set_identifier_value( @@ -320,14 +321,14 @@ def set_identifier_value( primary_source = self._get_editable_primary_source_by_field_name(field_name) primary_source.editor_values[index].identifier = value primary_source.editor_values[index].href = f"/item/{value}" - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] @rx.event def set_badge_value(self, field_name: str, index: int, value: str) -> EventSpec: """Set the badge attribute on an additive editor value.""" primary_source = self._get_editable_primary_source_by_field_name(field_name) primary_source.editor_values[index].badge = value - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] @rx.event def set_href_value(self, field_name: str, index: int, value: str) -> EventSpec: @@ -335,4 +336,4 @@ def set_href_value(self, field_name: str, index: int, value: str) -> EventSpec: primary_source = self._get_editable_primary_source_by_field_name(field_name) primary_source.editor_values[index].href = value primary_source.editor_values[index].external = True - return State.set_current_page_has_changes(True) + return State.set_current_page_has_changes(True) # type: ignore[misc] diff --git a/mex/editor/rules/transform.py b/mex/editor/rules/transform.py index 37116f37..8b66a194 100644 --- a/mex/editor/rules/transform.py +++ b/mex/editor/rules/transform.py @@ -335,7 +335,7 @@ def _transform_editor_value_to_model_value( input_config: InputConfig, ) -> AnyModelValue: """Transform an editor value back to a value to be used in mex.common.models.""" - if field_name in LINK_FIELDS_BY_CLASS_NAME[class_name]: + if field_name in LINK_FIELDS_BY_CLASS_NAME[class_name] and value.href: return Link( url=value.href, language=LinkLanguage[value.badge] @@ -343,7 +343,7 @@ def _transform_editor_value_to_model_value( else None, title=value.text, ) - if field_name in TEXT_FIELDS_BY_CLASS_NAME[class_name]: + if field_name in TEXT_FIELDS_BY_CLASS_NAME[class_name] and value.text: return Text( language=TextLanguage[value.badge] if value.badge and value.badge != LANGUAGE_VALUE_NONE diff --git a/mex/editor/search/main.py b/mex/editor/search/main.py index 994a2b60..cdc44238 100644 --- a/mex/editor/search/main.py +++ b/mex/editor/search/main.py @@ -25,7 +25,7 @@ def search_result(result: SearchResult) -> rx.Component: icon_by_stem_type( result.stem_type, size=22, - color=rx.color("accent", 11), + style=rx.Style(color=rx.color("accent", 11)), ), rx.link( render_title(result.title[0]), @@ -34,11 +34,11 @@ def search_result(result: SearchResult) -> rx.Component: render_additional_titles(result.title[1:]), ), render_search_preview(result.preview), - style={"width": "100%"}, + style=rx.Style(width="100%"), ), class_name="search-result-card", custom_attrs={"data-testid": f"result-{result.identifier}"}, - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -52,14 +52,16 @@ def search_input() -> rx.Component: max_length=100, name="query_string", placeholder="Search here...", - style={ - "--text-field-selection-color": "", - "--text-field-focus-color": "transparent", - "--text-field-border-width": "calc(1px * var(--scaling))", - "boxShadow": ( - "inset 0 0 0 var(--text-field-border-width) transparent" - ), - }, + style=rx.Style( + { + "--text-field-selection-color": "", + "--text-field-focus-color": "transparent", + "--text-field-border-width": "calc(1px * var(--scaling))", + "boxShadow": ( + "inset 0 0 0 var(--text-field-border-width) transparent" + ), + } + ), tab_index=1, type="text", ), @@ -75,7 +77,7 @@ def search_input() -> rx.Component: ), on_submit=[SearchState.handle_submit, *full_refresh], ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -85,7 +87,7 @@ def entity_type_choice(choice: tuple[str, bool]) -> rx.Component: choice[0], checked=choice[1], on_change=[ - SearchState.set_entity_type(choice[0]), + SearchState.set_entity_type(choice[0]), # type: ignore[misc] *full_refresh, ], disabled=SearchState.is_loading, @@ -97,10 +99,10 @@ def entity_type_filter() -> rx.Component: return rx.card( rx.text( "entityType", - style={ - "marginBottom": "var(--space-4)", - "userSelect": "none", - }, + style=rx.Style( + marginBottom="var(--space-4)", + userSelect="none", + ), ), rx.vstack( rx.foreach( @@ -109,7 +111,7 @@ def entity_type_filter() -> rx.Component: ), custom_attrs={"data-testid": "entity-types"}, ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -119,7 +121,7 @@ def primary_source_choice(choice: tuple[str, SearchPrimarySource]) -> rx.Compone choice[1].title, checked=choice[1].checked, on_change=[ - SearchState.set_had_primary_source(choice[0]), + SearchState.set_had_primary_source(choice[0]), # type: ignore[misc] *full_refresh, ], disabled=SearchState.is_loading, @@ -128,22 +130,13 @@ def primary_source_choice(choice: tuple[str, SearchPrimarySource]) -> rx.Compone def primary_source_filter() -> rx.Component: """Render checkboxes for filtering the search results by primary source.""" - return rx.card( - rx.text( - "hadPrimarySource", - style={ - "marginBottom": "var(--space-4)", - "userSelect": "none", - }, - ), - rx.vstack( - rx.foreach( - SearchState.had_primary_sources, - primary_source_choice, - ), - custom_attrs={"data-testid": "had-primary-sources"}, + return rx.vstack( + rx.foreach( + SearchState.had_primary_sources, + primary_source_choice, ), - style={"width": "100%"}, + custom_attrs={"data-testid": "had-primary-sources"}, + style=rx.Style(width="100%"), ) @@ -156,21 +149,22 @@ def reference_field_filter_identifier( rx.input( value=identifier.value, on_change=[ - lambda x: SearchState.set_reference_field_filter_identifier( + lambda x: SearchState.set_reference_field_filter_identifier( # type: ignore[misc] index, x ), *full_refresh, ], required=True, pattern=IDENTIFIER_PATTERN, - class_name=rx.cond(identifier.validation_msg, "bg-red-500", ""), + class_name=rx.cond(identifier.validation_msg, "bg-tomato-500", ""), custom_attrs={"data-testid": f"reference-field-filter-id-{index}"}, + width="80%", ), rx.button( rx.icon("circle-minus"), variant="soft", on_click=[ - lambda: SearchState.remove_reference_field_filter_identifier(index), + lambda: SearchState.remove_reference_field_filter_identifier(index), # type: ignore[misc] *full_refresh, ], custom_attrs={ @@ -178,58 +172,55 @@ def reference_field_filter_identifier( }, ), spacing="1", + style=rx.Style(width="100%"), ), rx.text( identifier.validation_msg, - class_name="text-red-500", + class_name="text-tomato-500", ), + style=rx.Style(width="100%"), ) def reference_field_filter() -> rx.Component: """Render dropdown and text inputs for reference filtering the search result.""" - return rx.card( - rx.text( - "Filter by field references", - style={ - "marginBottom": "var(--space-4)", - "userSelect": "none", - }, - ), - rx.vstack( - rx.hstack( - rx.select( - items=SearchState.all_fields_for_entity_types, - value=SearchState.reference_field_filter.field, - placeholder="Field to filter by", - on_change=[ - SearchState.set_reference_filter_field, - *full_refresh, - ], - custom_attrs={"data-testid": "reference-field-filter-field"}, - ), - rx.button( - rx.icon("x"), - on_click=[ - SearchState.set_reference_filter_field(""), - *full_refresh, - ], - ), + return rx.vstack( + rx.hstack( + rx.select( + items=SearchState.all_fields_for_entity_types, + value=SearchState.reference_field_filter.field, + placeholder="Field to filter by", + on_change=[ + SearchState.set_reference_filter_field, + *full_refresh, + ], + width="80%", + custom_attrs={"data-testid": "reference-field-filter-field"}, ), - rx.hstack( - rx.text("Values"), - rx.button( - rx.icon("circle-plus"), - variant="soft", - on_click=[ - SearchState.add_reference_field_filter_identifier, - ], - custom_attrs={"data-testid": "reference-field-filter-add-id"}, - ), + rx.button( + rx.icon("x"), + variant="soft", + on_click=[ + SearchState.set_reference_filter_field(""), # type: ignore[misc] + *full_refresh, + ], ), - rx.foreach( - SearchState.reference_field_filter.identifiers, - reference_field_filter_identifier, + spacing="1", + style=rx.Style(width="100%"), + ), + rx.foreach( + SearchState.reference_field_filter.identifiers, + reference_field_filter_identifier, + ), + rx.hstack( + rx.button( + rx.icon("circle-plus"), + rx.text("Add Filter"), + variant="soft", + on_click=[ + SearchState.add_reference_field_filter_identifier, + ], + custom_attrs={"data-testid": "reference-field-filter-add-id"}, ), ), custom_attrs={"data-testid": "reference-field-filter"}, @@ -244,37 +235,44 @@ def reference_filter_tab() -> rx.Component: Returns: rx.Component: The tab list component containing two tabs. """ - return rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger( - "Dynamisch", + return rx.card( + rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger( + "Dynamic", + value="dynamic", + custom_attrs={ + "data-testid": "reference-filter-strategy-dynamic-tab" + }, + ), + rx.tabs.trigger( + "PrimarySource", + value="had_primary_source", + custom_attrs={ + "data-testid": ( + "reference-filter-strategy-had-primary-source-tab" + ) + }, + ), + style=rx.Style(marginBottom="1rem"), + ), + rx.tabs.content( + reference_field_filter(), value="dynamic", - custom_attrs={"data-testid": "reference-filter-strategy-dynamic-tab"}, ), - rx.tabs.trigger( - "PrimarySource", + rx.tabs.content( + primary_source_filter(), value="had_primary_source", - custom_attrs={ - "data-testid": "reference-filter-strategy-had-primary-source-tab" - }, ), + default_value="dynamic", + value=f"{SearchState.reference_filter_strategy}", + on_change=[ + SearchState.set_reference_filter_strategy, + *full_refresh, + ], + disabled=SearchState.is_loading, ), - rx.tabs.content( - reference_field_filter(), - value="dynamic", - ), - rx.tabs.content( - primary_source_filter(), - value="had_primary_source", - ), - default_value="dynamic", - value=SearchState.reference_filter_strategy, - on_change=[ - SearchState.set_reference_filter_strategy, - *full_refresh, - ], - disabled=SearchState.is_loading, - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -286,7 +284,7 @@ def sidebar() -> rx.Component: reference_filter_tab(), spacing="4", custom_attrs={"data-testid": "search-sidebar"}, - style={"width": "25%"}, + style=rx.Style(width="25%"), ) @@ -296,15 +294,15 @@ def results_summary() -> rx.Component: rx.text( f"Showing {SearchState.current_results_length} " f"of {SearchState.total} items", - style={ - "color": "var(--gray-12)", - "fontWeight": "var(--font-weight-bold)", - "margin": "var(--space-4)", - "userSelect": "none", - }, + style=rx.Style( + color="var(--gray-12)", + fontWeight="var(--font-weight-bold)", + margin="var(--space-4)", + userSelect="none", + ), custom_attrs={"data-testid": "search-results-summary"}, ), - style={"width": "100%"}, + style=rx.Style(width="100%"), ) @@ -314,10 +312,10 @@ def search_results() -> rx.Component: SearchState.is_loading, rx.center( rx.spinner(size="3"), - style={ - "marginTop": "var(--space-6)", - "width": "100%", - }, + style=rx.Style( + marginTop="var(--space-6)", + width="100%", + ), ), rx.vstack( results_summary(), @@ -328,10 +326,10 @@ def search_results() -> rx.Component: pagination(SearchState), spacing="4", custom_attrs={"data-testid": "search-results-section"}, - style={ - "minWidth": "0", - "width": "100%", - }, + style=rx.Style( + minWidth="0", + width="100%", + ), ), ) @@ -343,6 +341,6 @@ def index() -> rx.Component: sidebar(), search_results(), spacing="4", - style={"width": "100%"}, + style=rx.Style(width="100%"), ) ) diff --git a/mex/editor/search/models.py b/mex/editor/search/models.py index d2cc43ba..02af0299 100644 --- a/mex/editor/search/models.py +++ b/mex/editor/search/models.py @@ -21,7 +21,7 @@ class SearchPrimarySource(rx.Base): class ReferenceFieldIdentifierFilter(rx.Base): - """Reference field identifer for value and validation msg.""" + """Reference field identifier for value and validation msg.""" value: str validation_msg: str | None diff --git a/mex/editor/search/state.py b/mex/editor/search/state.py index 2c67c7e2..c767c752 100644 --- a/mex/editor/search/state.py +++ b/mex/editor/search/state.py @@ -1,9 +1,9 @@ import math from collections.abc import Generator -from typing import TYPE_CHECKING, Annotated, Literal +from typing import TYPE_CHECKING, Literal import reflex as rx -from pydantic import Field, TypeAdapter, ValidationError +from pydantic import TypeAdapter, ValidationError from reflex.event import EventSpec from requests import HTTPError @@ -58,17 +58,17 @@ class SearchState(State): """State management for the search page.""" results: list[SearchResult] = [] - total: Annotated[int, Field(ge=0)] = 0 - query_string: Annotated[str, Field(max_length=1000)] = "" + total: int = 0 + limit: int = 50 + query_string: str = "" entity_types: dict[str, bool] = {k.stemType: False for k in MERGED_MODEL_CLASSES} reference_filter_strategy: Literal["had_primary_source", "dynamic"] = "dynamic" had_primary_sources: dict[str, SearchPrimarySource] = {} + current_page: int = 1 reference_field_filter: ReferenceFieldFilter = ReferenceFieldFilter( field="", identifiers=[] ) - current_page: Annotated[int, Field(ge=1)] = 1 - limit: Annotated[int, Field(ge=1, le=100)] = 50 is_loading: bool = True @rx.event @@ -121,9 +121,9 @@ def remove_reference_field_filter_identifier(self, index: int) -> None: def add_reference_field_filter_identifier(self) -> None: """Add a new empty identifier.""" self.reference_field_filter.identifiers.append( - ReferenceFieldIdentifierFilter(value="") + ReferenceFieldIdentifierFilter(value="", validation_msg=None) ) - self.set_reference_field_filter_identifier( + SearchState.set_reference_field_filter_identifier( # type: ignore[misc] len(self.reference_field_filter.identifiers) - 1, "" ) @@ -178,7 +178,7 @@ def disable_page_selection(self) -> bool: def load_search_params(self) -> None: """Load url params into the state.""" router: RouterData = self.get_value("router") - self.set_page(router.page.params.get("page", 1)) + self.set_page(router.page.params.get("page", 1)) # type: ignore[misc] self.query_string = router.page.params.get("q", "") type_params = router.page.params.get("entityType", []) type_params = type_params if isinstance(type_params, list) else [type_params] @@ -211,14 +211,15 @@ def load_search_params(self) -> None: self.reference_field_filter = ReferenceFieldFilter( field=router.page.params.get("referenceField", ""), identifiers=[ - ReferenceFieldIdentifierFilter(value=x) for x in ref_field_identifiers + ReferenceFieldIdentifierFilter(value=x, validation_msg=None) + for x in ref_field_identifiers ], ) @rx.event - def push_search_params(self) -> EventSpec | None: + def push_search_params(self) -> Generator[EventSpec | None, None, None]: """Push a new browser history item with updated search parameters.""" - return self.push_url_params( + yield self.push_url_params( { "q": self.query_string, "page": self.current_page, @@ -278,7 +279,7 @@ def go_to_next_page(self) -> None: self.current_page = self.current_page + 1 @rx.event - def scroll_to_top(self) -> Generator[EventSpec | None, None, None]: + def scroll_to_top(self) -> Generator[EventSpec, None, None]: """Scroll the page to the top.""" yield rx.call_script("window.scrollTo({top: 0, behavior: 'smooth'});") @@ -299,12 +300,12 @@ def refresh(self) -> Generator[EventSpec | None, None, None]: entity_type = [ ensure_prefix(k, "Merged") for k, v in self.entity_types.items() if v ] - filter_strategy_params = ( _build_dynamic_refresh_params(self.reference_field_filter) if self.reference_filter_strategy == "dynamic" else _build_had_primary_source_refresh_params(self.had_primary_sources) ) + skip = self.limit * (self.current_page - 1) self.is_loading = True yield None @@ -340,7 +341,7 @@ def get_available_primary_sources(self) -> Generator[EventSpec, None, None]: primary_sources_response = connector.fetch_preview_items( entity_type=[ensure_prefix(MergedPrimarySource.stemType, "Merged")], skip=0, - limit=100, + limit=maximum_number_of_primary_sources, ) except HTTPError as exc: yield from escalate_error( diff --git a/mex/editor/state.py b/mex/editor/state.py index 81a478cd..01154b57 100644 --- a/mex/editor/state.py +++ b/mex/editor/state.py @@ -1,4 +1,4 @@ -from collections.abc import Mapping +from collections.abc import Generator, Mapping from importlib.metadata import version from urllib.parse import urlencode @@ -96,26 +96,24 @@ def navigate(self, raw_path: str) -> EventSpec | None: return rx.redirect(self.navigate_target) @rx.event - def logout(self) -> EventSpec: + def logout(self) -> Generator[EventSpec, None, None]: """Log out a user.""" self.reset() - return rx.redirect("/") + yield rx.redirect("/") @rx.event - def check_mex_login(self) -> EventSpec | None: + def check_mex_login(self) -> Generator[EventSpec, None, None]: """Check if a user is logged in.""" if self.user_mex is None: self.target_path_after_login = self.router.page.raw_path - return rx.redirect("/login") - return None + yield rx.redirect("/login") @rx.event - def check_ldap_login(self) -> EventSpec | None: + def check_ldap_login(self) -> Generator[EventSpec, None, None]: """Check if a user is logged in to ldap.""" if self.user_ldap is None: self.target_path_after_login = self.router.page.raw_path - return rx.redirect("/login-ldap") - return None + yield rx.redirect("/login-ldap") @staticmethod def _update_raw_path( @@ -137,7 +135,6 @@ def _update_raw_path( raw_path = f"{raw_path}?{query_str}" nav_item.raw_path = raw_path - @rx.event def push_url_params( self, params: Mapping[str, int | str | list[str]], diff --git a/mex/editor/utils.py b/mex/editor/utils.py index 13b4e643..b51c6198 100644 --- a/mex/editor/utils.py +++ b/mex/editor/utils.py @@ -2,7 +2,9 @@ from mex.common.backend_api.connector import BackendApiConnector from mex.common.exceptions import EmptySearchResultError, MExError +from mex.common.settings import SETTINGS_STORE from mex.editor.models import EditorValue +from mex.editor.settings import EditorSettings from mex.editor.transform import transform_models_to_title @@ -26,3 +28,9 @@ async def resolve_editor_value(editor_value: EditorValue) -> None: else: msg = f"Cannot resolve editor value: {editor_value}" raise MExError(msg) + + +def load_settings() -> EditorSettings: + """Reset the settings store and fetch the editor settings.""" + SETTINGS_STORE.reset() + return EditorSettings.get() diff --git a/mex/mex.py b/mex/mex.py index 5f3c2198..a10fe2e1 100644 --- a/mex/mex.py +++ b/mex/mex.py @@ -23,8 +23,8 @@ from mex.editor.rules.state import RuleState from mex.editor.search.main import index as search_index from mex.editor.search.state import SearchState -from mex.editor.settings import EditorSettings from mex.editor.state import State +from mex.editor.utils import load_settings app = rx.App( theme=themes.theme(accent_color="blue", has_background=False), @@ -136,7 +136,7 @@ app.api.description = "Metadata editor web application." app.register_lifespan_task( - lambda: logger.info(EditorSettings.get().text()), + lambda: logger.info(load_settings().text()), ) app.register_lifespan_task( log_info, diff --git a/tests/edit/test_main.py b/tests/edit/test_main.py index f36bc273..3d57819e 100644 --- a/tests/edit/test_main.py +++ b/tests/edit/test_main.py @@ -604,7 +604,7 @@ def test_required_fields_red_asterisk( asterisk = field.get_by_text("*", exact=True) if field_name in expected_required_fields: expect(asterisk).to_be_visible() - expect(asterisk).to_have_css("color", "rgb(255, 0, 0)") + expect(asterisk).to_have_css("color", "rgb(255, 99, 71)") else: expect(asterisk).to_have_count(0) @@ -619,9 +619,9 @@ def test_deactivate_all_switch(edit_page: Page) -> None: page.screenshot(path="tests_edit_test_main-test_deactivate_all_switch-clicked.png") last_switch = None - all_swtiches = page.get_by_role("switch").all() + all_switches = page.get_by_role("switch").all() - for switch in all_swtiches: + for switch in all_switches: expect(switch).not_to_be_checked() last_switch = switch @@ -743,7 +743,7 @@ def test_edit_page_navigation_unsaved_changes_warning_cancel_save_and_navigate( expect(dialog).to_be_visible() # cancel the navigation and check if url is still edit page - dialog.get_by_role("button", name="Cancel").click() + dialog.get_by_role("button", name="Stay here").click() expect(page).to_have_url(re.compile("/item/.*")) # click save changes @@ -778,5 +778,5 @@ def test_edit_page_navigation_unsaved_changes_warning_discard_changes_and_naviga expect(dialog).to_be_visible() # discard changes and expect navigation (url is search page url) - dialog.get_by_role("button", name="Discard changes").click() + dialog.get_by_role("button", name="Navigate away").click() expect(page).to_have_url(re.compile("/")) diff --git a/tests/login/test_state.py b/tests/login/test_state.py index 58e466a1..ac480f3f 100644 --- a/tests/login/test_state.py +++ b/tests/login/test_state.py @@ -9,7 +9,7 @@ def test_login_state_login_success() -> None: parent_state=State(), ) - assert "/" in str(state.login()) + assert "/" in str(list(state.login())) # type: ignore[misc] assert state.user_mex assert state.user_mex.dict() == { "name": "writer", @@ -25,7 +25,7 @@ def test_login_state_login_error() -> None: parent_state=State(), ) - assert "Invalid credentials" in str(state.login()) + assert "Invalid credentials" in str(list(state.login())) # type: ignore[misc] assert not state.user_mex @@ -35,5 +35,5 @@ def test_login_state_redirect_to_original_url() -> None: password="writer_pass", # noqa: S106 parent_state=State(target_path_after_login="/some-url/"), ) - assert "/some-url/" in str(state.login()) + assert "/some-url/" in str(list(state.login())) # type: ignore[misc] assert state.user_mex diff --git a/tests/rules/test_state.py b/tests/rules/test_state.py index d5c8e092..560df2f2 100644 --- a/tests/rules/test_state.py +++ b/tests/rules/test_state.py @@ -1,7 +1,7 @@ import pytest -from reflex.state import serialize_mutable_proxy from mex.common.models import ContactPointRuleSetResponse, ExtractedContactPoint +from mex.common.types import MergedPrimarySourceIdentifier from mex.editor.models import EditorValue from mex.editor.rules.models import EditorPrimarySource, InputConfig from mex.editor.rules.state import RuleState @@ -26,9 +26,7 @@ def test_state_get_primary_sources_by_field_name() -> None: with pytest.raises(ValueError, match="field not found: someField"): state._get_primary_sources_by_field_name("someField") - primary_sources = serialize_mutable_proxy( - state._get_primary_sources_by_field_name("email") - ) + primary_sources = state._get_primary_sources_by_field_name("email") assert primary_sources == [ EditorPrimarySource( @@ -36,7 +34,7 @@ def test_state_get_primary_sources_by_field_name() -> None: identifier="somePrimarySource", href="/item/somePrimarySource", ), - identifier="somePrimarySource", + identifier=MergedPrimarySourceIdentifier("somePrimarySource"), input_config=InputConfig(), editor_values=[EditorValue(text="test@foo.bar")], enabled=True, @@ -46,7 +44,7 @@ def test_state_get_primary_sources_by_field_name() -> None: identifier="00000000000000", href="/item/00000000000000", ), - identifier="00000000000000", + identifier=MergedPrimarySourceIdentifier("00000000000000"), input_config=InputConfig(editable_text=True, allow_additive=True), editor_values=[], enabled=True, diff --git a/tests/search/test_main.py b/tests/search/test_main.py index 794bf7a4..d5e21a51 100644 --- a/tests/search/test_main.py +++ b/tests/search/test_main.py @@ -209,9 +209,9 @@ def test_reference_filter_fields_for_entity_type( hps_tab.click() expect(page.get_by_test_id("had-primary-sources")).to_be_visible() - dyn_tab = page.get_by_role("tab", name="Dynamisch") + dyn_tab = page.get_by_role("tab", name="Dynamic") dyn_tab.click() - assert page.get_by_test_id("reference-field-filter").is_visible() + expect(page.get_by_test_id("reference-field-filter")).to_be_visible() # select person entity entity_types = page.get_by_test_id("entity-types") @@ -235,7 +235,7 @@ def test_reference_filter_fields_for_entity_type( ] for field in expected_person_fields: select_item = page.get_by_role("option", name=field) - select_item.is_visible() + expect(select_item).to_be_visible() @pytest.mark.integration diff --git a/tests/test_state.py b/tests/test_state.py index 08361769..d2bf597e 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -10,7 +10,7 @@ def test_state_logout() -> None: state = State(user_mex=User(name="Test", authorization="Auth", write_access=True)) assert state.user_mex - assert "/" in str(state.logout()) + assert "/" in str(list(state.logout())) # type: ignore[misc] assert state.user_mex is None @@ -18,14 +18,14 @@ def test_state_check_login_pass() -> None: state = State(user_mex=User(name="Test", authorization="Auth", write_access=True)) assert state.user_mex - assert state.check_mex_login() is None + assert list(state.check_mex_login()) == [] # type: ignore[misc] def test_state_check_login_fail() -> None: state = State() assert state.user_mex is None - assert "/login" in str(state.check_mex_login()) + assert "/login" in str(list(state.check_mex_login())) # type: ignore[misc] @pytest.mark.parametrize(