diff --git a/pyproject.toml b/pyproject.toml index 8d76e9f0..559a9d4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ssb-datadoc" -version = "0.6.0" +version = "0.7.0" description = "Document dataset metadata. For use in Statistics Norway's metadata system." authors = ["Statistics Norway "] license = "MIT" @@ -107,6 +107,7 @@ exclude = ["docs/conf.py", "noxfile.py", "gunicorn.conf.py"] module = [ "dash.*", "dash_bootstrap_components", + "ssb_dash_components", "flask_healthz", "dapla", "gcsfs", diff --git a/src/datadoc/app.py b/src/datadoc/app.py index 4fc0ae10..852714ad 100644 --- a/src/datadoc/app.py +++ b/src/datadoc/app.py @@ -19,9 +19,6 @@ from datadoc.backend.unit_types import UnitTypes from datadoc.enums import SupportedLanguages from datadoc.frontend.callbacks.register_callbacks import register_callbacks -from datadoc.frontend.callbacks.register_callbacks import ( - register_new_variables_tab_callbacks, -) from datadoc.frontend.components.alerts import dataset_validation_error from datadoc.frontend.components.alerts import opened_dataset_error from datadoc.frontend.components.alerts import opened_dataset_success @@ -32,7 +29,6 @@ from datadoc.frontend.components.control_bars import header from datadoc.frontend.components.control_bars import progress_bar from datadoc.frontend.components.dataset_tab import build_dataset_tab -from datadoc.frontend.components.new_variables_tab import build_new_variables_tab from datadoc.frontend.components.variables_tab import build_variables_tab from datadoc.utils import get_app_version from datadoc.utils import pick_random_port @@ -49,14 +45,6 @@ def build_app(app: type[Dash]) -> Dash: build_variables_tab(), ] - # TODO @mmwinther: Remove this when new variables workspace is ready for production. - # https://statistics-norway.atlassian.net/browse/DPMETA-14 - if config.get_toggle_new_variables_workspace(): - tabs_children.append(build_new_variables_tab()) - logger.warning( - "New variables workspace is enabled, not yet ready for production!", - ) - app.layout = dbc.Container( style={"padding": "4px"}, children=[ @@ -84,11 +72,6 @@ def build_app(app: type[Dash]) -> Dash: register_callbacks(app) - # TODO @mmwinther: Remove this when new variables workspace is ready for production. - # https://statistics-norway.atlassian.net/browse/DPMETA-14 - if config.get_toggle_new_variables_workspace(): - register_new_variables_tab_callbacks(app) - return app diff --git a/src/datadoc/assets/variables_style.css b/src/datadoc/assets/variables_style.css index 5976c73a..31cf4465 100644 --- a/src/datadoc/assets/variables_style.css +++ b/src/datadoc/assets/variables_style.css @@ -82,7 +82,7 @@ width: 100%; } -.form-check.variable-checkbox{ +.form-check.ssb-checkbox{ align-self: center; } diff --git a/src/datadoc/backend/datadoc_metadata.py b/src/datadoc/backend/datadoc_metadata.py index 5a2643fe..6de4d072 100644 --- a/src/datadoc/backend/datadoc_metadata.py +++ b/src/datadoc/backend/datadoc_metadata.py @@ -25,7 +25,9 @@ from datadoc.frontend.fields.display_dataset import ( OBLIGATORY_DATASET_METADATA_IDENTIFIERS, ) -from datadoc.frontend.fields.display_variables import OBLIGATORY_VARIABLES_METADATA +from datadoc.frontend.fields.display_variables import ( + OBLIGATORY_VARIABLES_METADATA_IDENTIFIERS, +) from datadoc.utils import METADATA_DOCUMENT_FILE_SUFFIX from datadoc.utils import calculate_percentage from datadoc.utils import get_timestamp_now @@ -276,12 +278,12 @@ def percent_complete(self) -> int: ], ) for variable in self.variables: - num_all_fields += len(OBLIGATORY_VARIABLES_METADATA) + num_all_fields += len(OBLIGATORY_VARIABLES_METADATA_IDENTIFIERS) num_set_fields += len( [ k for k, v in variable.model_dump().items() - if k in OBLIGATORY_VARIABLES_METADATA and v is not None + if k in OBLIGATORY_VARIABLES_METADATA_IDENTIFIERS and v is not None ], ) return calculate_percentage(num_set_fields, num_all_fields) diff --git a/src/datadoc/config.py b/src/datadoc/config.py index 1b72b009..13671e88 100644 --- a/src/datadoc/config.py +++ b/src/datadoc/config.py @@ -79,11 +79,6 @@ def get_dash_development_mode() -> bool: return _get_config_item("DATADOC_DASH_DEVELOPMENT_MODE") == "True" -def get_toggle_new_variables_workspace() -> bool: - """Toggle on the new variables workspace.""" - return _get_config_item("DATADOC_TOGGLE_NEW_VARIABLE_WORKSPACE") == "True" - - def get_jupyterhub_service_prefix() -> str | None: """Get the JupyterHub service prefix.""" return _get_config_item("JUPYTERHUB_SERVICE_PREFIX") diff --git a/src/datadoc/frontend/callbacks/register_callbacks.py b/src/datadoc/frontend/callbacks/register_callbacks.py index 5fb5563b..9def19f5 100644 --- a/src/datadoc/frontend/callbacks/register_callbacks.py +++ b/src/datadoc/frontend/callbacks/register_callbacks.py @@ -23,24 +23,15 @@ from datadoc.frontend.callbacks.dataset import change_language_dataset_metadata from datadoc.frontend.callbacks.dataset import open_dataset_handling from datadoc.frontend.callbacks.utils import update_global_language_state -from datadoc.frontend.callbacks.variables import ( - accept_variable_datatable_metadata_input, -) from datadoc.frontend.callbacks.variables import accept_variable_metadata_input -from datadoc.frontend.callbacks.variables import ( - update_variable_table_dropdown_options_for_language, -) -from datadoc.frontend.callbacks.variables import update_variable_table_language +from datadoc.frontend.components.builders import VARIABLES_METADATA_INPUT +from datadoc.frontend.components.builders import build_edit_section +from datadoc.frontend.components.builders import build_ssb_accordion from datadoc.frontend.components.dataset_tab import DATASET_METADATA_INPUT from datadoc.frontend.components.dataset_tab import build_dataset_metadata_accordion -from datadoc.frontend.components.resources_test_new_variables import ( - VARIABLES_METADATA_INPUT, -) -from datadoc.frontend.components.resources_test_new_variables import build_edit_section -from datadoc.frontend.components.resources_test_new_variables import build_ssb_accordion from datadoc.frontend.fields.display_dataset import DISPLAYED_DROPDOWN_DATASET_METADATA -from datadoc.frontend.fields.display_new_variables import OBLIGATORY_VARIABLES_METADATA -from datadoc.frontend.fields.display_new_variables import OPTIONAL_VARIABLES_METADATA +from datadoc.frontend.fields.display_variables import OBLIGATORY_VARIABLES_METADATA +from datadoc.frontend.fields.display_variables import OPTIONAL_VARIABLES_METADATA if TYPE_CHECKING: import dash_bootstrap_components as dbc @@ -57,7 +48,14 @@ def register_callbacks(app: Dash) -> None: Output("progress-bar", "value"), Output("progress-bar", "label"), Input({"type": DATASET_METADATA_INPUT, "id": ALL}, "value"), - Input("variables-table", "data"), + Input( + { + "type": VARIABLES_METADATA_INPUT, + "variable_short_name": ALL, + "id": ALL, + }, + "value", + ), ) def callback_update_progress( value: MetadataInputTypes, # noqa: ARG001 argument required by Dash @@ -122,50 +120,6 @@ def callback_accept_dataset_metadata_input( ctx.triggered_id["id"], ) - @app.callback( - Output("variables-table", "data"), - Output("variables-validation-error", "is_open"), - Output("variables-validation-explanation", "children"), - State("variables-table", "active_cell"), - Input("variables-table", "data"), - State("variables-table", "data_previous"), - Input("language-dropdown", "value"), - prevent_initial_call=True, - ) - def callback_variable_table( - active_cell: dict, - data: list[dict], - data_previous: list[dict], - language: str, - ) -> tuple[list[dict], bool, str]: - """Update data in the variable table. - - Triggered upon: - - New data enetered in the variable table. - - Change of language. - - Will display an alert if validation fails. - """ - if ctx.triggered_id == "language-dropdown": - return update_variable_table_language(SupportedLanguages(language)) - - return accept_variable_datatable_metadata_input( - data, - active_cell, - data_previous, - ) - - @app.callback( - Output("variables-table", "dropdown"), - Input("language-dropdown", "value"), - ) - def callback_variable_table_dropdown_options( - language: str, - ) -> dict[str, dict[str, object]]: - """Update the options in variable table dropdowns when the language changes.""" - language = SupportedLanguages(language) - return update_variable_table_dropdown_options_for_language(language) - @app.callback( Output("opened-dataset-success", "is_open"), Output("opened-dataset-error", "is_open"), @@ -203,15 +157,6 @@ def callback_clear_accordion_values(n_clicks: int) -> list[dbc.AccordionItem]: return build_dataset_metadata_accordion(n_clicks) return no_update - -def register_new_variables_tab_callbacks(app: Dash) -> None: - """Define and register callbacks for the new variables tab. - - This may be included in the main register_callbacks function once it's - ready for production. - """ - logger.info("Registering callbacks for the new variable tab for %s", app.title) - @app.callback( Output("accordion-wrapper", "children"), Input("language-dropdown", "value"), @@ -228,7 +173,7 @@ def callback_populate_new_variables_workspace( variable.short_name, { "type": "variables-accordion", - "id": f"{variable.short_name}-{language}", + "id": f"{variable.short_name}-{language}", # Insert language into the ID to invalidate browser caches }, variable.short_name, children=[ diff --git a/src/datadoc/frontend/callbacks/variables.py b/src/datadoc/frontend/callbacks/variables.py index 20926bd7..6ab6c973 100644 --- a/src/datadoc/frontend/callbacks/variables.py +++ b/src/datadoc/frontend/callbacks/variables.py @@ -8,20 +8,13 @@ from pydantic import ValidationError from datadoc import state -from datadoc.enums import SupportedLanguages from datadoc.frontend.callbacks.utils import MetadataInputTypes from datadoc.frontend.callbacks.utils import find_existing_language_string -from datadoc.frontend.callbacks.utils import get_options_for_language from datadoc.frontend.callbacks.utils import parse_and_validate_dates -from datadoc.frontend.fields.display_variables import ( - DISPLAYED_DROPDOWN_VARIABLES_METADATA, -) -from datadoc.frontend.fields.display_variables import DISPLAYED_DROPDOWN_VARIABLES_TYPES from datadoc.frontend.fields.display_variables import ( MULTIPLE_LANGUAGE_VARIABLES_METADATA, ) from datadoc.frontend.fields.display_variables import VariableIdentifiers -from datadoc.utils import get_display_values if TYPE_CHECKING: from datadoc_model.model import LanguageStringType @@ -29,53 +22,6 @@ logger = logging.getLogger(__name__) -def get_boolean_options_for_language( - language: SupportedLanguages, -) -> list[dict[str, bool | str]]: - """Get boolean options for the given language. - - The Dash Datatable has no good support for boolean - choices, so we use a Dropdown. - """ - true_labels = { - SupportedLanguages.ENGLISH: "Yes", - SupportedLanguages.NORSK_NYNORSK: "Ja", - SupportedLanguages.NORSK_BOKMÅL: "Ja", - } - false_labels = { - SupportedLanguages.ENGLISH: "No", - SupportedLanguages.NORSK_NYNORSK: "Nei", - SupportedLanguages.NORSK_BOKMÅL: "Nei", - } - return [ - { - "label": f"{true_labels[language]}", - "value": True, - }, - { - "label": f"{false_labels[language]}", - "value": False, - }, - ] - - -def get_metadata_field( - data: list[dict], - data_previous: list[dict], - active_cell: dict, -) -> str: - """Find which field (column in the data table) has been updated.""" - for i in range(len(data)): - # Find which column we're in; by diffing the current and previous data - update_diff = list(data[i].items() - data_previous[i].items()) - if update_diff: - return update_diff[-1][0] - - # When diffing fails, we fall back to the active cell (this happens - # when the user pastes a value into the Data Table) - return active_cell["column_id"] - - def handle_multi_language_metadata( metadata_field: str, new_value: MetadataInputTypes | LanguageStringType, @@ -102,118 +48,6 @@ def handle_multi_language_metadata( return new_value -def accept_variable_datatable_metadata_input( - data: list[dict], - active_cell: dict, - data_previous: list[dict], -) -> tuple[list[dict], bool, str]: - """Validate and save the value when variable metadata is updated.""" - show_error = False - error_explanation = "" - output_data = data - metadata_field: str = get_metadata_field(data, data_previous, active_cell) - - for row_index in range(len(data)): - # Update all the variables for this column to ensure we read in the value - new_value: MetadataInputTypes | LanguageStringType = data[row_index][ - metadata_field - ] - updated_row_id = data[row_index][VariableIdentifiers.SHORT_NAME.value] - - try: - if metadata_field in MULTIPLE_LANGUAGE_VARIABLES_METADATA: - new_value = handle_multi_language_metadata( - metadata_field, - new_value, - updated_row_id, - ) - elif new_value == "": - # Allow clearing non-multiple-language text fields - new_value = None - - # Write the value to the variables structure - setattr( - state.metadata.variables_lookup[updated_row_id], - metadata_field, - new_value, - ) - except ValidationError as e: - show_error = True - error_explanation = f"`{e}`" - output_data = data_previous - logger.debug("Caught ValidationError:", exc_info=True) - else: - logger.debug("Successfully updated %s with %s", updated_row_id, new_value) - - return output_data, show_error, error_explanation - - -def update_variable_table_dropdown_options_for_language( - language: SupportedLanguages, -) -> dict[str, dict[str, object]]: - """Retrieve enum options for dropdowns in the Datatable. - - Handles the special case of boolean values which we represent in the Datatable - with a Dropdown but they're not backed by an Enum. Example return data structure as follows: - - .. code-block:: python - - { - "data_type": { - "options": [ - {"label": "TEKST", "value": "STRING"}, - {"label": "HELTALL", "value": "INTEGER"}, - {"label": "DESIMALTALL", "value": "FLOAT"}, - {"label": "DATOTID", "value": "DATETIME"}, - {"label": "BOOLSK", "value": "BOOLEAN"}, - ], - }, - "direct_person_identifying": { - "options": [ - {"label": "Ja", "value": True}, - {"label": "Nei", "value": False}, - ], - }, - "temporality_type": {"options": [{"label": "FAST", "value": "FIXED"}]}, - } - - Args: - language (SupportedLanguages): The language for metadata entry selected by the user. - - Returns: - Data structure with options for all dropdowns. - - """ - options: list[dict[str, object]] = [] - for field_type in DISPLAYED_DROPDOWN_VARIABLES_TYPES: - value = ( - get_boolean_options_for_language(language) - if field_type is bool - else get_options_for_language(language, field_type) - ) - options.append({"options": value}) - return dict(zip(DISPLAYED_DROPDOWN_VARIABLES_METADATA, options, strict=True)) - - -def update_variable_table_language( - language: SupportedLanguages, -) -> tuple[list[dict], bool, str]: - """Get data in the relevant language.""" - state.current_metadata_language = language - logger.debug("Updated variable table language: %s", language.name) - return ( - [ - get_display_values( - v, - state.current_metadata_language, - ) - for v in state.metadata.variables - ], - False, # Don't show validation error - "", # No validation explanation needed - ) - - def accept_variable_metadata_input( value: MetadataInputTypes, variable_short_name: str, diff --git a/src/datadoc/frontend/components/builders.py b/src/datadoc/frontend/components/builders.py index 70a70eca..d678508f 100644 --- a/src/datadoc/frontend/components/builders.py +++ b/src/datadoc/frontend/components/builders.py @@ -6,10 +6,17 @@ from dataclasses import dataclass from enum import Enum from enum import auto +from typing import TYPE_CHECKING import dash_bootstrap_components as dbc +import ssb_dash_components as ssb from dash import html +if TYPE_CHECKING: + from datadoc_model import model + + from datadoc.frontend.fields.display_variables import VariablesInputField + class AlertTypes(Enum): """Types of alerts.""" @@ -100,3 +107,81 @@ def build_ssb_button(text: str, icon_class: str, button_id: str) -> dbc.Button: class_name="ssb-btn primary-btn", id=button_id, ) + + +info_section = ( + "Informasjon om hvordan jobbe i Datadoc, antall variabler i datasettet: osv.." +) + +VARIABLES_METADATA_INPUT = "variables-metadata-input" + + +def build_input_field_section( + metadata_inputs: list[VariablesInputField], + variable: model.Variable, + language: str, +) -> dbc.Form: + """Create input fields.""" + return dbc.Form( + [ + i.render( + { + "type": VARIABLES_METADATA_INPUT, + "variable_short_name": variable.short_name, + "id": i.identifier, + }, + language, + variable, + ) + for i in metadata_inputs + ], + id=VARIABLES_METADATA_INPUT, + className="variables-input-group", + ) + + +def build_edit_section( + metadata_inputs: list, + title: str, + variable: model.Variable, + language: str, +) -> html.Section: + """Create input section.""" + return html.Section( + id={"type": "edit-section", "title": title}, + children=[ + ssb.Title(title, size=3, className="input-section-title"), + build_input_field_section(metadata_inputs, variable, language), + ], + className="input-section", + ) + + +def build_ssb_accordion( + header: str, + key: dict, + variable_short_name: str, + children: list, +) -> ssb.Accordion: + """Build Accordion for one variable.""" + return ssb.Accordion( + header=header, + id=key, + children=[ + html.Section( + id={ + "type": "variable-input-alerts", + "variable_short_name": variable_short_name, + }, + className="alert-section", + ), + html.Section( + id={ + "type": "variable-inputs", + "variable_short_name": variable_short_name, + }, + children=children, + ), + ], + className="variable", + ) diff --git a/src/datadoc/frontend/components/new_variables_tab.py b/src/datadoc/frontend/components/new_variables_tab.py deleted file mode 100644 index 4619daac..00000000 --- a/src/datadoc/frontend/components/new_variables_tab.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Components and layout for testing new layout for variables tab.""" - -from __future__ import annotations - -import dash_bootstrap_components as dbc -import ssb_dash_components as ssb # type: ignore[import-untyped] -from dash import html - -from datadoc.frontend.components.builders import build_ssb_styled_tab -from datadoc.frontend.components.resources_test_new_variables import info_section - - -def build_new_variables_tab() -> dbc.Tab: - """Build page content for new variables tab.""" - return build_ssb_styled_tab( - "Variabler Ny", - dbc.Container( - [ - html.Header( - [ - ssb.Title( - "Variabel detaljer", - size=2, - className="variables-title", - ), - ssb.Paragraph( - info_section, - id="variables-information", - ), - ssb.Input( - label="Søk i variabler", - searchField=True, - id="search-variables", - n_submit=0, - value="", - ), - ], - className="variables-header", - ), - html.Main( - id="accordion-wrapper", - className="main-content", - ), - ], - class_name="page-wrapper", - ), - ) diff --git a/src/datadoc/frontend/components/resources_test_new_variables.py b/src/datadoc/frontend/components/resources_test_new_variables.py deleted file mode 100644 index 63a24ce5..00000000 --- a/src/datadoc/frontend/components/resources_test_new_variables.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Test resources for testing new layout for variables tab.""" - -from __future__ import annotations - -import logging -from typing import TYPE_CHECKING - -import dash_bootstrap_components as dbc -import ssb_dash_components as ssb # type: ignore[import-untyped] -from dash import html - -if TYPE_CHECKING: - from datadoc_model import model - - from datadoc.frontend.fields.display_new_variables import VariablesInputField - -logger = logging.getLogger(__name__) - -info_section = ( - "Informasjon om hvordan jobbe i Datadoc, antall variabler i datasettet: osv.." -) - -VARIABLES_METADATA_INPUT = "variables-metadata-input" - - -def build_input_field_section( - metadata_inputs: list[VariablesInputField], - variable: model.Variable, - language: str, -) -> dbc.Form: - """Create input fields.""" - return dbc.Form( - [ - i.render( - { - "type": VARIABLES_METADATA_INPUT, - "variable_short_name": variable.short_name, - "id": i.identifier, - }, - language, - variable, - ) - for i in metadata_inputs - ], - id=VARIABLES_METADATA_INPUT, - className="variables-input-group", - ) - - -# One section of inputs (either obligatory or recommended) -def build_edit_section( - metadata_inputs: list, - title: str, - variable: model.Variable, - language: str, -) -> html.Section: - """Create input section.""" - return html.Section( - id={"type": "edit-section", "title": title}, - children=[ - ssb.Title(title, size=3, className="input-section-title"), - build_input_field_section(metadata_inputs, variable, language), - ], - className="input-section", - ) - - -# Accordion for variable -def build_ssb_accordion( - header: str, - key: dict, - variable_short_name: str, - children: list, -) -> ssb.Accordion: - """Build Accordion for one variable.""" - return ssb.Accordion( - header=header, - id=key, - children=[ - html.Section( - id={ - "type": "variable-input-alerts", - "variable_short_name": variable_short_name, - }, - className="alert-section", - ), - html.Section( - id={ - "type": "variable-inputs", - "variable_short_name": variable_short_name, - }, - children=children, - ), - ], - className="variable-accordion", - ) diff --git a/src/datadoc/frontend/components/variables_tab.py b/src/datadoc/frontend/components/variables_tab.py index 7e71505f..b136744f 100644 --- a/src/datadoc/frontend/components/variables_tab.py +++ b/src/datadoc/frontend/components/variables_tab.py @@ -1,75 +1,47 @@ -"""Components and layout for the Variables metadata tab.""" +"""Components and layout for variables tab.""" from __future__ import annotations import dash_bootstrap_components as dbc -from dash import dash_table +import ssb_dash_components as ssb from dash import html -from datadoc import state from datadoc.frontend.components.builders import build_ssb_styled_tab -from datadoc.frontend.fields.display_variables import DISPLAY_VARIABLES -from datadoc.frontend.fields.display_variables import VariableIdentifiers -from datadoc.utils import get_display_values +from datadoc.frontend.components.builders import info_section def build_variables_tab() -> dbc.Tab: - """Build the Variables metadata tab.""" + """Build the framework for the variables tab.""" return build_ssb_styled_tab( "Variabler", dbc.Container( - children=[ - dbc.Row(html.H2("Variabel detaljer", className="ssb-title")), - dbc.Row( - dash_table.DataTable( - id="variables-table", - # Populate fields with known values - data=[ - get_display_values(v, state.current_metadata_language) - for v in state.metadata.variables - ], - # Define columns based on the information in DISPLAY_VARIABLES - columns=[ - { - "name": variable.display_name, - "id": variable.identifier, - "editable": variable.editable, - "presentation": variable.presentation, - "hideable": variable.editable, - } - for variable in DISPLAY_VARIABLES.values() - if variable.identifier - != VariableIdentifiers.IDENTIFIER.value # Should be removed from the model, for now we hide it - ], - # Non-obligatory variables are hidden by default - hidden_columns=[ - v.identifier - for v in DISPLAY_VARIABLES.values() - if v.obligatory is False - ], - # Include tooltips for all columns - tooltip_header={ - v.identifier: v.description - for v in DISPLAY_VARIABLES.values() - }, - editable=True, - # Exclude short_name column from scrolling - fixed_columns={"headers": True, "data": 1}, - # Enable sorting and pagination - sort_action="native", - page_action="native", - page_size=20, - # Enable filtering - filter_action="native", - filter_options={"case": "insensitive"}, - # Use horizontal scroll, keep full width - style_table={ - "overflowX": "auto", - "minWidth": "100%", - "accentColor": "black", - }, - ), + [ + html.Header( + [ + ssb.Title( + "Variabel detaljer", + size=2, + className="variables-title", + ), + ssb.Paragraph( + info_section, + id="variables-information", + ), + ssb.Input( + label="Søk i variabler", + searchField=True, + id="search-variables", + n_submit=0, + value="", + ), + ], + className="variables-header", + ), + html.Main( + id="accordion-wrapper", + className="main-content", ), ], + class_name="page-wrapper", ), ) diff --git a/src/datadoc/frontend/fields/display_base.py b/src/datadoc/frontend/fields/display_base.py index db5f31f5..3e717126 100644 --- a/src/datadoc/frontend/fields/display_base.py +++ b/src/datadoc/frontend/fields/display_base.py @@ -10,14 +10,16 @@ from typing import Any import dash_bootstrap_components as dbc -import ssb_dash_components as ssb # type: ignore[import-untyped] +import ssb_dash_components as ssb from dash import dcc from datadoc import state from datadoc.enums import SupportedLanguages +from datadoc.frontend.callbacks.utils import get_language_strings_enum if TYPE_CHECKING: from collections.abc import Callable + from enum import Enum from dash.development.base_component import Component from datadoc_model import model @@ -36,6 +38,20 @@ } +def get_enum_options_for_language( + enum: Enum, + language: SupportedLanguages, +) -> list[dict[str, str]]: + """Generate the list of options based on the currently chosen language.""" + return [ + { + "title": i.get_value_for_language(language), + "id": i.name, + } + for i in get_language_strings_enum(enum) # type: ignore [attr-defined] + ] + + def input_kwargs_factory() -> dict[str, t.Any]: """Initialize the field extra_kwargs. @@ -45,8 +61,7 @@ def input_kwargs_factory() -> dict[str, t.Any]: return INPUT_KWARGS -# New empty kwargs for input new variables -def new_input_kwargs_factory() -> dict[str, t.Any]: +def empty_kwargs_factory() -> dict[str, t.Any]: """Initialize the field extra_kwargs. We aren't allowed to directly assign a mutable type like a dict to @@ -98,17 +113,6 @@ class DisplayMetadata: multiple_language_support: bool = False -@dataclass -class DisplayVariablesMetadata(DisplayMetadata): - """Controls for how a given metadata field should be displayed. - - Specific to variable fields. - """ - - options: dict[str, list[dict[str, str]]] | None = None - presentation: str | None = "input" - - @dataclass class DisplayDatasetMetadata(DisplayMetadata): """Controls for how a given metadata field should be displayed. @@ -128,7 +132,7 @@ class DisplayDatasetMetadataDropdown(DisplayDatasetMetadata): # fmt: off options_getter: Callable[[SupportedLanguages], list[dict[str, str]]] = lambda _: [] # noqa: E731 # fmt: on - extra_kwargs: dict[str, Any] = field(default_factory=new_input_kwargs_factory) + extra_kwargs: dict[str, Any] = field(default_factory=empty_kwargs_factory) component: type[Component] = dcc.Dropdown @@ -139,7 +143,7 @@ class VariablesInputField(DisplayMetadata): Specific to variable fields. """ - extra_kwargs: dict[str, Any] = field(default_factory=new_input_kwargs_factory) + extra_kwargs: dict[str, Any] = field(default_factory=empty_kwargs_factory) value_getter: Callable[[BaseModel, str], Any] = get_metadata_and_stringify type: str = "text" @@ -166,7 +170,7 @@ def render( class VariablesDropdownField(DisplayMetadata): """Control how a Dropdown should be displayed.""" - extra_kwargs: dict[str, Any] = field(default_factory=new_input_kwargs_factory) + extra_kwargs: dict[str, Any] = field(default_factory=empty_kwargs_factory) value_getter: Callable[[BaseModel, str], Any] = get_metadata_and_stringify # fmt: off options_getter: Callable[[SupportedLanguages], list[dict[str, str]]] = lambda _: [] # noqa: E731 @@ -209,6 +213,6 @@ def render( id=variable_id, disabled=not self.editable, label_class_name="ssb-checkbox checkbox-label", - class_name="variable-checkbox", + class_name="ssb-checkbox", value=value, ) diff --git a/src/datadoc/frontend/fields/display_new_variables.py b/src/datadoc/frontend/fields/display_new_variables.py deleted file mode 100644 index 5eb02c8c..00000000 --- a/src/datadoc/frontend/fields/display_new_variables.py +++ /dev/null @@ -1,212 +0,0 @@ -"""Functionality for displaying new variables metadata.""" - -from __future__ import annotations - -import functools -import logging -from enum import Enum -from typing import TYPE_CHECKING - -from datadoc import enums -from datadoc.frontend.callbacks.utils import get_language_strings_enum -from datadoc.frontend.fields.display_base import VariablesCheckboxField -from datadoc.frontend.fields.display_base import VariablesDropdownField -from datadoc.frontend.fields.display_base import VariablesInputField -from datadoc.frontend.fields.display_base import get_multi_language_metadata - -if TYPE_CHECKING: - from datadoc.enums import SupportedLanguages - -logger = logging.getLogger(__name__) - - -def get_enum_options_for_language( - enum: Enum, - language: SupportedLanguages, -) -> list[dict[str, str]]: - """Generate the list of options based on the currently chosen language.""" - return [ - { - "title": i.get_value_for_language(language), - "id": i.name, - } - for i in get_language_strings_enum(enum) # type: ignore [attr-defined] - ] - - -class NewVariableIdentifiers(str, Enum): - """As defined here: https://statistics-norway.atlassian.net/wiki/spaces/MPD/pages/3042869256/Variabelforekomst.""" - - SHORT_NAME = "short_name" - NAME = "name" - DATA_TYPE = "data_type" - VARIABLE_ROLE = "variable_role" - DEFINITION_URI = "definition_uri" - DIRECT_PERSON_IDENTIFYING = "direct_person_identifying" - DATA_SOURCE = "data_source" - POPULATION_DESCRIPTION = "population_description" - COMMENT = "comment" - TEMPORALITY_TYPE = "temporality_type" - MEASUREMENT_UNIT = "measurement_unit" - FORMAT = "format" - CLASSIFICATION_URI = "classification_uri" - SENTINEL_VALUE_URI = "sentinel_value_uri" - INVALID_VALUE_DESCRIPTION = "invalid_value_description" - IDENTIFIER = "id" - CONTAINS_DATA_FROM = "contains_data_from" - CONTAINS_DATA_UNTIL = "contains_data_until" - - -DISPLAY_VARIABLES: dict[ - NewVariableIdentifiers, - VariablesInputField | VariablesDropdownField | VariablesCheckboxField, -] = { - NewVariableIdentifiers.SHORT_NAME: VariablesInputField( - identifier=NewVariableIdentifiers.SHORT_NAME.value, - display_name="Kortnavn", - description="Fysisk navn på variabelen i datasettet. Bør tilsvare anbefalt kortnavn.", - obligatory=True, - editable=False, - ), - NewVariableIdentifiers.NAME: VariablesInputField( - identifier=NewVariableIdentifiers.NAME.value, - display_name="Navn", - description="Variabelnavn kan arves fra VarDef, men kan også dokumenteres/endres her.", - obligatory=True, - multiple_language_support=True, - type="text", - ), - NewVariableIdentifiers.DATA_TYPE: VariablesDropdownField( - identifier=NewVariableIdentifiers.DATA_TYPE.value, - display_name="Datatype", - description="Datatype", - obligatory=True, - options_getter=functools.partial( - get_enum_options_for_language, - enums.DataType, - ), - ), - NewVariableIdentifiers.VARIABLE_ROLE: VariablesDropdownField( - identifier=NewVariableIdentifiers.VARIABLE_ROLE.value, - display_name="Variabelens rolle", - description="Variabelens rolle i datasett", - obligatory=True, - options_getter=functools.partial( - get_enum_options_for_language, - enums.VariableRole, - ), - ), - NewVariableIdentifiers.DEFINITION_URI: VariablesInputField( - identifier=NewVariableIdentifiers.DEFINITION_URI.value, - display_name="Definition URI", - description="En lenke (URI) til variabelens definisjon i SSB (Vardok/VarDef)", - url=True, - obligatory=True, - type="url", - ), - NewVariableIdentifiers.DIRECT_PERSON_IDENTIFYING: VariablesCheckboxField( - identifier=NewVariableIdentifiers.DIRECT_PERSON_IDENTIFYING.value, - display_name="Direkte personidentifiserende informasjon", - description="Direkte personidentifiserende informasjon (DPI)", - obligatory=True, - ), - NewVariableIdentifiers.DATA_SOURCE: VariablesInputField( - identifier=NewVariableIdentifiers.DATA_SOURCE.value, - display_name="Datakilde", - description="Datakilde. Settes på datasettnivå, men kan overstyres på variabelforekomstnivå.", - multiple_language_support=True, - type="text", - ), - NewVariableIdentifiers.POPULATION_DESCRIPTION: VariablesInputField( - identifier=NewVariableIdentifiers.POPULATION_DESCRIPTION.value, - display_name="Populasjonen", - description="Populasjonen variabelen beskriver kan spesifiseres nærmere her. Settes på datasettnivå, men kan overstyres på variabelforekomstnivå.", - multiple_language_support=True, - type="text", - ), - NewVariableIdentifiers.COMMENT: VariablesInputField( - identifier=NewVariableIdentifiers.COMMENT.value, - display_name="Kommentar", - description="Ytterligere presiseringer av variabeldefinisjon", - multiple_language_support=True, - type="text", - ), - NewVariableIdentifiers.MEASUREMENT_UNIT: VariablesInputField( - identifier=NewVariableIdentifiers.MEASUREMENT_UNIT.value, - display_name="Måleenhet", - description="Måleenhet. Eksempel: NOK eller USD for valuta, KG eller TONN for vekt. Se også forslag til SSBs måletyper/måleenheter.", - type="text", - ), - NewVariableIdentifiers.FORMAT: VariablesInputField( - identifier=NewVariableIdentifiers.FORMAT.value, - display_name="Format", - description="Verdienes format (fysisk format eller regulært uttrykk) i maskinlesbar form ifm validering. Dette kan benyttes som en ytterligere presisering av datatypen (dataType) i de tilfellene hvor dette er relevant. ", - ), - NewVariableIdentifiers.CLASSIFICATION_URI: VariablesInputField( - identifier=NewVariableIdentifiers.CLASSIFICATION_URI.value, - display_name="Kodeverkets URI", - description="Lenke (URI) til gyldige kodeverk (klassifikasjon eller kodeliste) i KLASS", - url=True, - type="url", - ), - NewVariableIdentifiers.SENTINEL_VALUE_URI: VariablesInputField( - identifier=NewVariableIdentifiers.SENTINEL_VALUE_URI.value, - display_name="Spesialverdienes URI", - description="En lenke (URI) til en oversikt over 'spesialverdier' som inngår i variabelen.", - url=True, - type="url", - ), - NewVariableIdentifiers.INVALID_VALUE_DESCRIPTION: VariablesInputField( - identifier=NewVariableIdentifiers.INVALID_VALUE_DESCRIPTION.value, - display_name="Ugyldige verdier", - description="En beskrivelse av ugyldige verdier som inngår i variabelen dersom spesialverdiene ikke er tilstrekkelige eller ikke kan benyttes.", - multiple_language_support=True, - ), - NewVariableIdentifiers.IDENTIFIER: VariablesInputField( - identifier=NewVariableIdentifiers.IDENTIFIER.value, - display_name="Unik ID", - description="Unik SSB identifikator for variabelforekomsten i datasettet", - obligatory=False, - editable=False, - ), - NewVariableIdentifiers.CONTAINS_DATA_FROM: VariablesInputField( - identifier=NewVariableIdentifiers.CONTAINS_DATA_FROM.value, - display_name="Inneholder data f.o.m.", - description="Variabelforekomsten i datasettet inneholder data fra og med denne dato.", - type="date", - ), - NewVariableIdentifiers.CONTAINS_DATA_UNTIL: VariablesInputField( - identifier=NewVariableIdentifiers.CONTAINS_DATA_UNTIL.value, - display_name="Inneholder data t.o.m.", - description="Variabelforekomsten i datasettet inneholder data til og med denne dato.", - type="date", - ), -} - - -for v in DISPLAY_VARIABLES.values(): - if v.multiple_language_support: - v.value_getter = get_multi_language_metadata - -# Copied from dataset_tab - not in use - should we use this? -MULTIPLE_LANGUAGE_VARIABLES_METADATA = [ - m.identifier for m in DISPLAY_VARIABLES.values() if m.multiple_language_support -] - -OBLIGATORY_VARIABLES_METADATA = [ - m for m in DISPLAY_VARIABLES.values() if m.obligatory and m.editable -] - -OPTIONAL_VARIABLES_METADATA = [ - m for m in DISPLAY_VARIABLES.values() if not m.obligatory and m.editable -] - -# Copied from dataset_tab - not in use - should we use this? -DISPLAYED_VARIABLES_METADATA: list[ - VariablesInputField | VariablesDropdownField | VariablesCheckboxField -] = (OBLIGATORY_VARIABLES_METADATA + OPTIONAL_VARIABLES_METADATA) - -# Copied from dataset_tab - not in use - should we use this? -OBLIGATORY_VARIABLES_METADATA_IDENTIFIERS: list[str] = [ - m.identifier for m in DISPLAY_VARIABLES.values() if m.obligatory and m.editable -] diff --git a/src/datadoc/frontend/fields/display_variables.py b/src/datadoc/frontend/fields/display_variables.py index 3031fca7..f38c91dc 100644 --- a/src/datadoc/frontend/fields/display_variables.py +++ b/src/datadoc/frontend/fields/display_variables.py @@ -2,12 +2,15 @@ from __future__ import annotations -import typing +import functools from enum import Enum -from datadoc_model import model - -from datadoc.frontend.fields.display_base import DisplayVariablesMetadata +from datadoc import enums +from datadoc.frontend.fields.display_base import VariablesCheckboxField +from datadoc.frontend.fields.display_base import VariablesDropdownField +from datadoc.frontend.fields.display_base import VariablesInputField +from datadoc.frontend.fields.display_base import get_enum_options_for_language +from datadoc.frontend.fields.display_base import get_multi_language_metadata class VariableIdentifiers(str, Enum): @@ -33,117 +36,138 @@ class VariableIdentifiers(str, Enum): CONTAINS_DATA_UNTIL = "contains_data_until" -DISPLAY_VARIABLES = { - VariableIdentifiers.SHORT_NAME: DisplayVariablesMetadata( +DISPLAY_VARIABLES: dict[ + VariableIdentifiers, + VariablesInputField | VariablesDropdownField | VariablesCheckboxField, +] = { + VariableIdentifiers.SHORT_NAME: VariablesInputField( identifier=VariableIdentifiers.SHORT_NAME.value, display_name="Kortnavn", description="Fysisk navn på variabelen i datasettet. Bør tilsvare anbefalt kortnavn.", obligatory=True, editable=False, ), - VariableIdentifiers.NAME: DisplayVariablesMetadata( + VariableIdentifiers.NAME: VariablesInputField( identifier=VariableIdentifiers.NAME.value, display_name="Navn", description="Variabelnavn kan arves fra VarDef, men kan også dokumenteres/endres her.", obligatory=True, multiple_language_support=True, + type="text", ), - VariableIdentifiers.DATA_TYPE: DisplayVariablesMetadata( + VariableIdentifiers.DATA_TYPE: VariablesDropdownField( identifier=VariableIdentifiers.DATA_TYPE.value, display_name="Datatype", description="Datatype", obligatory=True, - presentation="dropdown", + options_getter=functools.partial( + get_enum_options_for_language, + enums.DataType, + ), ), - VariableIdentifiers.VARIABLE_ROLE: DisplayVariablesMetadata( + VariableIdentifiers.VARIABLE_ROLE: VariablesDropdownField( identifier=VariableIdentifiers.VARIABLE_ROLE.value, display_name="Variabelens rolle", description="Variabelens rolle i datasett", obligatory=True, - presentation="dropdown", + options_getter=functools.partial( + get_enum_options_for_language, + enums.VariableRole, + ), ), - VariableIdentifiers.DEFINITION_URI: DisplayVariablesMetadata( + VariableIdentifiers.DEFINITION_URI: VariablesInputField( identifier=VariableIdentifiers.DEFINITION_URI.value, display_name="Definition URI", description="En lenke (URI) til variabelens definisjon i SSB (Vardok/VarDef)", url=True, obligatory=True, + type="url", ), - VariableIdentifiers.DIRECT_PERSON_IDENTIFYING: DisplayVariablesMetadata( + VariableIdentifiers.DIRECT_PERSON_IDENTIFYING: VariablesCheckboxField( identifier=VariableIdentifiers.DIRECT_PERSON_IDENTIFYING.value, - display_name="DPI", + display_name="Direkte personidentifiserende informasjon", description="Direkte personidentifiserende informasjon (DPI)", obligatory=True, - presentation="dropdown", ), - VariableIdentifiers.DATA_SOURCE: DisplayVariablesMetadata( + VariableIdentifiers.DATA_SOURCE: VariablesInputField( identifier=VariableIdentifiers.DATA_SOURCE.value, display_name="Datakilde", description="Datakilde. Settes på datasettnivå, men kan overstyres på variabelforekomstnivå.", multiple_language_support=True, + type="text", ), - VariableIdentifiers.POPULATION_DESCRIPTION: DisplayVariablesMetadata( + VariableIdentifiers.POPULATION_DESCRIPTION: VariablesInputField( identifier=VariableIdentifiers.POPULATION_DESCRIPTION.value, display_name="Populasjonen", description="Populasjonen variabelen beskriver kan spesifiseres nærmere her. Settes på datasettnivå, men kan overstyres på variabelforekomstnivå.", multiple_language_support=True, + type="text", ), - VariableIdentifiers.COMMENT: DisplayVariablesMetadata( + VariableIdentifiers.COMMENT: VariablesInputField( identifier=VariableIdentifiers.COMMENT.value, display_name="Kommentar", description="Ytterligere presiseringer av variabeldefinisjon", multiple_language_support=True, + type="text", ), - VariableIdentifiers.TEMPORALITY_TYPE: DisplayVariablesMetadata( + VariableIdentifiers.TEMPORALITY_TYPE: VariablesDropdownField( identifier=VariableIdentifiers.TEMPORALITY_TYPE.value, display_name="Temporalitetstype", description="Temporalitetstype. Settes enten for variabelforekomst eller datasett. Se Temporalitet, hendelser og forløp.", - presentation="dropdown", + options_getter=functools.partial( + get_enum_options_for_language, + enums.TemporalityTypeType, + ), ), - VariableIdentifiers.MEASUREMENT_UNIT: DisplayVariablesMetadata( + VariableIdentifiers.MEASUREMENT_UNIT: VariablesInputField( identifier=VariableIdentifiers.MEASUREMENT_UNIT.value, display_name="Måleenhet", description="Måleenhet. Eksempel: NOK eller USD for valuta, KG eller TONN for vekt. Se også forslag til SSBs måletyper/måleenheter.", + type="text", ), - VariableIdentifiers.FORMAT: DisplayVariablesMetadata( + VariableIdentifiers.FORMAT: VariablesInputField( identifier=VariableIdentifiers.FORMAT.value, display_name="Format", description="Verdienes format (fysisk format eller regulært uttrykk) i maskinlesbar form ifm validering. Dette kan benyttes som en ytterligere presisering av datatypen (dataType) i de tilfellene hvor dette er relevant. ", ), - VariableIdentifiers.CLASSIFICATION_URI: DisplayVariablesMetadata( + VariableIdentifiers.CLASSIFICATION_URI: VariablesInputField( identifier=VariableIdentifiers.CLASSIFICATION_URI.value, display_name="Kodeverkets URI", description="Lenke (URI) til gyldige kodeverk (klassifikasjon eller kodeliste) i KLASS", url=True, + type="url", ), - VariableIdentifiers.SENTINEL_VALUE_URI: DisplayVariablesMetadata( + VariableIdentifiers.SENTINEL_VALUE_URI: VariablesInputField( identifier=VariableIdentifiers.SENTINEL_VALUE_URI.value, display_name="Spesialverdienes URI", description="En lenke (URI) til en oversikt over 'spesialverdier' som inngår i variabelen.", url=True, + type="url", ), - VariableIdentifiers.INVALID_VALUE_DESCRIPTION: DisplayVariablesMetadata( + VariableIdentifiers.INVALID_VALUE_DESCRIPTION: VariablesInputField( identifier=VariableIdentifiers.INVALID_VALUE_DESCRIPTION.value, display_name="Ugyldige verdier", description="En beskrivelse av ugyldige verdier som inngår i variabelen dersom spesialverdiene ikke er tilstrekkelige eller ikke kan benyttes.", multiple_language_support=True, ), - VariableIdentifiers.IDENTIFIER: DisplayVariablesMetadata( + VariableIdentifiers.IDENTIFIER: VariablesInputField( identifier=VariableIdentifiers.IDENTIFIER.value, display_name="Unik ID", description="Unik SSB identifikator for variabelforekomsten i datasettet", obligatory=False, editable=False, ), - VariableIdentifiers.CONTAINS_DATA_FROM: DisplayVariablesMetadata( + VariableIdentifiers.CONTAINS_DATA_FROM: VariablesInputField( identifier=VariableIdentifiers.CONTAINS_DATA_FROM.value, display_name="Inneholder data f.o.m.", description="Variabelforekomsten i datasettet inneholder data fra og med denne dato.", + type="date", ), - VariableIdentifiers.CONTAINS_DATA_UNTIL: DisplayVariablesMetadata( + VariableIdentifiers.CONTAINS_DATA_UNTIL: VariablesInputField( identifier=VariableIdentifiers.CONTAINS_DATA_UNTIL.value, display_name="Inneholder data t.o.m.", description="Variabelforekomsten i datasettet inneholder data til og med denne dato.", + type="date", ), } @@ -151,22 +175,18 @@ class VariableIdentifiers(str, Enum): m.identifier for m in DISPLAY_VARIABLES.values() if m.multiple_language_support ] -URL_VARIABLES_METADATA = [m.identifier for m in DISPLAY_VARIABLES.values() if m.url] +for v in DISPLAY_VARIABLES.values(): + if v.multiple_language_support: + v.value_getter = get_multi_language_metadata -DISPLAYED_DROPDOWN_VARIABLES_METADATA = [ - m.identifier for m in DISPLAY_VARIABLES.values() if m.presentation == "dropdown" +OBLIGATORY_VARIABLES_METADATA = [ + m for m in DISPLAY_VARIABLES.values() if m.obligatory and m.editable ] -DISPLAYED_DROPDOWN_VARIABLES_TYPES = [] - -types = typing.get_type_hints(model.Variable) - -for m in DISPLAY_VARIABLES.values(): - if m.presentation == "dropdown": - field_type = typing.get_args(types[m.identifier])[0] - DISPLAYED_DROPDOWN_VARIABLES_TYPES.append(field_type) - - -OBLIGATORY_VARIABLES_METADATA = [ +OBLIGATORY_VARIABLES_METADATA_IDENTIFIERS = [ m.identifier for m in DISPLAY_VARIABLES.values() if m.obligatory and m.editable ] + +OPTIONAL_VARIABLES_METADATA = [ + m for m in DISPLAY_VARIABLES.values() if not m.obligatory and m.editable +] diff --git a/tests/frontend/callbacks/test_variables_callbacks.py b/tests/frontend/callbacks/test_variables_callbacks.py index 0c0248a3..9f698533 100644 --- a/tests/frontend/callbacks/test_variables_callbacks.py +++ b/tests/frontend/callbacks/test_variables_callbacks.py @@ -1,212 +1,24 @@ -"""Tests for the callbacks package.""" +"""Tests for the variables callbacks module.""" from __future__ import annotations import datetime -import random -from copy import deepcopy from typing import TYPE_CHECKING from typing import Any from uuid import UUID import pytest -from datadoc_model import model from pydantic_core import Url from datadoc import enums from datadoc import state -from datadoc.enums import DataType from datadoc.enums import SupportedLanguages -from datadoc.frontend.callbacks.utils import MetadataInputTypes -from datadoc.frontend.callbacks.utils import find_existing_language_string -from datadoc.frontend.callbacks.variables import ( - accept_variable_datatable_metadata_input, -) from datadoc.frontend.callbacks.variables import accept_variable_metadata_input -from datadoc.frontend.callbacks.variables import ( - update_variable_table_dropdown_options_for_language, -) -from datadoc.frontend.callbacks.variables import update_variable_table_language from datadoc.frontend.fields.display_variables import VariableIdentifiers if TYPE_CHECKING: from datadoc.backend.datadoc_metadata import DataDocMetadata - -DATA_ORIGINAL = [ - { - "short_name": "pers_id", - "variable_role": None, - }, -] -DATA_VALID = [ - { - "short_name": "pers_id", - "variable_role": "IDENTIFIER", - }, -] -DATA_NONETYPE = [ - {"short_name": "pers_id", "variable_role": "IDENTIFIER", "name": None}, -] -DATA_INVALID = [ - { - "short_name": "pers_id", - "variable_role": 3.1415, - }, -] -DATA_CLEAR_URI = [ - {"short_name": "pers_id", "variable_role": None, "definition_uri": ""}, -] - - -@pytest.fixture() -def active_cell() -> dict[str, MetadataInputTypes]: - return {"row": 1, "column": 1, "column_id": "short_name", "row_id": None} - - -def test_accept_variable_table_metadata_input_no_change_in_data( - metadata: DataDocMetadata, - active_cell: dict[str, MetadataInputTypes], -): - state.metadata = metadata - output = accept_variable_datatable_metadata_input( - DATA_ORIGINAL, - active_cell, - DATA_ORIGINAL, - ) - assert output[0] == DATA_ORIGINAL - assert output[1] is False - assert output[2] == "" - - -def test_accept_variable_table_metadata_input_new_data( - metadata: DataDocMetadata, - active_cell: dict[str, MetadataInputTypes], -): - state.metadata = metadata - output = accept_variable_datatable_metadata_input( - DATA_VALID, - active_cell, - DATA_ORIGINAL, - ) - - assert state.metadata.variables_lookup["pers_id"].variable_role == "IDENTIFIER" - assert output[0] == DATA_VALID - assert output[1] is False - assert output[2] == "" - - -def test_accept_variable_table_metadata_clear_string( - metadata: DataDocMetadata, - active_cell: dict[str, MetadataInputTypes], -): - state.metadata = metadata - output = accept_variable_datatable_metadata_input( - DATA_CLEAR_URI, - active_cell, - DATA_ORIGINAL, - ) - - assert state.metadata.variables_lookup["pers_id"].definition_uri is None - assert output[0] == DATA_CLEAR_URI - assert output[1] is False - assert output[2] == "" - - -def test_accept_variable_table_metadata_input_incorrect_data_type( - metadata: DataDocMetadata, - active_cell: dict[str, MetadataInputTypes], -): - state.metadata = metadata - previous_metadata = deepcopy(state.metadata.variables) - output = accept_variable_datatable_metadata_input( - DATA_INVALID, - active_cell, - DATA_ORIGINAL, - ) - - assert output[0] == DATA_ORIGINAL - assert output[1] is True - assert "validation error for Variable" in output[2] - assert state.metadata.variables == previous_metadata - - -def test_find_existing_language_string_pre_existing_strings( - english_name: str, - bokmål_name: str, - nynorsk_name: str, - language_object: model.LanguageStringType, -): - dataset_metadata = model.Dataset() - dataset_metadata.name = language_object - state.current_metadata_language = SupportedLanguages.NORSK_NYNORSK - language_strings = find_existing_language_string( - dataset_metadata, - nynorsk_name, - "name", - ) - assert language_strings == model.LanguageStringType( - nb=bokmål_name, - en=english_name, - nn=nynorsk_name, - ) - - -def test_update_variable_table_language( - metadata: DataDocMetadata, - bokmål_name: str, - language_object: model.LanguageStringType, -): - state.metadata = metadata - test_variable = random.choice( # noqa: S311 not for cryptographic purposes - [v.short_name for v in state.metadata.variables], - ) - state.metadata.variables_lookup[test_variable].name = language_object - output = update_variable_table_language( - SupportedLanguages.NORSK_BOKMÅL, - ) - try: - name = next( - d[VariableIdentifiers.NAME.value] - for d in output[0] - if d[VariableIdentifiers.SHORT_NAME.value] == test_variable - ) - except StopIteration as e: - msg = f"Could not find name for {test_variable = } in {output = }" - raise AssertionError(msg) from e - assert name == bokmål_name - - -def test_nonetype_value_for_language_string( - metadata: DataDocMetadata, - active_cell: dict[str, MetadataInputTypes], - language_object: model.LanguageStringType, -): - state.metadata = metadata - state.metadata.variables_lookup["pers_id"].name = language_object - state.current_metadata_language = SupportedLanguages.NORSK_NYNORSK - accept_variable_datatable_metadata_input(DATA_NONETYPE, active_cell, DATA_ORIGINAL) - - assert state.metadata.variables_lookup["pers_id"].name == language_object - - -def test_update_variable_table_dropdown_options_for_language(): - options = update_variable_table_dropdown_options_for_language( - SupportedLanguages.NORSK_BOKMÅL, - ) - assert all(k in model.Variable.model_fields for k in options) - assert all(list(v.keys()) == ["options"] for v in options.values()) - try: - assert all( - list(d.keys()) == ["label", "value"] - for v in options.values() - for d in next(iter(v.values())) - ) - except StopIteration as e: - msg = f"Could not extract actual value from {options.values() = }" - raise AssertionError(msg) from e - assert [d["label"] for d in options["data_type"]["options"]] == [ - i.get_value_for_language(SupportedLanguages.NORSK_BOKMÅL) for i in DataType - ] + from datadoc.frontend.callbacks.utils import MetadataInputTypes @pytest.mark.parametrize(