From 08cd2638a1c015ed121617d0b4b648b2a178440a Mon Sep 17 00:00:00 2001 From: Sebastian Aranda Date: Mon, 16 Dec 2024 18:28:37 -0300 Subject: [PATCH] Add new components_json field to the jira_fields table. Also add deprecation advice and code comments for systems, subsystems, cscs, components, primary_software_components and primary_hardware_components. --- ...3f83_add_components_json_field_to_jira_.py | 42 ++++++ src/narrativelog/create_tables.py | 16 ++- src/narrativelog/message.py | 41 +++++- src/narrativelog/routers/add_message.py | 54 +++++++- src/narrativelog/routers/edit_message.py | 52 +++++++- src/narrativelog/routers/find_messages.py | 123 ++++++++++++++++-- 6 files changed, 297 insertions(+), 31 deletions(-) create mode 100644 alembic/versions/49ef39173f83_add_components_json_field_to_jira_.py diff --git a/alembic/versions/49ef39173f83_add_components_json_field_to_jira_.py b/alembic/versions/49ef39173f83_add_components_json_field_to_jira_.py new file mode 100644 index 0000000..c2f599d --- /dev/null +++ b/alembic/versions/49ef39173f83_add_components_json_field_to_jira_.py @@ -0,0 +1,42 @@ +"""add components_json field to jira_fields table + +Revision ID: 49ef39173f83 +Revises: 54f755dbdb6f +Create Date: 2024-12-18 17:01:39.676895 + +""" +import logging + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "49ef39173f83" +down_revision = "54f755dbdb6f" +branch_labels = None +depends_on = None + + +JIRA_FIELDS_TABLE_NAME = "jira_fields" + + +def upgrade(log: logging.Logger, table_names: set[str]) -> None: + if JIRA_FIELDS_TABLE_NAME not in table_names: + log.info(f"No {JIRA_FIELDS_TABLE_NAME} table; nothing to do") + return + log.info("Add 'components_json'") + + op.add_column( + JIRA_FIELDS_TABLE_NAME, + sa.Column("components_json", sa.JSON(), nullable=True), + ) + + +def downgrade(log: logging.Logger, table_names: set[str]) -> None: + if JIRA_FIELDS_TABLE_NAME not in table_names: + log.info(f"No {JIRA_FIELDS_TABLE_NAME} table; nothing to do") + return + + log.info("Drop 'components_json'") + op.drop_column(JIRA_FIELDS_TABLE_NAME, "components_json") diff --git a/src/narrativelog/create_tables.py b/src/narrativelog/create_tables.py index 9ed05c0..5b95d57 100644 --- a/src/narrativelog/create_tables.py +++ b/src/narrativelog/create_tables.py @@ -8,7 +8,7 @@ import sqlalchemy as sa import sqlalchemy.types as saty -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.dialects.postgresql import JSONB, UUID # Length of the site_id field. SITE_ID_LEN = 16 @@ -60,8 +60,14 @@ def create_message_table(metadata: sa.MetaData) -> sa.Table: sa.Column("date_invalidated", saty.DateTime(), nullable=True), sa.Column("parent_id", UUID(as_uuid=True), nullable=True), # Added 2022-07-19 + # 'systems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead sa.Column("systems", saty.ARRAY(sa.Text), nullable=True), + # 'subsystems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead sa.Column("subsystems", saty.ARRAY(sa.Text), nullable=True), + # 'cscs' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead sa.Column("cscs", saty.ARRAY(sa.Text), nullable=True), # Added 2022-07-37 sa.Column("date_end", saty.DateTime(), nullable=True), @@ -110,10 +116,18 @@ def create_jira_fields_table(metadata: sa.MetaData) -> sa.Table: sa.Column( "id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ), + # Added 2024-12-16 + sa.Column("components_json", JSONB, nullable=True), + # 'components' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead sa.Column("components", saty.ARRAY(sa.Text), nullable=True), + # 'primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead sa.Column( "primary_software_components", saty.ARRAY(sa.Text), nullable=True ), + # 'primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead sa.Column( "primary_hardware_components", saty.ARRAY(sa.Text), nullable=True ), diff --git a/src/narrativelog/message.py b/src/narrativelog/message.py index d009a4e..ab855b2 100644 --- a/src/narrativelog/message.py +++ b/src/narrativelog/message.py @@ -46,13 +46,21 @@ class Message(BaseModel): ) # Added 2022-07-19 systems: None | list[str] = Field( - title="Zero or more system names.", + title="Zero or more system names. " + "This field is deprecated and will be removed in v1.0.0. " + "Please use 'components_json' instead.", + ) + subsystems: None | list[str] = Field( + title="Zero or more subsystem names. " + "This field is deprecated and will be removed in v1.0.0. " + "Please use 'components_json' instead.", ) - subsystems: None | list[str] = Field(title="Zero or more subsystem names.") cscs: None | list[str] = Field( title="Zero or more CSCs names. " "Each entry should be in the form 'name' or 'name:index', " - "where 'name' is the SAL component name and 'index' is the SAL index." + "where 'name' is the SAL component name and 'index' is the SAL index. " + "This field is deprecated and will be removed in v1.0.0. " + "Please use 'components_json' instead.", ) # Added 2022-07-27 date_end: None | datetime.datetime = Field( @@ -61,15 +69,21 @@ class Message(BaseModel): # Added 2023-08-10 components: None | list[str] = Field( title="Zero or more component names. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "This field is deprecated and will be removed in v1.0.0. " + "Please use 'components_json' instead.", ) primary_software_components: None | list[str] = Field( title="Zero or more primary software component names. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "This field is deprecated and will be removed in v1.0.0. " + "Please use 'components_json' instead.", ) primary_hardware_components: None | list[str] = Field( title="Zero or more primary hardware component names. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "This field is deprecated and will be removed in v1.0.0. " + "Please use 'components_json' instead.", ) # Added 2023-10-24 category: None | str = Field( @@ -78,6 +92,14 @@ class Message(BaseModel): time_lost_type: None | str = Field( title="Type of time lost.", ) + # Added 2024-12-16 + components_json: None | dict = Field( + default_factory=dict, + title="JSON representation of systems and subsystems on the OBS jira project. " + "For a full list of valid keys please refer to: " + "https://rubinobs.atlassian.net/wiki/spaces/LSSTCOM/pages/53741849" + "/Systems+Sub-Systems+and+Components+Proposal+for+JIRA", + ) class Config: orm_mode = True @@ -85,9 +107,16 @@ class Config: JIRA_FIELDS = ( + # 'components' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead "components", + # 'primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead "primary_software_components", + # 'primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead "primary_hardware_components", + "components_json", ) MESSAGE_FIELDS = tuple( set(Message.schema()["properties"].keys()) - set(JIRA_FIELDS) diff --git a/src/narrativelog/routers/add_message.py b/src/narrativelog/routers/add_message.py index 74c41c7..23371d6 100644 --- a/src/narrativelog/routers/add_message.py +++ b/src/narrativelog/routers/add_message.py @@ -41,37 +41,57 @@ async def add_message( systems: None | list[str] = fastapi.Body( default=None, - description="Zero or more systems to which the message applies.", + description="Zero or more systems to which the message applies. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), subsystems: None | list[str] = fastapi.Body( default=None, - description="Zero or more subsystems to which the message applies", + description="Zero or more subsystems to which the message applies. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), cscs: None | list[str] = fastapi.Body( default=None, description="Zero or more CSCs to which the message applies. " "Each entry should be in the form 'name' or 'name:index', " - "where 'name' is the SAL component name and 'index' is the SAL index.", + "where 'name' is the SAL component name and 'index' is the SAL index. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), components: None | list[str] = fastapi.Body( default=None, description="Zero or more components to which the message applies. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), primary_software_components: None | list[str] = fastapi.Body( default=None, description="Primary software components to which the message applies. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), primary_hardware_components: None | list[str] = fastapi.Body( default=None, description="Primary hardware components to which the message applies. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", + ), + components_json: None + | dict = fastapi.Body( + default={}, + description="JSON representation of systems and subsystems " + "on the OBS jira project. For a full list of valid keys please refer to: " + "https://rubinobs.atlassian.net/wiki/spaces/LSSTCOM/pages/53741849" + "/Systems+Sub-Systems+and+Components+Proposal+for+JIRA", ), urls: list[str] = fastapi.Body( default=[], @@ -147,8 +167,14 @@ async def add_message( user_agent=user_agent, is_human=is_human, date_added=curr_tai.tai.datetime, + # 'systems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead systems=systems, + # 'subsystems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead subsystems=subsystems, + # 'cscs' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead cscs=cscs, category=category, time_lost_type=time_lost_type, @@ -166,17 +192,33 @@ async def add_message( if any( field is not None for field in ( + # 'components' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead components, + # 'primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead primary_software_components, + # 'primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead primary_hardware_components, + components_json, ) ): result_jira_fields = await connection.execute( jira_fields_table.insert() .values( + # 'components' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead components=components, + # 'primary_software_components' field is deprecated + # and will be removed in v1.0.0. + # Please use 'components_json' instead primary_software_components=primary_software_components, + # 'primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. + # Please use 'components_json' instead primary_hardware_components=primary_hardware_components, + components_json=components_json, message_id=row_message.id, ) .returning(sa.literal_column("*")) diff --git a/src/narrativelog/routers/edit_message.py b/src/narrativelog/routers/edit_message.py index a1efe7c..43038a0 100644 --- a/src/narrativelog/routers/edit_message.py +++ b/src/narrativelog/routers/edit_message.py @@ -44,13 +44,17 @@ async def edit_message( | list[str] = fastapi.Body( default=None, description="Zero or more systems to which the message applied. " - "If specified, replaces all existing entries.", + "If specified, replaces all existing entries." + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), subsystems: None | list[str] = fastapi.Body( default=None, description="Zero or more subsystems to which the message applies. " - "If specified, replaces all existing entries.", + "If specified, replaces all existing entries." + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), cscs: None | list[str] = fastapi.Body( @@ -58,28 +62,44 @@ async def edit_message( description="Zero or more CSCs to which the message applies. " "Each entry should be in the form 'name' or 'name:index', " "where 'name' is the SAL component name and 'index' is the SAL index. " - "If specified, replaces all existing entries.", + "If specified, replaces all existing entries." + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), components: None | list[str] = fastapi.Body( default=None, description="Zero or more components to which the message applies. " "Each entry should be a valid component name entry on the OBS jira project. " - "If specified, replaces all existing entries.", + "If specified, replaces all existing entries." + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), primary_software_components: None | list[str] = fastapi.Body( default=None, description="Primary software components to which the message applies. " "Each entry should be a valid component name entry on the OBS jira project. " - "If specified, replaces all existing entries.", + "If specified, replaces all existing entries." + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", ), primary_hardware_components: None | list[str] = fastapi.Body( default=None, description="Primary hardware components to which the message applies. " "Each entry should be a valid component name entry on the OBS jira project. " - "If specified, replaces all existing entries.", + "If specified, replaces all existing entries." + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_json' instead.", + ), + components_json: None + | dict = fastapi.Body( + default={}, + description="JSON representation of systems and subsystems " + "on the OBS jira project. For a full list of valid keys please refer to: " + "https://rubinobs.atlassian.net/wiki/spaces/LSSTCOM/pages/53741849" + "/Systems+Sub-Systems+and+Components+Proposal+for+JIRA", ), urls: None | list[str] = fastapi.Body( @@ -143,12 +163,25 @@ async def edit_message( "message_text", "level", "tags", + # 'systems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead "systems", + # 'subsystems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead "subsystems", + # 'cscs' field is deprecated and will be removed in v1.0.0. + # Please use 'components_json' instead "cscs", + # 'components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead "components", + # 'primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead "primary_software_components", + # 'primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead "primary_hardware_components", + "components_json", "category", "time_lost_type", "urls", @@ -165,9 +198,16 @@ async def edit_message( request_data[name] = value jira_update_params = { + # 'components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead "components", + # 'primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead "primary_software_components", + # 'primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_json' instead "primary_hardware_components", + "components_json", } async with state.narrativelog_db.engine.begin() as connection: diff --git a/src/narrativelog/routers/find_messages.py b/src/narrativelog/routers/find_messages.py index e345e88..2008450 100644 --- a/src/narrativelog/routers/find_messages.py +++ b/src/narrativelog/routers/find_messages.py @@ -3,6 +3,7 @@ import datetime import enum import http +import json import fastapi import sqlalchemy as sa @@ -101,42 +102,54 @@ async def find_messages( default=None, description="System names or fragments of names. All messages " "with a system that matches any of these are included. " - "Repeat the parameter for each value.", + "Repeat the parameter for each value. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), exclude_systems: None | list[str] = fastapi.Query( default=None, description="System names or fragments of names. All messages " "with a system that matches any of these are excluded. " - "Repeat the parameter for each value.", + "Repeat the parameter for each value. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), subsystems: None | list[str] = fastapi.Query( default=None, description="Subsystem names or fragments of names. All messages " "with a subsystem that matches any of these are included. " - "Repeat the parameter for each value.", + "Repeat the parameter for each value. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), exclude_subsystems: None | list[str] = fastapi.Query( default=None, description="Subsystem names or fragments of names. All messages " "with a subsystem that matches any of these are excluded. " - "Repeat the parameter for each value.", + "Repeat the parameter for each value. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), cscs: None | list[str] = fastapi.Query( default=None, description="CSC names or fragments of CSC names, " "of which at least one must be present. " - "Repeat the parameter for each value.", + "Repeat the parameter for each value. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), exclude_cscs: None | list[str] = fastapi.Query( default=None, description="CSC names or fragments of CSC names, " "of which all must be absent. " - "Repeat the parameter for each value.", + "Repeat the parameter for each value. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), components: None | list[str] = fastapi.Query( @@ -144,7 +157,9 @@ async def find_messages( description="Component names or fragments of names. All messages " "with a component that matches any of these are included. " "Repeat the parameter for each value. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), exclude_components: None | list[str] = fastapi.Query( @@ -152,7 +167,9 @@ async def find_messages( description="Component names or fragments of names. All messages " "with a component that matches any of these are excluded. " "Repeat the parameter for each value. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), primary_software_components: None | list[str] = fastapi.Query( @@ -160,7 +177,9 @@ async def find_messages( description="Primary software components names or fragments of names. " "All messages with a component that matches any of these are included. " "Repeat the parameter for each value. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), exclude_primary_software_components: None | list[str] = fastapi.Query( @@ -168,7 +187,9 @@ async def find_messages( description="Primary software components names or fragments of names. " "All messages with a component that matches any of these are excluded. " "Repeat the parameter for each value. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), primary_hardware_components: None | list[str] = fastapi.Query( @@ -176,14 +197,32 @@ async def find_messages( description="Primary hardware components names or fragments of names. " "All messages with a component that matches any of these are included. " "Repeat the parameter for each value. " - "Each entry should be a valid component name entry on the OBS jira project.", + "Each entry should be a valid component name entry on the OBS jira project. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", ), exclude_primary_hardware_components: None | list[str] = fastapi.Query( default=None, description="Primary hardware components names or fragments of names. " "All messages with a component that matches any of these are excluded. " - "Repeat the parameter for each value.", + "Repeat the parameter for each value. " + "**This field is deprecated and will be removed in v1.0.0**. " + "Please use 'components_path' instead.", + ), + components_path: None + | str = fastapi.Query( + default=None, + description="Components structure in JSON format." + "All messages with a 'components_json' field that " + "matches the specified structure are included." + "The structure represents the hierarchy of " + "systems and subsystems on the OBS jira project. " + "Current structure: `{'systems': ['system1', ..., 'systemN'], " + "'subsystems': ['subsystem1', ..., 'subsystemN'], " + "'components': ['component1', ..., 'componentN']}`. " + "E.g. `{'systems': ['AuxTel'], 'subsystems': ['ATCamera']}` will match" + " all messages that have 'AuxTel' as system and 'ATCamera' as subsystem.", ), urls: None | list[str] = fastapi.Query( @@ -299,18 +338,43 @@ async def find_messages( "max_level", "user_ids", "user_agents", + # 'systems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "systems", + # 'exclude_systems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "exclude_systems", + # 'subsystems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "subsystems", + # 'exclude_subsystems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "exclude_subsystems", + # 'cscs' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "cscs", + # 'exclude_cscs' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "exclude_cscs", + # 'components' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "components", + # 'exclude_components' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "exclude_components", + # 'primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "primary_software_components", + # 'exclude_primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "exclude_primary_software_components", + # 'primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "primary_hardware_components", + # 'exclude_primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "exclude_primary_hardware_components", + "components_path", "tags", "exclude_tags", "urls", @@ -386,8 +450,14 @@ async def find_messages( conditions.append(column == None) # noqa elif key in { "tags", + # 'systems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "systems", + # 'subsystems' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "subsystems", + # 'cscs' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "cscs", "urls", }: @@ -406,16 +476,28 @@ async def find_messages( column = message_table.columns[key] conditions.append(column.op("&&")(value)) elif key in { + # 'components' field is deprecated and will be removed in v1.0.0. + # Please use 'components_path' instead "components", + # 'primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "primary_software_components", + # 'primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "primary_hardware_components", }: column = jira_fields_table.columns[key] conditions.append(column.op("&&")(value)) elif key in { "exclude_tags", + # 'exclude_systems' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "exclude_systems", + # 'exclude_subsystems' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "exclude_subsystems", + # 'exclude_cscs' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "exclude_cscs", }: # Value is a list; field name is the end of the key. @@ -425,13 +507,30 @@ async def find_messages( column = message_table.columns[column_name] conditions.append(sa.sql.not_(column.op("&&")(value))) elif key in { + # 'exclude_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "exclude_components", + # 'exclude_primary_software_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "exclude_primary_software_components", + # 'exclude_primary_hardware_components' field is deprecated + # and will be removed in v1.0.0. Please use 'components_path' instead "exclude_primary_hardware_components", }: column_name = key[8:] column = jira_fields_table.columns[column_name] conditions.append(sa.sql.not_(column.op("&&")(value))) + elif key in {"components_path"}: + try: + parsed_value = json.loads(value) + except json.JSONDecodeError as error: + raise fastapi.HTTPException( + status_code=http.HTTPStatus.BAD_REQUEST, + detail=f"Invalid JSON in {key}: {error}", + ) + column_name = "components_json" + column = jira_fields_table.columns[column_name] + conditions.append(column.contains(parsed_value)) elif key in { "site_ids", "instruments",