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..0de4059 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,18 @@ 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, subsystems and components " + "on the OBS jira project. An example of a valid payload is: " + '`{"systems": ["Simonyi", "AuxTel"], ' + '{"subsystems": ["TMA", "Mount"], ' + '{"components": ["MTMount CSC"]}`. ' + "For a full list of valid systems, subsystems and components " + "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 +111,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..d0a6f5d 100644 --- a/src/narrativelog/routers/add_message.py +++ b/src/narrativelog/routers/add_message.py @@ -41,37 +41,62 @@ 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=None, + description="JSON representation of systems, subsystems and components " + "on the OBS jira project. An example of a valid payload is: " + '`{"systems": ["Simonyi", "AuxTel"], ' + '{"subsystems": ["TMA", "Mount"], ' + '{"components": ["MTMount CSC"]}`. ' + "For a full list of valid systems, subsystems and components " + "please refer to: [Systems, Sub-Systems and Components Proposal " + "for the OBS Jira project](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 +172,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 +197,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..c9a1e7e 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,49 @@ 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=None, + description="JSON representation of systems, subsystems and components " + "on the OBS jira project. An example of a valid payload is: " + '`{"systems": ["Simonyi", "AuxTel"], ' + '{"subsystems": ["TMA", "Mount"], ' + '{"components": ["MTMount CSC"]}`. ' + "For a full list of valid systems, subsystems and components " + "please refer to: [Systems, Sub-Systems and Components Proposal " + "for the OBS Jira project](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 +168,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 +203,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..b38d7c9 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,72 @@ 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 to include. " + 'All messages with a "components_json" field that ' + "matches at least a key with any of the values specified within " + "it are included. The JSON object represents the current " + "hierarchy of systems, subsystems and components on the OBS Jira project: " + '`{"systems": ["system1", ..., "systemN"], ' + '"subsystems": ["subsystem1", ..., "subsystemN"], ' + '"components": ["component1", ..., "componentN"]}`. ' + 'E.g. Setting "components_path" to `{"systems": ["AuxTel", "Simonyi"], ' + '"subsystems": ["Mount", "TMA"], ' + '"components": ["ATMCS CSC"]}` will match ' + 'all messages that have "AuxTel" OR ' + '"Simonyi" values under the "systems" key ' + 'OR have "Mount" OR "TMA" values under the "subsystems" key ' + 'OR have "ATMCS CSC" value under the "components" key. ' + 'Note that setting "components_path" to `{"subsystems": ' + '["Mount", "TMA"]}` is the same as setting it to ' + '`{"subsystems": ["TMA", "Mount"]}` so will end up ' + 'in the same result. Also setting it to `{"subsystems": []}` will ' + 'include all messages that have at least the "subsystems" key defined. ' + "Any key with a value that is not a list will be ignored. " + 'Furthermore setting "components_path" to `{}` will have no effect and ' + "an invalid JSON will raise a 400 error.", + ), + exclude_components_path: None + | str = fastapi.Query( + default=None, + description="Components structure in JSON format to exclude. " + 'All messages with a "components_json" field that ' + "matches at least a key with any of the values specified within " + "it are excluded. The JSON object represents the current " + "hierarchy of systems, subsystems and components on the OBS Jira project: " + '`{"systems": ["system1", ..., "systemN"], ' + '"subsystems": ["subsystem1", ..., "subsystemN"], ' + '"components": ["component1", ..., "componentN"]}`. ' + 'E.g. Setting "exclude_components_path" to `{"systems": ["AuxTel", ' + '"Simonyi"], "subsystems": ["Mount", "TMA"], ' + '"components": ["ATMCS CSC"]}` will match ' + 'all messages that have "AuxTel" OR ' + '"Simonyi" values under the "systems" key ' + 'OR have "Mount" OR "TMA" values under the "subsystems" key ' + 'OR have "ATMCS CSC" value under the "components" key. ' + 'Note that setting "exclude_components_path" to `{"subsystems": ' + '["Mount", "TMA"]}` is the same as setting it to ' + '`{"subsystems": ["TMA", "Mount"]}` so will end up ' + 'in the same result. Also setting it to `{"subsystems": []}` will ' + 'include all messages that have at least the "subsystems" key defined. ' + "Any key with a value that is not a list will be ignored. " + 'Furthermore setting "exclude_components_path" to `{}` will have no effect ' + "and an invalid JSON will raise a 400 error.", ), urls: None | list[str] = fastapi.Query( @@ -299,18 +378,44 @@ 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", + "exclude_components_path", "tags", "exclude_tags", "urls", @@ -386,8 +491,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 +517,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 +548,59 @@ 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] + individual_conditions = [] + for key in parsed_value: + value = parsed_value[key] + if not value or not isinstance(value, list): + continue + for element in value: + path = {key: [element]} + individual_conditions.append(column.contains(path)) + conditions.append(sa.sql.or_(*individual_conditions)) + elif key in {"exclude_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] + individual_conditions = [] + for key in parsed_value: + value = parsed_value[key] + if not value or not isinstance(value, list): + continue + for element in value: + path = {key: [element]} + individual_conditions.append(column.contains(path)) + conditions.append( + sa.sql.not_(sa.sql.or_(*individual_conditions)) + ) elif key in { "site_ids", "instruments",