Commit 8b95ff9b authored by Samuel GAIST's avatar Samuel GAIST
Browse files

[widgets][experimenteditor] Implement error hinting mechanism

parent 9efd122d
...@@ -44,6 +44,7 @@ from ..backend.assetmodel import AssetModel ...@@ -44,6 +44,7 @@ from ..backend.assetmodel import AssetModel
from ..backend.resourcemodels import AlgorithmResourceModel from ..backend.resourcemodels import AlgorithmResourceModel
from ..backend.resourcemodels import ExperimentResources from ..backend.resourcemodels import ExperimentResources
from ..backend.resourcemodels import QueueResourceModel from ..backend.resourcemodels import QueueResourceModel
from ..backend.resourcemodels import experiment_resources
from ..widgets.experimenteditor import AlgorithmParametersEditor from ..widgets.experimenteditor import AlgorithmParametersEditor
from ..widgets.experimenteditor import AnalyzerBlockEditor from ..widgets.experimenteditor import AnalyzerBlockEditor
from ..widgets.experimenteditor import BlockEditor from ..widgets.experimenteditor import BlockEditor
...@@ -1122,6 +1123,7 @@ class TestExperimentEditor: ...@@ -1122,6 +1123,7 @@ class TestExperimentEditor:
@pytest.fixture() @pytest.fixture()
def experiment_editor(self, qtbot, beat_context, experiment_declaration): def experiment_editor(self, qtbot, beat_context, experiment_declaration):
experiment_resources.setContext(beat_context)
editor = ExperimentEditor() editor = ExperimentEditor()
editor.set_context(beat_context) editor.set_context(beat_context)
editor.load_json(experiment_declaration) editor.load_json(experiment_declaration)
...@@ -1129,14 +1131,11 @@ class TestExperimentEditor: ...@@ -1129,14 +1131,11 @@ class TestExperimentEditor:
return editor return editor
@pytest.mark.parametrize("experiment", get_valid_experiments(prefix)) @pytest.mark.parametrize("experiment", get_valid_experiments(prefix))
def test_load_and_dump(self, qtbot, beat_context, test_prefix, experiment): def test_load_and_dump(self, experiment_editor, test_prefix, experiment):
experiment_declaration = get_experiment_declaration(test_prefix, experiment) experiment_declaration = get_experiment_declaration(test_prefix, experiment)
editor = ExperimentEditor() experiment_editor.load_json(experiment_declaration)
editor.set_context(beat_context)
editor.load_json(experiment_declaration)
qtbot.addWidget(editor)
assert editor.dump_json() == experiment_declaration assert experiment_editor.dump_json() == experiment_declaration
def test_change_dataset(self, qtbot, experiment_editor, experiment_declaration): def test_change_dataset(self, qtbot, experiment_editor, experiment_declaration):
dataset_editor = experiment_editor.datasets_widget.widget_list[0] dataset_editor = experiment_editor.datasets_widget.widget_list[0]
...@@ -1299,3 +1298,17 @@ class TestExperimentEditor: ...@@ -1299,3 +1298,17 @@ class TestExperimentEditor:
dump = experiment_editor.dump_json() dump = experiment_editor.dump_json()
assert dump != experiment_declaration assert dump != experiment_declaration
assert "environment" not in dump["blocks"][block_editor.block_name] assert "environment" not in dump["blocks"][block_editor.block_name]
def test_showing_error(self, qtbot, experiment_editor, experiment_declaration):
experiment_editor.load_json(experiment_declaration)
ERROR_BLOCK = "echo"
errors = {ERROR_BLOCK: ["show this error"]}
experiment_editor.setBlockErrors(errors)
editor = experiment_editor.findEditor(ERROR_BLOCK)
assert editor.error_label.toolTip() == "show this error"
experiment_editor.clearBlockErrors()
assert editor.error_label.toolTip() == ""
...@@ -184,7 +184,22 @@ class AbstractBaseEditor(QWidget): ...@@ -184,7 +184,22 @@ class AbstractBaseEditor(QWidget):
def __init__(self, prefix_path, parent=None): def __init__(self, prefix_path, parent=None):
super().__init__(parent) super().__init__(parent)
self.prefix_path = prefix_path self.prefix_path = prefix_path
self.error_label = QLabel(self)
icon = self.style().standardIcon(QStyle.SP_MessageBoxWarning)
self.error_label.setPixmap(icon.pixmap(32, 32, QIcon.Normal, QIcon.On))
layout = QVBoxLayout(self)
layout.addWidget(self.error_label)
self.setErrors([])
def setErrors(self, errors):
if errors:
self.error_label.setVisible(True)
self.error_label.setToolTip("\n".join(errors))
else:
self.error_label.setVisible(False)
self.error_label.setToolTip("")
@pyqtSlot(str) @pyqtSlot(str)
def setPrefixPath(self, prefix_path): def setPrefixPath(self, prefix_path):
...@@ -280,7 +295,7 @@ class IOMapperDialog(QDialog): ...@@ -280,7 +295,7 @@ class IOMapperDialog(QDialog):
outputs = block_data.get("outputs") outputs = block_data.get("outputs")
if outputs: if outputs:
for alg_output, output in block_data["outputs"].items(): for alg_output, output in outputs.items():
combobox = QComboBox() combobox = QComboBox()
combobox.addItems(alg_outputs) combobox.addItems(alg_outputs)
combobox.setCurrentText(alg_output) combobox.setCurrentText(alg_output)
...@@ -351,8 +366,9 @@ class DatasetEditor(AbstractBaseEditor): ...@@ -351,8 +366,9 @@ class DatasetEditor(AbstractBaseEditor):
row_layout.addWidget(self.dataset_combobox, 1) row_layout.addWidget(self.dataset_combobox, 1)
row_layout.addWidget(self.reset_button) row_layout.addWidget(self.reset_button)
layout = QFormLayout(self) layout = QFormLayout()
layout.addRow(self.block_name, row_layout) layout.addRow(self.block_name, row_layout)
self.layout().addLayout(layout)
self.dataset_combobox.currentTextChanged.connect(self.dataChanged) self.dataset_combobox.currentTextChanged.connect(self.dataChanged)
self.dataset_combobox.currentTextChanged.connect(self.datasetChanged) self.dataset_combobox.currentTextChanged.connect(self.datasetChanged)
...@@ -421,7 +437,8 @@ class AlgorithmParametersEditor(AbstractBaseEditor): ...@@ -421,7 +437,8 @@ class AlgorithmParametersEditor(AbstractBaseEditor):
self.json_data = {} self.json_data = {}
self.edited = {} self.edited = {}
self.dump_edited_only = True self.dump_edited_only = True
self.form_layout = QFormLayout(self) self.form_layout = QFormLayout()
self.layout().addLayout(self.form_layout)
def setDumpEditOnlyEnabled(self, enabled): def setDumpEditOnlyEnabled(self, enabled):
"""Sets whether the dump will contain all or only the modified fields""" """Sets whether the dump will contain all or only the modified fields"""
...@@ -617,7 +634,7 @@ class ExecutionPropertiesEditor(AbstractBaseEditor): ...@@ -617,7 +634,7 @@ class ExecutionPropertiesEditor(AbstractBaseEditor):
self.parameters_layout.addRow(self.tr("Queue"), self.queue_combobox) self.parameters_layout.addRow(self.tr("Queue"), self.queue_combobox)
self.parameters_layout.addRow(self.tr("Parameters"), self.parameters_editor) self.parameters_layout.addRow(self.tr("Parameters"), self.parameters_editor)
layout = QVBoxLayout(self) layout = self.layout()
layout.addWidget(self.algorithm_combobox) layout.addWidget(self.algorithm_combobox)
layout.addLayout(button_layout) layout.addLayout(button_layout)
layout.addWidget(groupbox) layout.addWidget(groupbox)
...@@ -705,9 +722,10 @@ class ExecutionPropertiesEditor(AbstractBaseEditor): ...@@ -705,9 +722,10 @@ class ExecutionPropertiesEditor(AbstractBaseEditor):
for key, value in zip(inputs, self.io_mapping["inputs"].values()): for key, value in zip(inputs, self.io_mapping["inputs"].values()):
io_mapping["inputs"][key] = value io_mapping["inputs"][key] = value
if outputs and sorted(outputs) != sorted(self.io_mapping["outputs"].keys()): outputs_mapping = self.io_mapping.get("outputs", {})
if outputs and sorted(outputs) != sorted(outputs_mapping.keys()):
io_mapping["outputs"] = {} io_mapping["outputs"] = {}
for key, value in zip(outputs, self.io_mapping["outputs"].values()): for key, value in zip(outputs, outputs_mapping.values()):
io_mapping["outputs"][key] = value io_mapping["outputs"][key] = value
if io_mapping: if io_mapping:
...@@ -832,7 +850,7 @@ class BlockEditor(AbstractBlockEditor): ...@@ -832,7 +850,7 @@ class BlockEditor(AbstractBlockEditor):
self.properties_editor = ExecutionPropertiesEditor(prefix_path) self.properties_editor = ExecutionPropertiesEditor(prefix_path)
self.properties_editor.setAlgorithmResourceModel(self.algorithm_model) self.properties_editor.setAlgorithmResourceModel(self.algorithm_model)
layout = QVBoxLayout(self) layout = self.layout()
layout.addWidget(QLabel(self.block_name)) layout.addWidget(QLabel(self.block_name))
layout.addWidget(self.properties_editor) layout.addWidget(self.properties_editor)
...@@ -895,7 +913,7 @@ class LoopBlockEditor(AbstractBlockEditor): ...@@ -895,7 +913,7 @@ class LoopBlockEditor(AbstractBlockEditor):
evaluator_layout = QVBoxLayout(evaluator_groupbox) evaluator_layout = QVBoxLayout(evaluator_groupbox)
evaluator_layout.addWidget(self.evaluator_properties_editor) evaluator_layout.addWidget(self.evaluator_properties_editor)
layout = QVBoxLayout(self) layout = self.layout()
layout.addWidget(QLabel(self.block_name)) layout.addWidget(QLabel(self.block_name))
layout.addWidget(processor_groupbox) layout.addWidget(processor_groupbox)
layout.addWidget(evaluator_groupbox) layout.addWidget(evaluator_groupbox)
...@@ -1000,7 +1018,7 @@ class GlobalParametersEditor(AbstractBaseEditor): ...@@ -1000,7 +1018,7 @@ class GlobalParametersEditor(AbstractBaseEditor):
form_layout.addRow(self.tr("Environment"), self.environment_combobox) form_layout.addRow(self.tr("Environment"), self.environment_combobox)
form_layout.addRow(self.tr("Queue"), self.queue_combobox) form_layout.addRow(self.tr("Queue"), self.queue_combobox)
layout = QVBoxLayout(self) layout = self.layout()
layout.addLayout(form_layout) layout.addLayout(form_layout)
layout.addWidget(self.parameters_editor_listwidget) layout.addWidget(self.parameters_editor_listwidget)
layout.addStretch(1) layout.addStretch(1)
...@@ -1187,6 +1205,10 @@ class ExperimentEditor(AbstractAssetEditor): ...@@ -1187,6 +1205,10 @@ class ExperimentEditor(AbstractAssetEditor):
algorithms.add(editor.evaluator_properties_editor.algorithm) algorithms.add(editor.evaluator_properties_editor.algorithm)
self.globalparameters_widget.setup(algorithms) self.globalparameters_widget.setup(algorithms)
def __onBlockChanged(self, asset_name):
sender = self.sender()
self.blockChanged.emit(sender.block_name, sender.dump())
def _asset_models(self): def _asset_models(self):
"""Reimpl""" """Reimpl"""
...@@ -1328,6 +1350,7 @@ class ExperimentEditor(AbstractAssetEditor): ...@@ -1328,6 +1350,7 @@ class ExperimentEditor(AbstractAssetEditor):
for name, dataset in datasets.items(): for name, dataset in datasets.items():
editor = DatasetEditor(name, self.prefix_path) editor = DatasetEditor(name, self.prefix_path)
editor.load(dataset) editor.load(dataset)
editor.datasetChanged.connect(self.__onBlockChanged)
self.datasets_widget.addWidget(editor) self.datasets_widget.addWidget(editor)
used_algorithms = set() used_algorithms = set()
...@@ -1344,6 +1367,7 @@ class ExperimentEditor(AbstractAssetEditor): ...@@ -1344,6 +1367,7 @@ class ExperimentEditor(AbstractAssetEditor):
editor.setEnvironmentModel(self.processing_env_model) editor.setEnvironmentModel(self.processing_env_model)
editor.load(data) editor.load(data)
editor.algorithmChanged.connect(self.__onAlgorithmChanged) editor.algorithmChanged.connect(self.__onAlgorithmChanged)
editor.algorithmChanged.connect(self.__onBlockChanged)
container.addWidget(editor) container.addWidget(editor)
if type_ == "loops": if type_ == "loops":
...@@ -1423,6 +1447,52 @@ class ExperimentEditor(AbstractAssetEditor): ...@@ -1423,6 +1447,52 @@ class ExperimentEditor(AbstractAssetEditor):
return data return data
def findEditor(self, block_name):
"""Find the editor corresponding to the block_name"""
editor = None
widgets = (
self.datasets_widget.widget_list
+ self.blocks_widget.widget_list
+ self.loops_widget.widget_list
+ self.analyzers_widget.widget_list
)
for widget in widgets:
if widget.block_name == block_name:
editor = widget
break
return editor
def loadToolchainData(self, toolchain): def loadToolchainData(self, toolchain):
"""Load the data from the toolchain where needed"""
for widget in self.datasets_widget.widget_list: for widget in self.datasets_widget.widget_list:
widget.loadToolchainData(toolchain) widget.loadToolchainData(toolchain)
def clearBlockErrors(self):
"""Clear error hinting"""
for widget in [
self.datasets_widget,
self.blocks_widget,
self.loops_widget,
self.analyzers_widget,
]:
for editor in widget.widget_list:
editor.setErrors([])
def setBlockErrors(self, errors_map):
"""Set error hinting on editors
errors_maps provides a dictionary of block name / error string list
"""
for widget in [
self.datasets_widget,
self.blocks_widget,
self.loops_widget,
self.analyzers_widget,
]:
for editor in widget.widget_list:
errors = errors_map.get(editor.block_name, [])
editor.setErrors(errors)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment