Skip to content
55 changes: 17 additions & 38 deletions src/techui_builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def _generate_json_map(

# Create initial node at top of .bob file
current_node = JsonMap(
str(screen_path.relative_to(self._write_directory)),
str(screen_path.resolve().relative_to(self._write_directory.resolve())),
display_name=None,
)

Expand Down Expand Up @@ -318,9 +318,7 @@ def _generate_json_map(
macro_dict = self._get_macros(open_display)

case "embedded":
file_elem = self._extract_action_button_file_from_embedded(
widget_elem.file, dest_path
)
file_elem = widget_elem.file
name_elem = widget_elem.name.text
macro_dict = self._get_macros(widget_elem)

Expand Down Expand Up @@ -359,14 +357,23 @@ def _generate_json_map(
)
else:
child_node = JsonMap(
str(file_path), display_name, exists=("IOC" in macro_dict)
str(file_path),
display_name,
exists=("IOC" in macro_dict or ("https:/" in str(file_path))),
)

child_node.macros = macro_dict
# TODO: make this work for only list[JsonMap]
assert isinstance(current_node.children, list)
# TODO: fix typing
current_node.children.append(child_node)
if widget_type == "embedded":
for embedded_child in child_node.children:
embedded_child.macros = {**embedded_child.macros, **macro_dict}
embedded_child.display_name = display_name
current_node.children.append(embedded_child)

else:
child_node.macros = macro_dict
# TODO: make this work for only list[JsonMap]
assert isinstance(current_node.children, list)
# TODO: fix typing
current_node.children.append(child_node)

except etree.ParseError as e:
current_node.error = f"XML parse error: {e}"
Expand Down Expand Up @@ -410,34 +417,6 @@ def _get_component_label(
display_name = child_labels[name_elem]
return display_name

def _extract_action_button_file_from_embedded(
self, file_elem: ObjectifiedElement, dest_path: Path
) -> ObjectifiedElement:
file_path = Path(file_elem.text.strip() if file_elem.text else "")
file_path = dest_path.joinpath(file_path)
if not file_path.exists():
rel_file_path = Path(str(file_elem.base)).relative_to(
dest_path.absolute(), walk_up=True
)
file_path = dest_path.joinpath(rel_file_path)
tree = objectify.parse(file_path.absolute())
root: ObjectifiedElement = tree.getroot()

# Find all <widget> elements
widgets = [
w
for w in root.findall(".//widget")
if w.get("type", default=None) == "action_button"
]

for widget_elem in widgets:
open_display = _get_action_group(widget_elem)
if open_display is None:
continue
file_elem = open_display.file
return file_elem
return file_elem

def _get_macros(self, element: ObjectifiedElement):
if hasattr(element, "macros"):
macros = element.macros.getchildren()
Expand Down
20 changes: 16 additions & 4 deletions src/techui_builder/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,24 @@ def _allocate_widget(
self, scrn_mapping: Mapping, component: Entity
) -> EmbeddedDisplay | ActionButton | None | list[EmbeddedDisplay | ActionButton]:
name, suffix, suffix_label = self._initialise_name_suffix(component)

# Get relative path to screen
scrn_path = self.support_path.joinpath(f"bob/{scrn_mapping['file']}")
logger_.debug(f"Screen path: {scrn_path}")
file = scrn_mapping["file"]
if file.startswith("$(IOC)"):
scrn_path = data_scrn_path = file.replace(
"$(IOC)", f"{self.beamline_url}/{component.service_name}"
) # Only works with related displays as
# embedded displays need to access the file to get dimensions

assert scrn_mapping["type"] == "related", (
"Only related displays can have remote screens"
)
else:
scrn_path = self.support_path.joinpath(f"bob/{file}")
logger_.debug(f"Screen path: {scrn_path}")

# Path of screen relative to data/ so it knows where to open the file from
data_scrn_path = scrn_path.relative_to(self.synoptic_dir, walk_up=True)
# Path of screen relative to data/ so it knows where to open the file from
data_scrn_path = scrn_path.relative_to(self.synoptic_dir, walk_up=True)

# For Gui Components with multiple components embedded, we add a suffix field
# to the components, and adjust the name and suffix accordingly
Expand Down
22 changes: 13 additions & 9 deletions tests/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,15 @@ def test_generate_json_map(


# TODO: write this test
def test_generate_json_map_embedded_screen(
builder_with_test_files, example_json_map, components
):
def test_generate_json_map_embedded_screen(builder_with_test_files, example_json_map):
builder_with_test_files._get_component_label = Mock(
side_effect=["Display", "Detector", "Embedded Display"]
side_effect=[
"Display",
"Detector",
"Embedded Display",
"Embedded Display",
"Embedded Display",
]
)

screen_path = Path("tests/test_files/test_bob_embedded.bob").absolute()
Expand All @@ -330,14 +334,14 @@ def test_generate_json_map_embedded_screen(
example_json_map.file = "test_bob_embedded.bob"
example_json_map.children.append(
JsonMap(
"$(IOC)/pmacAxis.pvi.bob", display_name="Embedded Display", exists=False
"$(IOC)/pmacAxis.pvi.bob",
display_name="Embedded Display",
exists=False,
macros={"M": "$(M)", "P": "$(P)"},
)
)

test_json_map = builder_with_test_files._generate_json_map(
screen_path, dest_path, components
)

test_json_map = builder_with_test_files._generate_json_map(screen_path, dest_path)
assert test_json_map == example_json_map


Expand Down
21 changes: 21 additions & 0 deletions tests/test_files/widget_url_screen.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" ?>
<widget type="action_button" version="2.0.0">
<name>CAM</name>
<x>0</x>
<y>0</y>
<width>100</width>
<height>40</height>
<pv_name/>
<text>CAM</text>
<actions>
<action type="open_display">
<description>Open Display</description>
<macros>
<P>BL01T-DI-IOC-01</P>
<R>:CAM:</R>
</macros>
<file>test_url/bl01t-di-ioc-01/ADAravis_summary.bob</file>
<target>tab</target>
</action>
</actions>
</widget>
25 changes: 25 additions & 0 deletions tests/test_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,31 @@ def test_generator_allocate_widget(generator):
assert str(widget) == xml_content


def test_generator_allocate_widget_with_remote_screens(generator):
generator._initilise_name_suffix = Mock(return_value=("CAM:", "CAM:", "R"))

scrn_mapping = {
"file": "$(IOC)/ADAravis_summary.bob",
"prefix": "$(P)$(R)",
"type": "related",
}
component = Entity(
service_name="bl01t-di-ioc-01",
type="ADAravis.aravisCamera",
P="BL01T-DI-IOC-01",
desc=None,
M=None,
R=":CAM:",
)
widget = generator._allocate_widget(scrn_mapping, component)
control_widget = Path("tests/test_files/widget_url_screen.xml")

with open(control_widget) as f:
xml_content = f.read()
print(str(widget))
assert str(widget) == xml_content


def test_generator_allocate_widget_with_suffix(generator):
generator._initialise_name_suffix = Mock(return_value=(":CAM:", ":CAM:", "R"))

Expand Down