Skip to content

Commit

Permalink
Refactor components_json field structure to represent hierarchy among…
Browse files Browse the repository at this point in the history
… systems, subsystems and components.
  • Loading branch information
sebastian-aranda committed Jan 6, 2025
1 parent 36ad769 commit f31a7c9
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 94 deletions.
30 changes: 22 additions & 8 deletions src/narrativelog/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from pydantic import BaseModel, Field

from .utils import JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK


class Message(BaseModel):
id: uuid.UUID = Field(title="Message ID: a UUID that is the primary key.")
Expand Down Expand Up @@ -95,14 +97,26 @@ class Message(BaseModel):
# 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",
title="""
JSON representation of systems, subsystems and components hierarchy
on the OBS jira project. An example of a valid payload is:
{
"name": "AuxTel",
"children": [
{
"name": "Dome",
"children": [
{
"name": "AT Azimuth Drives"
}
]
}
]
}
For a full list of valid systems, subsystems and components please refer to: """
f"""{JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK}.""",
)

class Config:
Expand Down
30 changes: 21 additions & 9 deletions src/narrativelog/routers/add_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from ..message import Message
from ..shared_state import SharedState, get_shared_state
from ..utils import JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK
from .normalize_tags import TAG_DESCRIPTION, normalize_tags

router = fastapi.APIRouter()
Expand Down Expand Up @@ -88,15 +89,26 @@ async def add_message(
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)",
description="""
JSON representation of systems, subsystems and components hierarchy
on the OBS jira project. An example of a valid payload is:
{
"name": "AuxTel",
"children": [
{
"name": "Dome",
"children": [
{
"name": "AT Azimuth Drives"
}
]
}
]
}
For a full list of valid systems, subsystems and components please refer to: """
f"""{JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK}.""",
),
urls: list[str] = fastapi.Body(
default=[],
Expand Down
243 changes: 166 additions & 77 deletions src/narrativelog/routers/find_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from ..message import MESSAGE_ORDER_BY_VALUES, Message
from ..shared_state import SharedState, get_shared_state
from ..utils import JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK
from .normalize_tags import TAG_DESCRIPTION, normalize_tags

router = fastapi.APIRouter()
Expand Down Expand Up @@ -211,59 +212,157 @@ async def find_messages(
"Please use 'components_path' instead.",
),
components_path: None
| str = fastapi.Query(
| list[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 "
'the "components_path" 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.",
description="""
List of strings with components structure in JSON format to include.
Matches all messages with at least a JSON path included in the "components_path" list.
Each element in the list is a JSON string as the object that represents the current
hierarchy of systems, subsystems and components on the OBS Jira project:
`System -> Subsystem -> Component`
(check """
f"""{JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK}"""
""" for valid hierarchies).
E.g. Setting a "components_path" string (you can add multiple) to
{
"name": "AuxTel",
"children": [
{
"name": "Dome",
"children": [
{
"name": "AT Azimuth Drives"
}
]
}
]
}
will match all messages that have "AuxTel" as system AND
"Dome" as subsystem of "AuxTel" AND
"AT Azimuth Drives" as component of "Dome".
Note that setting "components_path" to
{
"name": "AuxTel",
"children": [
{
"name": "Dome"
},
{
"name": "Mount"
}
]
}
Is the same as setting it to
{
"name": "AuxTel",
"children": [
{
"name": "Mount"
},
{
"name": "Dome"
}
]
}
In other words, the order of the children is not important.
Also setting a `children` key as `[]` is the same as not defining it
(will be ignored). Furthermore setting a "components_path" string
to `{}` will have no effect and any invalid JSON will raise a 400 error.
Take in mind that values are case senstive.
All defined JSON strings will be joined with an OR operator.
E.g. setting two "components_path" as
{ "name": "AuxTel" }
and
{ "name": "Simonyi" }
Will match all messages that have "AuxTel" OR "Simonyi" as system.""",
),
exclude_components_path: None
| str = fastapi.Query(
| list[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 "
'the "exclude_components_path" 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.",
description="""
List of strings with components structure in JSON format to exclude.
Exclude all messages with at least a JSON path
included in the "exclude_components_path" list.
Each element in the list is a JSON string as the object that represents the current
hierarchy of systems, subsystems and components on the OBS Jira project:
`System -> Subsystem -> Component`
(check """
f"""{JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK}"""
""" for valid hierarchies).
E.g. Setting a "exclude_components_path" string (you can add multiple) to
{
"name": "AuxTel",
"children": [
{
"name": "Dome",
"children": [
{
"name": "AT Azimuth Drives"
}
]
}
]
}
will exclude all messages that have "AuxTel" as system AND
"Dome" as subsystem of "AuxTel" AND
"AT Azimuth Drives" as component of "Dome".
Note that setting "exclude_components_path" to
{
"name": "AuxTel",
"children": [
{
"name": "Dome"
},
{
"name": "Mount"
}
]
}
Is the same as setting it to
{
"name": "AuxTel",
"children": [
{
"name": "Mount"
},
{
"name": "Dome"
}
]
}
In other words, the order of the children is not important.
Also setting a `children` key as `[]` is the same as not defining it
(will be ignored). Furthermore setting a "exclude_components_path" string
to `{}` will have no effect and any invalid JSON will raise a 400 error.
Take in mind that values are case senstive.
All defined JSON strings will be joined with an OR operator.
E.g. setting two "exclude_components_path" as
{ "name": "AuxTel" }
and
{ "name": "Simonyi" }
Will exclude all messages that have "AuxTel" OR "Simonyi" as system.""",
),
urls: None
| list[str] = fastapi.Query(
Expand Down Expand Up @@ -563,42 +662,32 @@ async def find_messages(
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))
for path in value:
try:
parsed_value = json.loads(path)
except json.JSONDecodeError as error:
raise fastapi.HTTPException(
status_code=http.HTTPStatus.BAD_REQUEST,
detail=f"Invalid JSON in {key}: {error}",
)
individual_conditions.append(column.contains(parsed_value))
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))
for path in value:
try:
parsed_value = json.loads(path)
except json.JSONDecodeError as error:
raise fastapi.HTTPException(
status_code=http.HTTPStatus.BAD_REQUEST,
detail=f"Invalid JSON in {key}: {error}",
)
individual_conditions.append(column.contains(parsed_value))
conditions.append(
sa.sql.not_(sa.sql.or_(*individual_conditions))
)
Expand Down
7 changes: 7 additions & 0 deletions src/narrativelog/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__all__ = ["JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK"]

JIRA_OBS_SYSTEMS_HIERARCHY_MARKDOWN_LINK = (
"[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)"
)

0 comments on commit f31a7c9

Please sign in to comment.