Skip to content

Commit

Permalink
feature/mx-1649 prevent and subtract (#192)
Browse files Browse the repository at this point in the history
# PR Context
- for local testing, needs a backend started with debug=True and
identity_provider=graph
- removed localization of temporals because those don't convert back to
rules that easily. let's wait until localization becomes a hard
requirement

# Added
- add toggles for preventive and subtractive rules
- add functionality to edit component for submitting rules
- add utility function to escalate errors to all consoles
- temporarily add BackendIdentityProvider (stop-gap MX-1763)

# Changes
- rename FixedX and EditableX classes to EditorX for consistency
 
# Removed

- drop dev-dependency to mex-backend, use the flush endpoint instead
- temporarily removed localization of temporals entity output

---------

Signed-off-by: Nicolas Drebenstedt <[email protected]>
Co-authored-by: Franziska Diehr <[email protected]>
Co-authored-by: RKI | Metadata Exchange <[email protected]>
Co-authored-by: rababerladuseladim <[email protected]>
  • Loading branch information
4 people authored Jan 22, 2025
1 parent fc45395 commit cad7ddc
Show file tree
Hide file tree
Showing 28 changed files with 1,567 additions and 727 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ jobs:
ports:
- 7687:7687
backend:
image: ghcr.io/robert-koch-institut/mex-backend:0.22.0
image: ghcr.io/robert-koch-institut/mex-backend:0.28.0
env:
MEX_BACKEND_API_USER_DATABASE: ${{ secrets.MEX_BACKEND_API_USER_DATABASE }}
MEX_BACKEND_API_KEY_DATABASE: ${{ secrets.MEX_BACKEND_API_KEY_DATABASE }}
MEX_IDENTITY_PROVIDER: graph
MEX_GRAPH_URL: neo4j://neo4j:7687
MEX_DEBUG: True
ports:
- 8080:8080

Expand Down Expand Up @@ -81,6 +83,7 @@ jobs:
env:
MEX_BACKEND_API_USER_DATABASE: ${{ secrets.MEX_BACKEND_API_USER_DATABASE }}
MEX_BACKEND_API_KEY: ${{ secrets.MEX_BACKEND_API_KEY }}
MEX_IDENTITY_PROVIDER: backend
run: |
pdm run editor run &
sleep 30 &&
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- add toggles for preventive and subtractive rules
- add functionality to edit component for submitting rules
- add utility function to escalate errors to all consoles
- temporarily add BackendIdentityProvider (stop-gap MX-1763)

### Changes

- bump cookiecutter template to 57e9b7
- rename FixedX and EditableX classes to EditorX for consistency

### Deprecated

### Removed

- drop dev-dependency to mex-backend, use the flush endpoint instead
- temporarily removed localization of temporals entity output

### Fixed

### Security
Expand Down
34 changes: 17 additions & 17 deletions mex/editor/components.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import reflex as rx

from mex.editor.edit.models import FixedValue
from mex.editor.edit.models import EditorValue


def fixed_internal_link(value: FixedValue) -> rx.Component:
"""Render a fixed value as a clickable internal link that reloads the edit page."""
def render_internal_link(value: EditorValue) -> rx.Component:
"""Render an editor value as a clickable internal link that loads the edit page."""
return rx.link(
value.text,
href=value.href,
Expand All @@ -13,8 +13,8 @@ def fixed_internal_link(value: FixedValue) -> rx.Component:
)


def fixed_external_link(value: FixedValue) -> rx.Component:
"""Render a fixed value as a clickable external link that opens in a new window."""
def render_external_link(value: EditorValue) -> rx.Component:
"""Render an editor value as a clickable external link that opens in a new tab."""
return rx.link(
value.text,
href=value.href,
Expand All @@ -24,25 +24,25 @@ def fixed_external_link(value: FixedValue) -> rx.Component:
)


def fixed_link(value: FixedValue) -> rx.Component:
"""Render a fixed value as a clickable link that reloads the edit page."""
def render_link(value: EditorValue) -> rx.Component:
"""Render an editor value as an internal or external link."""
return rx.cond(
value.external,
fixed_external_link(value),
fixed_internal_link(value),
render_external_link(value),
render_internal_link(value),
)


def fixed_text(value: FixedValue) -> rx.Component:
"""Render a fixed value as a text span."""
def render_text(value: EditorValue) -> rx.Component:
"""Render an editor value as a text span."""
return rx.text(
value.text,
as_="span",
)


def postfix_badge(value: FixedValue) -> rx.Component:
"""Render a generic badge after the fixed value."""
def postfix_badge(value: EditorValue) -> rx.Component:
"""Render a generic badge after an editor value."""
return rx.badge(
value.badge,
radius="full",
Expand All @@ -51,13 +51,13 @@ def postfix_badge(value: FixedValue) -> rx.Component:
)


def fixed_value(value: FixedValue) -> rx.Component:
"""Return a single fixed value."""
def render_value(value: EditorValue) -> rx.Component:
"""Render a single editor value."""
return rx.hstack(
rx.cond(
value.href,
fixed_link(value),
fixed_text(value),
render_link(value),
render_text(value),
),
rx.cond(
value.badge,
Expand Down
179 changes: 135 additions & 44 deletions mex/editor/edit/main.py
Original file line number Diff line number Diff line change
@@ -1,94 +1,185 @@
from typing import cast

import reflex as rx

from mex.editor.components import fixed_value
from mex.editor.edit.models import EditableField, EditablePrimarySource, FixedValue
from mex.editor.components import render_value
from mex.editor.edit.models import EditorField, EditorPrimarySource, EditorValue
from mex.editor.edit.state import EditState
from mex.editor.layout import page


def fixed_value_card(
field_name: str, primary_source: str | None, index: int, value: FixedValue
def editor_value_switch(
field_name: str,
primary_source: str | None,
value: EditorValue,
index: int,
) -> rx.Component:
"""Return a switch for toggling subtractive rules."""
return rx.switch(
checked=value.enabled,
on_change=lambda enabled: cast(EditState, EditState).toggle_field_value(
field_name,
value,
enabled,
),
custom_attrs={"data-testid": f"switch-{field_name}-{primary_source}-{index}"},
)


def editor_value_card(
field_name: str,
primary_source: str | None,
index: int,
value: EditorValue,
) -> rx.Component:
"""Return a card containing a single fixed value."""
"""Return a card containing a single editor value."""
return rx.card(
fixed_value(value),
rx.hstack(
render_value(value),
rx.cond(
cast(rx.vars.ArrayVar, EditState.editor_fields).contains(field_name),
editor_value_switch(field_name, primary_source, value, index),
),
),
style={"width": "30vw"},
custom_attrs={"data-testid": f"value-{field_name}_{primary_source}_{index}"},
custom_attrs={"data-testid": f"value-{field_name}-{primary_source}-{index}"},
)


def primary_source_switch(
field_name: str,
model: EditorPrimarySource,
) -> rx.Component:
"""Return a switch for toggling preventive rules."""
return rx.switch(
checked=model.enabled,
on_change=lambda enabled: cast(EditState, EditState).toggle_primary_source(
field_name,
cast(str, model.name.href),
enabled,
),
custom_attrs={"data-testid": f"switch-{field_name}-{model.identifier}"},
)


def editable_primary_source(
field_name: str, model: EditablePrimarySource
def primary_source_name(
field_name: str,
model: EditorPrimarySource,
) -> rx.Component:
"""Return the name of a primary source as a card with a preventive rule toggle."""
return rx.card(
rx.hstack(
render_value(model.name),
rx.cond(
cast(rx.vars.ArrayVar, EditState.editor_fields).contains(field_name),
primary_source_switch(field_name, model),
),
),
style={"width": "20vw"},
custom_attrs={
"data-testid": f"primary-source-{field_name}-{model.name.text}-name"
},
)


def editor_primary_source(
field_name: str,
model: EditorPrimarySource,
) -> rx.Component:
"""Return a horizontal grid of cards for editing one primary source."""
return rx.hstack(
rx.card(
fixed_value(model.name),
style={"width": "20vw"},
custom_attrs={
"data-testid": f"primary-source-{field_name}_{model.name.text}"
},
),
primary_source_name(field_name, model),
rx.vstack(
rx.foreach(
model.editor_values,
lambda value, index: fixed_value_card(
lambda value, index: editor_value_card(
field_name,
model.name.text,
index,
value,
),
)
),
),
custom_attrs={
"data-testid": f"primary-source-{field_name}-{model.name.text}",
},
)


def editable_field(model: EditableField) -> rx.Component:
def editor_field(model: EditorField) -> rx.Component:
"""Return a horizontal grid of cards for editing one field."""
return rx.hstack(
rx.card(
rx.text(model.name),
style={"width": "15vw"},
custom_attrs={"data-testid": f"field-{model.name}"},
custom_attrs={"data-testid": f"field-{model.name}-name"},
),
rx.foreach(
model.primary_sources,
lambda primary_source: editable_primary_source(
model.name,
primary_source,
),
rx.vstack(
rx.foreach(
model.primary_sources,
lambda primary_source: editor_primary_source(
model.name,
primary_source,
),
)
),
width="90vw",
custom_attrs={"data-testid": f"field-{model.name}"},
role="row",
)


def submit_button() -> rx.Component:
"""Render a submit button to save the rule set."""
return rx.button(
"Save",
color_scheme="jade",
size="3",
on_click=EditState.submit_rule_set,
style={"margin": "1em 0"},
custom_attrs={"data-testid": "submit-button"},
)


def edit_heading() -> rx.Component:
"""Return the heading for the edit page."""
return rx.heading(
rx.hstack(
rx.foreach(
EditState.item_title,
render_value,
),
),
custom_attrs={"data-testid": "edit-heading"},
style={
"margin": "1em 0",
"whiteSpace": "nowrap",
"overflow": "hidden",
"textOverflow": "ellipsis",
"maxWidth": "80vw",
},
)


def index() -> rx.Component:
"""Return the index for the edit component."""
return page(
rx.box(
rx.heading(
rx.hstack(
rx.foreach(
EditState.item_title,
fixed_value,
)
),
custom_attrs={"data-testid": "edit-heading"},
style={
"margin": "1em 0",
"whiteSpace": "nowrap",
"overflow": "hidden",
"textOverflow": "ellipsis",
"maxWidth": "80%",
},
),
edit_heading(),
rx.vstack(
rx.foreach(
EditState.fields,
editable_field,
editor_field,
),
rx.cond(
EditState.fields,
submit_button(),
),
),
style={"width": "100%", "margin": "0 2em 1em"},
style={
"width": "100%",
"margin": "0 2em 1em",
},
custom_attrs={"data-testid": "edit-section"},
),
)
15 changes: 9 additions & 6 deletions mex/editor/edit/models.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import reflex as rx

from mex.editor.models import FixedValue
from mex.common.types import MergedPrimarySourceIdentifier
from mex.editor.models import EditorValue


class EditablePrimarySource(rx.Base):
class EditorPrimarySource(rx.Base):
"""Model for describing the editor state for one primary source."""

name: FixedValue
editor_values: list[FixedValue]
name: EditorValue
identifier: MergedPrimarySourceIdentifier
editor_values: list[EditorValue] = []
enabled: bool = True


class EditableField(rx.Base):
class EditorField(rx.Base):
"""Model for describing the editor state for a single field."""

name: str
primary_sources: list[EditablePrimarySource]
primary_sources: list[EditorPrimarySource] = []
Loading

0 comments on commit cad7ddc

Please sign in to comment.