diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cedc7ecd..b5deec9a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -99,7 +99,7 @@ jobs: env: AIIDA_WARN_v3: 1 run: | - pytest -v tests --cov + pytest -v tests --cov --durations=0 - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4.0.1 diff --git a/aiida_workgraph/__init__.py b/aiida_workgraph/__init__.py index 335a5ea7..05582eda 100644 --- a/aiida_workgraph/__init__.py +++ b/aiida_workgraph/__init__.py @@ -9,6 +9,6 @@ from .task import Task from .decorator import task, build_task -__version__ = "0.3.14" +__version__ = "0.3.15" __all__ = ["WorkGraph", "Task", "task", "build_task"] diff --git a/aiida_workgraph/calculations/python.py b/aiida_workgraph/calculations/python.py index f1d0e66e..90961605 100644 --- a/aiida_workgraph/calculations/python.py +++ b/aiida_workgraph/calculations/python.py @@ -50,6 +50,9 @@ def define(cls, spec: CalcJobProcessSpec) -> None: # type: ignore[override] spec.input( "function_name", valid_type=Str, serializer=to_aiida_type, required=False ) + spec.input( + "process_label", valid_type=Str, serializer=to_aiida_type, required=False + ) spec.input_namespace( "function_kwargs", valid_type=Data, required=False ) # , serializer=serialize_to_aiida_nodes) @@ -137,7 +140,16 @@ def _build_process_label(self) -> str: :returns: The process label to use for ``ProcessNode`` instances. """ - return f"PythonJob<{self.inputs.function_name.value}>" + if self.inputs.process_label: + return self.inputs.process_label.value + else: + return f"PythonJob<{self.inputs.function_name.value}>" + + def on_create(self) -> None: + """Called when a Process is created.""" + + super().on_create() + self.node.label = self.inputs.process_label.value def prepare_for_submission(self, folder: Folder) -> CalcInfo: """Prepare the calculation for submission. @@ -265,9 +277,10 @@ def prepare_for_submission(self, folder: Folder) -> CalcInfo: dirpath = pathlib.Path(folder._abspath) with folder.open(filename, "wb") as handle: pickle.dump(input_values, handle) - # create a singlefiledata object for the pickled data - file_data = SinglefileData(file=f"{dirpath}/{filename}") - local_copy_list.append((file_data.uuid, file_data.filename, filename)) + # create a singlefiledata object for the pickled data + file_data = SinglefileData(file=f"{dirpath}/{filename}") + file_data.store() + local_copy_list.append((file_data.uuid, file_data.filename, filename)) codeinfo = CodeInfo() codeinfo.stdin_name = self.options.input_filename diff --git a/aiida_workgraph/calculations/python_parser.py b/aiida_workgraph/calculations/python_parser.py index ae6dd08a..00f486c6 100644 --- a/aiida_workgraph/calculations/python_parser.py +++ b/aiida_workgraph/calculations/python_parser.py @@ -11,7 +11,7 @@ def parse(self, **kwargs): The outputs could be a namespce, e.g., outputs=[ - {"identifier": "Namespace", "name": "add_multiply"}, + {"identifier": "workgraph.namespace", "name": "add_multiply"}, {"name": "add_multiply.add"}, {"name": "add_multiply.multiply"}, {"name": "minus"}, @@ -100,7 +100,7 @@ def serialize_output(self, result, output): """Serialize outputs.""" name = output["name"] - if output["identifier"].upper() == "NAMESPACE": + if output["identifier"].upper() == "WORKGRAPH.NAMESPACE": if isinstance(result, dict): serialized_result = {} for key, value in result.items(): @@ -108,7 +108,8 @@ def serialize_output(self, result, output): full_name_output = self.find_output(full_name) if ( full_name_output - and full_name_output["identifier"].upper() == "NAMESPACE" + and full_name_output["identifier"].upper() + == "WORKGRAPH.NAMESPACE" ): serialized_result[key] = self.serialize_output( value, full_name_output diff --git a/aiida_workgraph/collection.py b/aiida_workgraph/collection.py index c89d3254..6e826bf0 100644 --- a/aiida_workgraph/collection.py +++ b/aiida_workgraph/collection.py @@ -38,6 +38,9 @@ def new( # make links between the tasks task.set(links) return task + if isinstance(identifier, str) and identifier.upper() == "WHILE": + task = super().new("workgraph.while", name, uuid, **kwargs) + return task if isinstance(identifier, WorkGraph): identifier = build_task_from_workgraph(identifier) return super().new(identifier, name, uuid, **kwargs) @@ -86,6 +89,7 @@ def new( # build the socket on the fly if the identifier is a callable if callable(identifier): + print("identifier is callable", identifier) identifier = build_socket_from_AiiDA(identifier) # Call the original new method return super().new(identifier, name, **kwargs) diff --git a/aiida_workgraph/decorator.py b/aiida_workgraph/decorator.py index 6225cd8d..c0705e0d 100644 --- a/aiida_workgraph/decorator.py +++ b/aiida_workgraph/decorator.py @@ -15,11 +15,11 @@ WorkChain: "WORKCHAIN", } -aiida_socket_maping = { - orm.Int: "AiiDAInt", - orm.Float: "AiiDAFloat", - orm.Str: "AiiDAString", - orm.Bool: "AiiDABool", +aiida_socket_mapping = { + orm.Int: "workgraph.aiida_int", + orm.Float: "workgraph.aiida_float", + orm.Str: "workgraph.aiida_str", + orm.Bool: "workgraph.aiida_bool", } @@ -53,9 +53,9 @@ def add_input_recursive( if port_name not in input_names: inputs.append( { - "identifier": "Namespace", + "identifier": "workgraph.namespace", "name": port_name, - "property": {"identifier": "Any", "default": {}}, + "property": {"identifier": "workgraph.any", "default": {}}, } ) if required: @@ -71,11 +71,13 @@ def add_input_recursive( # port.valid_type can be a single type or a tuple of types, # we only support single type for now if isinstance(port.valid_type, tuple) and len(port.valid_type) > 1: - socket_type = "Any" + socket_type = "workgraph.any" if isinstance(port.valid_type, tuple) and len(port.valid_type) == 1: - socket_type = aiida_socket_maping.get(port.valid_type[0], "Any") + socket_type = aiida_socket_mapping.get( + port.valid_type[0], "workgraph.any" + ) else: - socket_type = aiida_socket_maping.get(port.valid_type, "Any") + socket_type = aiida_socket_mapping.get(port.valid_type, "workgraph.any") inputs.append({"identifier": socket_type, "name": port_name}) if required: args.append(port_name) @@ -102,12 +104,12 @@ def add_output_recursive( # so if you change the value of one port, the value of all the ports of other tasks will be changed # consider to use None as default value if port_name not in output_names: - outputs.append({"identifier": "Namespace", "name": port_name}) + outputs.append({"identifier": "workgraph.namespace", "name": port_name}) for value in port.values(): add_output_recursive(outputs, value, prefix=port_name, required=required) else: if port_name not in output_names: - outputs.append({"identifier": "Any", "name": port_name}) + outputs.append({"identifier": "workgraph.any", "name": port_name}) return outputs @@ -219,9 +221,9 @@ def build_task_from_AiiDA( tdata["var_kwargs"] = name inputs.append( { - "identifier": "Any", + "identifier": "workgraph.any", "name": name, - "property": {"identifier": "Any", "default": {}}, + "property": {"identifier": "workgraph.any", "default": {}}, } ) # TODO In order to reload the WorkGraph from process, "is_pickle" should be True @@ -234,15 +236,19 @@ def build_task_from_AiiDA( "is_pickle": True, } if tdata["task_type"].upper() in ["CALCFUNCTION", "WORKFUNCTION"]: - outputs = [{"identifier": "Any", "name": "result"}] if not outputs else outputs + outputs = ( + [{"identifier": "workgraph.any", "name": "result"}] + if not outputs + else outputs + ) # get the source code of the function tdata["executor"] = serialize_function(executor) # tdata["executor"]["type"] = tdata["task_type"] # print("kwargs: ", kwargs) # add built-in sockets - outputs.append({"identifier": "Any", "name": "_outputs"}) - outputs.append({"identifier": "Any", "name": "_wait"}) - inputs.append({"identifier": "Any", "name": "_wait", "link_limit": 1e6}) + outputs.append({"identifier": "workgraph.any", "name": "_outputs"}) + outputs.append({"identifier": "workgraph.any", "name": "_wait"}) + inputs.append({"identifier": "workgraph.any", "name": "_wait", "link_limit": 1e6}) tdata["node_class"] = Task tdata["args"] = args tdata["kwargs"] = kwargs @@ -278,10 +284,10 @@ def build_pythonjob_task(func: Callable) -> Task: inputs = tdata["inputs"] inputs.extend( [ - {"identifier": "String", "name": "computer"}, - {"identifier": "String", "name": "code_label"}, - {"identifier": "String", "name": "code_path"}, - {"identifier": "String", "name": "prepend_text"}, + {"identifier": "workgraph.string", "name": "computer"}, + {"identifier": "workgraph.string", "name": "code_label"}, + {"identifier": "workgraph.string", "name": "code_path"}, + {"identifier": "workgraph.string", "name": "prepend_text"}, ] ) outputs = tdata["outputs"] @@ -324,7 +330,7 @@ def build_shelljob_task( nodes = {} if nodes is None else nodes keys = list(nodes.keys()) for key in keys: - inputs.append({"identifier": "Any", "name": f"nodes.{key}"}) + inputs.append({"identifier": "workgraph.any", "name": f"nodes.{key}"}) # input is a output of another task, we make a link if isinstance(nodes[key], NodeSocket): links[f"nodes.{key}"] = nodes[key] @@ -337,14 +343,14 @@ def build_shelljob_task( # Extend the outputs tdata["outputs"].extend( [ - {"identifier": "Any", "name": "stdout"}, - {"identifier": "Any", "name": "stderr"}, + {"identifier": "workgraph.any", "name": "stdout"}, + {"identifier": "workgraph.any", "name": "stderr"}, ] ) outputs = [] if outputs is None else outputs parser_outputs = [] if parser_outputs is None else parser_outputs outputs = [ - {"identifier": "Any", "name": ShellParser.format_link_label(output)} + {"identifier": "workgraph.any", "name": ShellParser.format_link_label(output)} for output in outputs ] outputs.extend(parser_outputs) @@ -356,8 +362,8 @@ def build_shelljob_task( tdata["identifier"] = "ShellJob" tdata["inputs"].extend( [ - {"identifier": "Any", "name": "command"}, - {"identifier": "Any", "name": "resolve_command"}, + {"identifier": "workgraph.any", "name": "command"}, + {"identifier": "workgraph.any", "name": "resolve_command"}, ] ) tdata["kwargs"].extend(["command", "resolve_command"]) @@ -379,17 +385,21 @@ def build_task_from_workgraph(wg: any) -> Task: # add all the inputs/outputs from the tasks in the workgraph for task in wg.tasks: # inputs - inputs.append({"identifier": "Any", "name": f"{task.name}"}) + inputs.append({"identifier": "workgraph.any", "name": f"{task.name}"}) for socket in task.inputs: if socket.name == "_wait": continue - inputs.append({"identifier": "Any", "name": f"{task.name}.{socket.name}"}) + inputs.append( + {"identifier": "workgraph.any", "name": f"{task.name}.{socket.name}"} + ) # outputs - outputs.append({"identifier": "Any", "name": f"{task.name}"}) + outputs.append({"identifier": "workgraph.any", "name": f"{task.name}"}) for socket in task.outputs: if socket.name in ["_wait", "_outputs"]: continue - outputs.append({"identifier": "Any", "name": f"{task.name}.{socket.name}"}) + outputs.append( + {"identifier": "workgraph.any", "name": f"{task.name}.{socket.name}"} + ) group_outputs.append( { "name": f"{task.name}.{socket.name}", @@ -398,9 +408,9 @@ def build_task_from_workgraph(wg: any) -> Task: ) kwargs = [input["name"] for input in inputs] # add built-in sockets - outputs.append({"identifier": "Any", "name": "_outputs"}) - outputs.append({"identifier": "Any", "name": "_wait"}) - inputs.append({"identifier": "Any", "name": "_wait", "link_limit": 1e6}) + outputs.append({"identifier": "workgraph.any", "name": "_outputs"}) + outputs.append({"identifier": "workgraph.any", "name": "_wait"}) + inputs.append({"identifier": "workgraph.any", "name": "_wait", "link_limit": 1e6}) tdata["node_class"] = Task tdata["kwargs"] = kwargs tdata["inputs"] = inputs @@ -421,6 +431,38 @@ def build_task_from_workgraph(wg: any) -> Task: return task +def nonfunctional_usage(callable: Callable): + """ + This is a decorator for a decorator factory (a function that returns a decorator). + It allows the usage of the decorator factory in a nonfunctional way. So a decorator + factory that has been decorated by this decorator that could only be used befor like + this + + .. code-block:: python + + @decorator_factory() + def foo(): + pass + + can now be also used like this + + .. code-block:: python + + @decorator_factory + def foo(): + pass + + """ + + def decorator_task_wrapper(*args, **kwargs): + if len(args) == 1 and isinstance(args[0], Callable) and len(kwargs) == 0: + return callable()(args[0]) + else: + return callable(*args, **kwargs) + + return decorator_task_wrapper + + def generate_tdata( func: Callable, identifier: str, @@ -440,9 +482,9 @@ def generate_tdata( ) task_outputs = outputs # add built-in sockets - _inputs.append({"identifier": "Any", "name": "_wait", "link_limit": 1e6}) - task_outputs.append({"identifier": "Any", "name": "_wait"}) - task_outputs.append({"identifier": "Any", "name": "_outputs"}) + _inputs.append({"identifier": "workgraph.any", "name": "_wait", "link_limit": 1e6}) + task_outputs.append({"identifier": "workgraph.any", "name": "_wait"}) + task_outputs.append({"identifier": "workgraph.any", "name": "_outputs"}) tdata = { "node_class": Task, "identifier": identifier, @@ -467,6 +509,7 @@ class TaskDecoratorCollection: # decorator with arguments indentifier, args, kwargs, properties, inputs, outputs, executor @staticmethod + @nonfunctional_usage def decorator_task( identifier: Optional[str] = None, task_type: str = "Normal", @@ -501,7 +544,7 @@ def decorator(func): func, identifier, inputs or [], - outputs or [{"identifier": "Any", "name": "result"}], + outputs or [{"identifier": "workgraph.any", "name": "result"}], properties or [], catalog, task_type, @@ -516,6 +559,7 @@ def decorator(func): # decorator with arguments indentifier, args, kwargs, properties, inputs, outputs, executor @staticmethod + @nonfunctional_usage def decorator_graph_builder( identifier: Optional[str] = None, properties: Optional[List[Tuple[str, str]]] = None, @@ -543,7 +587,8 @@ def decorator(func): func.identifier = identifier task_outputs = [ - {"identifier": "Any", "name": output["name"]} for output in outputs + {"identifier": "workgraph.any", "name": output["name"]} + for output in outputs ] # print(task_inputs, task_outputs) # @@ -566,6 +611,7 @@ def decorator(func): return decorator @staticmethod + @nonfunctional_usage def calcfunction(**kwargs: Any) -> Callable: def decorator(func): # First, apply the calcfunction decorator @@ -584,6 +630,7 @@ def decorator(func): return decorator @staticmethod + @nonfunctional_usage def workfunction(**kwargs: Any) -> Callable: def decorator(func): # First, apply the workfunction decorator @@ -602,6 +649,7 @@ def decorator(func): return decorator @staticmethod + @nonfunctional_usage def pythonjob(**kwargs: Any) -> Callable: def decorator(func): # first create a task from the function @@ -627,7 +675,10 @@ def decorator(func): def __call__(self, *args, **kwargs): # This allows using '@task' to directly apply the decorator_task functionality - return self.decorator_task(*args, **kwargs) + if len(args) == 1 and isinstance(args[0], Callable) and len(kwargs) == 0: + return self.decorator_task()(args[0]) + else: + return self.decorator_task(*args, **kwargs) task = TaskDecoratorCollection() diff --git a/aiida_workgraph/engine/utils.py b/aiida_workgraph/engine/utils.py index ac1140bb..e4ddb03e 100644 --- a/aiida_workgraph/engine/utils.py +++ b/aiida_workgraph/engine/utils.py @@ -92,6 +92,7 @@ def prepare_for_python_task(task: dict, kwargs: dict, var_kwargs: dict) -> dict: output_info = task["outputs"] # transfer the args to kwargs inputs = { + "process_label": f"PythonJob<{task['name']}>", "function_source_code": orm.Str(function_source_code), "function_name": orm.Str(function_name), "code": code, diff --git a/aiida_workgraph/engine/workgraph.py b/aiida_workgraph/engine/workgraph.py index a51b3c60..91dc801b 100644 --- a/aiida_workgraph/engine/workgraph.py +++ b/aiida_workgraph/engine/workgraph.py @@ -32,6 +32,7 @@ from aiida.engine import run_get_node from aiida_workgraph.utils import create_and_pause_process from aiida_workgraph.task import Task +from aiida_workgraph.utils import get_nested_dict, update_nested_dict if t.TYPE_CHECKING: from aiida.engine.runners import Runner # pylint: disable=unused-import @@ -415,6 +416,7 @@ def on_create(self) -> None: ) saver = WorkGraphSaver(self.node, wgdata, restart_process=restart_process) saver.save() + self.node.label = wgdata["name"] def setup(self) -> None: # track if the awaitable callback is added to the runner @@ -475,7 +477,8 @@ def get_task(self, name: str): return task def update_task(self, task: Task): - """Update task in the context.""" + """Update task in the context. + This is used in error handlers to update the task parameters.""" self.ctx.tasks[task.name]["properties"] = task.properties_to_dict() self.reset_task(task.name) @@ -517,55 +520,7 @@ def set_task_results(self) -> None: for name, task in self.ctx.tasks.items(): if self.get_task_state_info(name, "action").upper() == "RESET": self.reset_task(task["name"]) - process = self.get_task_state_info(name, "process") - if process: - self.set_task_result(task) - self.set_task_result(task) - - def set_task_result(self, task: t.Dict[str, t.Any]) -> None: - name = task["name"] - # print(f"set task result: {name}") - node = self.get_task_state_info(name, "process") - if isinstance(node, orm.ProcessNode): - # print(f"set task result: {name} process") - state = self.get_task_state_info( - task["name"], "process" - ).process_state.value.upper() - if node.is_finished_ok: - self.set_task_state_info(task["name"], "state", state) - if task["metadata"]["node_type"].upper() == "WORKGRAPH": - # expose the outputs of all the tasks in the workgraph - task["results"] = {} - outgoing = node.base.links.get_outgoing() - for link in outgoing.all(): - if isinstance(link.node, ProcessNode) and getattr( - link.node, "process_state", False - ): - task["results"][link.link_label] = link.node.outputs - else: - task["results"] = node.outputs - # self.ctx.new_data[name] = task["results"] - self.set_task_state_info(task["name"], "state", "FINISHED") - self.task_set_context(name) - self.report(f"Task: {name} finished.") - # all other states are considered as failed - else: - task["results"] = node.outputs - # self.ctx.new_data[name] = task["results"] - self.set_task_state_info(task["name"], "state", "FAILED") - # set child tasks state to SKIPPED - self.set_tasks_state( - self.ctx.connectivity["child_node"][name], "SKIPPED" - ) - self.report(f"Task: {name} failed.") - self.run_error_handlers(name) - elif isinstance(node, orm.Data): - task["results"] = {task["outputs"][0]["name"]: node} - self.set_task_state_info(task["name"], "state", "FINISHED") - self.task_set_context(name) - self.report(f"Task: {name} finished.") - else: - task["results"] = None + self.update_task_state(name) def apply_action(self, msg: dict) -> None: @@ -591,20 +546,24 @@ def apply_task_actions(self, msg: dict) -> None: if action.upper() == "SKIP": pass - def reset_task(self, name: str) -> None: - """Reset task.""" + def reset_task(self, name: str, recursive: bool = True) -> None: + """Reset task state and remove it from the executed task. + If recursive is True, reset its child tasks.""" - self.report(f"Task {name} action: RESET.") self.set_task_state_info(name, "state", "PLANNED") self.set_task_state_info(name, "process", None) self.remove_executed_task(name) - # reset its child tasks - names = self.ctx.connectivity["child_node"][name] - for name in names: - self.set_task_state_info(name, "state", "PLANNED") - self.ctx.tasks[name]["result"] = None - self.set_task_state_info(name, "process", None) - self.remove_executed_task(name) + if recursive: + self.report(f"Task {name} action: RESET.") + # if the task is a while task, reset its child tasks + if self.ctx.tasks[name]["metadata"]["node_type"].upper() == "WHILE": + self.ctx.tasks[name]["execution_count"] = 0 + for child_task in self.ctx.tasks[name]["properties"]["tasks"]["value"]: + self.reset_task(child_task, recursive=False) + # reset its child tasks + names = self.ctx.connectivity["child_node"][name] + for name in names: + self.reset_task(name, recursive=False) def pause_task(self, name: str) -> None: """Pause task.""" @@ -629,7 +588,7 @@ def continue_workgraph(self) -> None: "SKIPPED", ]: continue - ready, output = self.check_parent_state(name) + ready, _ = self.is_task_ready_to_run(name) if ready and self.task_should_run(name): task_to_run.append(name) # @@ -637,20 +596,98 @@ def continue_workgraph(self) -> None: self.run_tasks(task_to_run) def update_task_state(self, name: str) -> None: - """Update task state if task is a Awaitable.""" + """Update task state when the task is finished.""" print("update task state: ", name) task = self.ctx.tasks[name] - if task["metadata"]["node_type"].upper() in [ - "CALCFUNCTION", - "WORKFUNCTION", - "CALCJOB", - "WORKCHAIN", - "GRAPH_BUILDER", - "WORKGRAPH", - "PYTHONJOB", - "SHELLJOB", - ] and self.get_task_state_info(task["name"], "state") in ["CREATED", "RUNNING"]: - self.set_task_result(task) + # print(f"set task result: {name}") + node = self.get_task_state_info(name, "process") + if isinstance(node, orm.ProcessNode): + # print(f"set task result: {name} process") + state = node.process_state.value.upper() + if node.is_finished_ok: + self.set_task_state_info(task["name"], "state", state) + if task["metadata"]["node_type"].upper() == "WORKGRAPH": + # expose the outputs of all the tasks in the workgraph + task["results"] = {} + outgoing = node.base.links.get_outgoing() + for link in outgoing.all(): + if isinstance(link.node, ProcessNode) and getattr( + link.node, "process_state", False + ): + task["results"][link.link_label] = link.node.outputs + else: + task["results"] = node.outputs + # self.ctx.new_data[name] = task["results"] + self.set_task_state_info(task["name"], "state", "FINISHED") + self.task_set_context(name) + self.report(f"Task: {name} finished.") + # all other states are considered as failed + else: + task["results"] = node.outputs + # self.ctx.new_data[name] = task["results"] + self.set_task_state_info(task["name"], "state", "FAILED") + # set child tasks state to SKIPPED + self.set_tasks_state( + self.ctx.connectivity["child_node"][name], "SKIPPED" + ) + self.report(f"Task: {name} failed.") + self.run_error_handlers(name) + elif isinstance(node, orm.Data): + task["results"] = {task["outputs"][0]["name"]: node} + self.set_task_state_info(task["name"], "state", "FINISHED") + self.task_set_context(name) + self.report(f"Task: {name} finished.") + else: + task["results"] = None + + self.update_parent_task_state(name) + + def update_parent_task_state(self, name: str) -> None: + """Update parent task state.""" + parent_task = self.ctx.tasks[name].get("parent_task", None) + if parent_task: + if self.ctx.tasks[parent_task]["metadata"]["node_type"].upper() == "WHILE": + self.update_while_task_state(parent_task) + + def update_while_task_state(self, name: str) -> None: + """Update while task state.""" + finished, _ = self.is_while_task_finished(name) + + if finished: + should_run = self.should_run_while_task(name) + if should_run: + self.ctx.tasks[name]["execution_count"] += 1 + self.reset_task(name) + else: + self.set_task_state_info(name, "state", "FINISHED") + + def should_run_while_task(self, name: str) -> tuple[bool, t.Any]: + """Check if the while task should run.""" + # check the conditions of the while task + task = self.ctx.tasks[name] + not_excess_max_iterations = ( + self.ctx.tasks[name]["execution_count"] + < self.ctx.tasks[name]["properties"]["max_iterations"]["value"] + ) + conditions = [not_excess_max_iterations] + for condition in task["properties"]["conditions"]["value"]: + value = get_nested_dict(self.ctx, condition) + conditions.append(value) + return False not in conditions + + def is_while_task_finished(self, name: str) -> tuple[bool, t.Any]: + """Check if the while task is finished.""" + task = self.ctx.tasks[name] + finished = True + for name in task["properties"]["tasks"]["value"]: + if self.get_task_state_info(name, "state") not in [ + "FINISHED", + "SKIPPED", + "FAILED", + ]: + finished = False + break + return finished, None def run_error_handlers(self, task_name: str) -> None: """Run error handler.""" @@ -802,7 +839,7 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None "PYTHONJOB", "SHELLJOB", ]: - if len(self._awaitables) > self.ctx.max_number_awaitables: + if len(self._awaitables) >= self.ctx.max_number_awaitables: print( MAX_NUMBER_AWAITABLES_MSG.format( self.ctx.max_number_awaitables, name @@ -838,14 +875,8 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None if task["metadata"]["node_type"].upper() == "NODE": print("task type: node.") results = self.run_executor(executor, [], kwargs, var_args, var_kwargs) - self.set_task_state_info(task["name"], "process", results) - task["results"] = {task["outputs"][0]["name"]: results} - self.ctx.input_tasks[name] = results - self.set_task_state_info(name, "state", "FINISHED") - self.task_set_context(name) - # ValueError: attempted to add an input link after the process node was already stored. - # self.node.base.links.add_incoming(results, "INPUT_WORK", name) - self.report(f"Task: {name} finished.") + self.set_task_state_info(name, "process", results) + self.update_task_state(name) if continue_workgraph: self.continue_workgraph() elif task["metadata"]["node_type"].upper() == "DATA": @@ -853,12 +884,9 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None for key in self.ctx.tasks[name]["metadata"]["args"]: kwargs.pop(key, None) results = create_data_node(executor, args, kwargs) - task["results"] = {task["outputs"][0]["name"]: results} - self.set_task_state_info(task["name"], "process", results) + self.set_task_state_info(name, "process", results) + self.update_task_state(name) self.ctx.new_data[name] = results - self.set_task_state_info(name, "state", "FINISHED") - self.task_set_context(name) - self.report(f"Task: {name} finished.") if continue_workgraph: self.continue_workgraph() elif task["metadata"]["node_type"].upper() in [ @@ -877,19 +905,13 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None executor, **kwargs, **var_kwargs ) process.label = name - # only one output - if isinstance(results, orm.Data): - results = {task["outputs"][0]["name"]: results} - task["results"] = results # print("results: ", results) - self.set_task_state_info(task["name"], "process", process) - self.set_task_state_info(name, "state", "FINISHED") - self.task_set_context(name) - self.report(f"Task: {name} finished.") + self.set_task_state_info(name, "process", process) + self.update_task_state(name) except Exception as e: print(e) self.report(e) - self.set_task_state_info(task["name"], "state", "FAILED") + self.set_task_state_info(name, "state", "FAILED") # set child state to FAILED self.set_tasks_state( self.ctx.connectivity["child_node"][name], "SKIPPED" @@ -920,7 +942,7 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None process = self.submit(executor, **kwargs) self.set_task_state_info(name, "state", "RUNNING") process.label = name - self.set_task_state_info(task["name"], "process", process) + self.set_task_state_info(name, "process", process) self.to_context(**{name: process}) elif task["metadata"]["node_type"].upper() in ["GRAPH_BUILDER"]: print("task type: graph_builder.") @@ -931,7 +953,7 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None wg.save(metadata={"call_link_label": name}) print("submit workgraph: ") process = self.submit(wg.process_inited) - self.set_task_state_info(task["name"], "process", process) + self.set_task_state_info(name, "process", process) self.set_task_state_info(name, "state", "RUNNING") self.to_context(**{name: process}) elif task["metadata"]["node_type"].upper() in ["WORKGRAPH"]: @@ -940,7 +962,7 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None inputs, _ = prepare_for_workgraph_task(task, kwargs) print("submit workgraph: ") process = self.submit(WorkGraphEngine, inputs=inputs) - self.set_task_state_info(task["name"], "process", process) + self.set_task_state_info(name, "process", process) self.set_task_state_info(name, "state", "RUNNING") self.to_context(**{name: process}) elif task["metadata"]["node_type"].upper() in ["PYTHONJOB"]: @@ -964,7 +986,7 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None process = self.submit(PythonJob, **inputs) self.set_task_state_info(name, "state", "RUNNING") process.label = name - self.set_task_state_info(task["name"], "process", process) + self.set_task_state_info(name, "process", process) self.to_context(**{name: process}) elif task["metadata"]["node_type"].upper() in ["SHELLJOB"]: from aiida_shell.calculations.shell import ShellJob @@ -986,8 +1008,11 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None process = self.submit(ShellJob, **inputs) self.set_task_state_info(name, "state", "RUNNING") process.label = name - self.set_task_state_info(task["name"], "process", process) + self.set_task_state_info(name, "process", process) self.to_context(**{name: process}) + elif task["metadata"]["node_type"].upper() in ["WHILE"]: + self.set_task_state_info(name, "state", "RUNNING") + self.continue_workgraph() elif task["metadata"]["node_type"].upper() in ["NORMAL"]: print("Task type: Normal.") # normal function does not have a process @@ -1016,6 +1041,7 @@ def run_tasks(self, names: t.List[str], continue_workgraph: bool = True) -> None self.set_task_state_info(name, "state", "FINISHED") self.task_set_context(name) self.report(f"Task: {name} finished.") + self.update_parent_task_state(name) if continue_workgraph: self.continue_workgraph() # print("result from node: ", task["results"]) @@ -1034,7 +1060,6 @@ def get_inputs( t.Dict[str, t.Any], ]: """Get input based on the links.""" - from aiida_workgraph.utils import get_nested_dict args = [] args_dict = {} @@ -1114,7 +1139,6 @@ def get_inputs( def update_context_variable(self, value: t.Any) -> t.Any: # replace context variables - from aiida_workgraph.utils import get_nested_dict """Get value from context.""" if isinstance(value, dict): @@ -1138,70 +1162,79 @@ def task_set_context(self, name: str) -> None: result = self.ctx.tasks[name]["results"][key] update_nested_dict(self.ctx, value, result) - def check_task_state(self, name: str) -> None: - """Check task states. - - - if all input tasks finished, launch task - - if task is a scatter task, check if all scattered tasks finished + def is_task_ready_to_run(self, name: str) -> t.Tuple[bool, t.Optional[str]]: + """Check if the task ready to run. + For normal node, we need to check its parent nodes: + - wait tasks + - input tasks + For task inside a while zone, we need to check if the while task is ready. + - while parent task + For while task, we need to check its child tasks, and the conditions. + - all input tasks of the while task are ready + - while conditions """ - # print(f" Check task {name} state: ") - if self.get_task_state_info(name, "state") in ["PLANNED", "WAITING"]: - ready, output = self.check_parent_state(name) - if ready: - # print(f" Task {name} is ready to launch.") - self.ctx.msgs.append(f"task,{name}:action:LAUNCH") # noqa E231 - elif self.get_task_state_info(name, "state") in ["SCATTERED"]: - state, action = self.check_scattered_state(name) - self.ctx.msgs.append(f"task,{name}:state:{state}") # noqa E231 - else: - # print(f" Task {name} is in state {self.ctx.tasks[name]['state']}") - pass - - def check_parent_state(self, name: str) -> t.Tuple[bool, t.Optional[str]]: task = self.ctx.tasks[name] - inputs = task.get("inputs", None) + inputs = task.get("inputs", []) wait_tasks = self.ctx.tasks[name].get("wait", []) - # print(" wait_tasks: ", wait_tasks) - ready = True - if inputs is None and len(wait_tasks) == 0: - return ready - else: - # check the wait task first - for task_name in wait_tasks: - # in case the task is removed - if task_name not in self.ctx.tasks: - continue - if self.get_task_state_info(task_name, "state") not in [ + parent_task = self.ctx.tasks[name].get("parent_task", None) + # wait, inputs, parent_task, child_tasks, conditions + parent_states = [True, True, True, True, True] + # if the task belongs to a while zoone + if parent_task: + state = self.get_task_state_info(parent_task, "state") + if state not in ["RUNNING"]: + parent_states[2] = False + # if the task is a while task + if task["metadata"]["node_type"].upper() == "WHILE": + # check if the all the child tasks are ready + for child_task_name in self.ctx.connectivity["while"][name]["input_tasks"]: + ready, parent_states = self.is_task_ready_to_run(child_task_name) + if not ready: + parent_states[3] = False + break + # check the conditions of the while task + parent_states[4] = self.should_run_while_task(name) + # check the wait task first + for task_name in wait_tasks: + # in case the task is removed + if task_name not in self.ctx.tasks: + continue + if self.get_task_state_info(task_name, "state") not in [ + "FINISHED", + "SKIPPED", + "FAILED", + ]: + parent_states[0] = False + break + for input in inputs: + # print(" input, ", input["from_node"], self.ctx.tasks[input["from_node"]]["state"]) + for link in input["links"]: + if self.get_task_state_info(link["from_node"], "state") not in [ "FINISHED", "SKIPPED", "FAILED", ]: - ready = False - return ready, f"Task {name} wait for {task_name}" - for input in inputs: - # print(" input, ", input["from_node"], self.ctx.tasks[input["from_node"]]["state"]) - for link in input["links"]: - if self.get_task_state_info(link["from_node"], "state") not in [ + parent_states[1] = False + break + # check if the input task belong to a while task, and the while task is ready + parent_task = self.ctx.tasks[link["from_node"]].get("parent_task", None) + # if the task itself does not belong to the while task + if ( + parent_task + and name + not in self.ctx.tasks[parent_task]["properties"]["tasks"]["value"] + ): + state = self.get_task_state_info(parent_task, "state") + if state not in [ "FINISHED", "SKIPPED", "FAILED", ]: - ready = False - return ( - ready, - f"{name}, input: {link['from_node']} is {self.ctx.tasks[link['from_node']]['state']}", - ) - return ready, None - - # def expose_graph_build_outputs(self, name): - # # print("expose_graph_build_outputs") - # outputs = {} - # process = self.ctx.tasks[name]["process"] - # outgoing = process.base.links.get_outgoing() - # for output in self.ctx.tasks[name]["group_outputs"]: - # node = outgoing.get_node_by_label(output[0]) - # outputs[output[2]] = getattr(node.outputs, output[1]) - # return outputs + parent_states[1] = False + break + # print("is task ready to run: ", name, all(parent_states), parent_states) + return all(parent_states), parent_states + def reset(self) -> None: print("Reset") self.ctx._execution_count += 1 @@ -1296,8 +1329,6 @@ def message_receive( def finalize(self) -> t.Optional[ExitCode]: """""" - from aiida_workgraph.utils import get_nested_dict, update_nested_dict - # expose outputs of the workgraph group_outputs = {} print("workgraph outputs: ", self.ctx.workgraph["metadata"]["group_outputs"]) diff --git a/aiida_workgraph/executors/qe.py b/aiida_workgraph/executors/qe.py index 275a916e..e803ba17 100644 --- a/aiida_workgraph/executors/qe.py +++ b/aiida_workgraph/executors/qe.py @@ -5,7 +5,7 @@ @task( inputs=[ - {"identifier": "String", "name": "pseudo_family"}, + {"identifier": "workgraph.string", "name": "pseudo_family"}, {"identifier": StructureData, "name": "structure"}, ], outputs=[{"identifier": UpfData, "name": "Pseudo"}], diff --git a/aiida_workgraph/properties/__init__.py b/aiida_workgraph/properties/__init__.py index 895fa725..199f9f06 100644 --- a/aiida_workgraph/properties/__init__.py +++ b/aiida_workgraph/properties/__init__.py @@ -1,3 +1,6 @@ from node_graph.utils import get_entries -property_pool = get_entries(entry_point_name="aiida_workgraph.property") +property_pool = { + **get_entries(entry_point_name="node_graph.property"), + **get_entries(entry_point_name="aiida_workgraph.property"), +} diff --git a/aiida_workgraph/properties/built_in.py b/aiida_workgraph/properties/builtins.py similarity index 84% rename from aiida_workgraph/properties/built_in.py rename to aiida_workgraph/properties/builtins.py index 7306fc84..12f8111c 100644 --- a/aiida_workgraph/properties/built_in.py +++ b/aiida_workgraph/properties/builtins.py @@ -1,38 +1,14 @@ from typing import Dict, List, Union, Callable from node_graph.property import NodeProperty -from node_graph.serializer import SerializeJson, SerializePickle -from node_graph.properties.builtin import ( - VectorProperty, - BaseDictProperty, - BaseListProperty, - IntProperty, - BoolProperty, - FloatProperty, - StringProperty, -) +from node_graph.serializer import SerializeJson +from node_graph.properties.builtins import PropertyVector, PropertyAny from aiida import orm -class AnyProperty(NodeProperty, SerializePickle): - """A new class for Any type.""" - - identifier: str = "Any" - data_type = "Any" - - def __init__( - self, - name: str, - description: str = "", - default: Union[int, str, None] = None, - update: Callable = None, - ) -> None: - super().__init__(name, description, default, update) - - -class AiiDAIntProperty(NodeProperty, SerializeJson): +class PropertyAiiDAInt(NodeProperty, SerializeJson): """A new class for integer type.""" - identifier: str = "AiiDAInt" + identifier: str = "workgraph.aiida_int" data_type = "Int" def __init__( @@ -68,10 +44,10 @@ def set_value(self, value: Union[int, orm.Int, str]) -> None: raise Exception("{} is not a integer.".format(value)) -class AiiDAFloatProperty(NodeProperty, SerializeJson): +class PropertyAiiDAFloat(NodeProperty, SerializeJson): """A new class for float type.""" - identifier: str = "AiiDAFloat" + identifier: str = "workgraph.aiida_float" data_type = "Float" def __init__( @@ -107,10 +83,10 @@ def set_value(self, value: Union[float, orm.Float, int, orm.Int, str]) -> None: raise Exception("{} is not a float.".format(value)) -class AiiDABoolProperty(NodeProperty, SerializeJson): +class PropertyAiiDABool(NodeProperty, SerializeJson): """A new class for bool type.""" - identifier: str = "AiiDABool" + identifier: str = "workgraph.aiida_bool" data_type = "Bool" def __init__( @@ -146,10 +122,10 @@ def set_value(self, value: Union[bool, orm.Bool, int, str]) -> None: raise Exception("{} is not a bool.".format(value)) -class AiiDAStringProperty(NodeProperty, SerializeJson): +class PropertyAiiDAString(NodeProperty, SerializeJson): """A new class for string type.""" - identifier: str = "AiiDAString" + identifier: str = "workgraph.aiida_string" data_type = "String" def __init__( @@ -184,10 +160,10 @@ def set_value(self, value: Union[str, orm.Str]) -> None: raise Exception("{} is not a string.".format(value)) -class AiiDADictProperty(NodeProperty, SerializeJson): +class PropertyAiiDADict(NodeProperty, SerializeJson): """A new class for Dict type.""" - identifier: str = "AiiDADict" + identifier: str = "workgraph.aiida_dict" data_type = "Dict" def __init__( @@ -222,10 +198,10 @@ def set_value(self, value: Union[Dict, orm.Dict, str]) -> None: raise Exception("{} is not a dict.".format(value)) -class AiiDAIntVectorProperty(VectorProperty): +class PropertyAiiDAIntVector(PropertyVector): """A new class for integer vector type.""" - identifier: str = "AiiDAIntVector" + identifier: str = "workgraph.aiida_int_vector" data_type = "AiiDAIntVector" def __init__( @@ -260,10 +236,10 @@ def set_value(self, value: List[int]) -> None: ) -class AiiDAFloatVectorProperty(VectorProperty): +class PropertyAiiDAFloatVector(PropertyVector): """A new class for float vector type.""" - identifier: str = "AiiDAFloatVector" + identifier: str = "workgraph.aiida_float_vector" data_type = "AiiDAFloatVector" def __init__( @@ -304,10 +280,10 @@ def get_metadata(self): # Vector -class BoolVectorProperty(VectorProperty): +class PropertyBoolVector(PropertyVector): """A new class for bool vector type.""" - identifier: str = "BoolVector" + identifier: str = "workgraph.bool_vector" data_type = "BoolVector" def __init__( @@ -340,19 +316,14 @@ def set_value(self, value: List[Union[bool, int]]) -> None: ) -property_list = [ - IntProperty, - FloatProperty, - BoolProperty, - StringProperty, - AnyProperty, - BaseDictProperty, - BaseListProperty, - AiiDAIntProperty, - AiiDAFloatProperty, - AiiDAStringProperty, - AiiDABoolProperty, - AiiDADictProperty, - AiiDAIntVectorProperty, - AiiDAFloatVectorProperty, +__all__ = [ + PropertyAny, + PropertyAiiDAInt, + PropertyAiiDAFloat, + PropertyAiiDABool, + PropertyAiiDAString, + PropertyAiiDADict, + PropertyAiiDAIntVector, + PropertyAiiDAFloatVector, + PropertyBoolVector, ] diff --git a/aiida_workgraph/sockets/__init__.py b/aiida_workgraph/sockets/__init__.py index 4a661495..82b2a9b8 100644 --- a/aiida_workgraph/sockets/__init__.py +++ b/aiida_workgraph/sockets/__init__.py @@ -1,3 +1,6 @@ from node_graph.utils import get_entries -socket_pool = get_entries(entry_point_name="aiida_workgraph.socket") +socket_pool = { + **get_entries(entry_point_name="node_graph.socket"), + **get_entries(entry_point_name="aiida_workgraph.socket"), +} diff --git a/aiida_workgraph/sockets/built_in.py b/aiida_workgraph/sockets/builtins.py similarity index 72% rename from aiida_workgraph/sockets/built_in.py rename to aiida_workgraph/sockets/builtins.py index 2303fe1e..2401cf6e 100644 --- a/aiida_workgraph/sockets/built_in.py +++ b/aiida_workgraph/sockets/builtins.py @@ -1,38 +1,24 @@ from typing import Optional, Any from aiida_workgraph.socket import TaskSocket from node_graph.serializer import SerializeJson, SerializePickle -from node_graph.sockets.builtin import ( - SocketInt, - SocketFloat, - SocketString, - SocketBool, - SocketBaseDict, - SocketBaseList, -) class SocketAny(TaskSocket, SerializePickle): - """Socket for any time.""" + """Any socket.""" - identifier: str = "Any" + identifier: str = "workgraph.any" def __init__( - self, - name: str, - node: Optional[Any] = None, - type: str = "INPUT", - index: int = 0, - uuid: Optional[str] = None, - **kwargs: Any + self, name, node=None, type="INPUT", index=0, uuid=None, **kwargs ) -> None: super().__init__(name, node, type, index, uuid=uuid) - self.add_property("Any", name, **kwargs) + self.add_property("workgraph.any", name, **kwargs) class SocketNamespace(TaskSocket, SerializePickle): """Namespace socket.""" - identifier: str = "Namespace" + identifier: str = "workgraph.namespace" def __init__( self, @@ -44,13 +30,13 @@ def __init__( **kwargs: Any ) -> None: super().__init__(name, node, type, index, uuid=uuid) - self.add_property("Any", name, **kwargs) + self.add_property("workgraph.any", name, **kwargs) class SocketAiiDAFloat(TaskSocket, SerializeJson): """AiiDAFloat socket.""" - identifier: str = "AiiDAFloat" + identifier: str = "workgraph.aiida_float" def __init__( self, @@ -62,13 +48,13 @@ def __init__( **kwargs: Any ) -> None: super().__init__(name, node, type, index, uuid=uuid) - self.add_property("AiiDAFloat", name, **kwargs) + self.add_property("workgraph.aiida_float", name, **kwargs) class SocketAiiDAInt(TaskSocket, SerializeJson): """AiiDAInt socket.""" - identifier: str = "AiiDAInt" + identifier: str = "workgraph.aiida_int" def __init__( self, @@ -80,13 +66,13 @@ def __init__( **kwargs: Any ) -> None: super().__init__(name, node, type, index, uuid=uuid) - self.add_property("AiiDAInt", name, **kwargs) + self.add_property("workgraph.aiida_int", name, **kwargs) class SocketAiiDAString(TaskSocket, SerializeJson): """AiiDAString socket.""" - identifier: str = "AiiDAString" + identifier: str = "workgraph.aiida_string" def __init__( self, @@ -104,7 +90,7 @@ def __init__( class SocketAiiDABool(TaskSocket, SerializeJson): """AiiDABool socket.""" - identifier: str = "AiiDABool" + identifier: str = "workgraph.aiida_bool" def __init__( self, @@ -122,7 +108,7 @@ def __init__( class SocketAiiDAIntVector(TaskSocket, SerializeJson): """Socket with a AiiDAIntVector property.""" - identifier: str = "AiiDAIntVector" + identifier: str = "workgraph.aiida_int_vector" def __init__( self, @@ -140,7 +126,7 @@ def __init__( class SocketAiiDAFloatVector(TaskSocket, SerializeJson): """Socket with a FloatVector property.""" - identifier: str = "FloatVector" + identifier: str = "workgraph.aiida_float_vector" def __init__( self, @@ -153,21 +139,3 @@ def __init__( ) -> None: super().__init__(name, node, type, index, uuid=uuid) self.add_property("FloatVector", name, **kwargs) - - -socket_list = [ - SocketAny, - SocketNamespace, - SocketInt, - SocketFloat, - SocketString, - SocketBool, - SocketBaseDict, - SocketBaseList, - SocketAiiDAInt, - SocketAiiDAFloat, - SocketAiiDAString, - SocketAiiDABool, - SocketAiiDAIntVector, - SocketAiiDAFloatVector, -] diff --git a/aiida_workgraph/task.py b/aiida_workgraph/task.py index 5d93c696..e4f3ee7c 100644 --- a/aiida_workgraph/task.py +++ b/aiida_workgraph/task.py @@ -29,7 +29,7 @@ class Task(GraphNode): def __init__( self, context_mapping: Optional[List[Any]] = None, - wait: List[Union[str, GraphNode]] = [], + wait: List[Union[str, GraphNode]] = None, process: Optional[aiida.orm.ProcessNode] = None, pk: Optional[int] = None, **kwargs: Any, diff --git a/aiida_workgraph/tasks/__init__.py b/aiida_workgraph/tasks/__init__.py index 9dca0ab0..9230d873 100644 --- a/aiida_workgraph/tasks/__init__.py +++ b/aiida_workgraph/tasks/__init__.py @@ -1,44 +1,7 @@ from node_graph.utils import get_entries -from .builtin import AiiDAGather, AiiDAToCtx, AiiDAFromCtx -from .test import ( - AiiDAInt, - AiiDAFloat, - AiiDAString, - AiiDAList, - AiiDADict, - AiiDANode, - AiiDACode, - AiiDAAdd, - AiiDAGreater, - AiiDASumDiff, - AiiDAArithmeticMultiplyAdd, -) -from .qe import ( - AiiDAKpoint, - AiiDAPWPseudo, - AiiDAStructure, -) - -task_list = [ - AiiDAGather, - AiiDAToCtx, - AiiDAFromCtx, - AiiDAInt, - AiiDAFloat, - AiiDAString, - AiiDAList, - AiiDADict, - AiiDANode, - AiiDACode, - AiiDAAdd, - AiiDAGreater, - AiiDASumDiff, - AiiDAArithmeticMultiplyAdd, - AiiDAKpoint, - AiiDAPWPseudo, - AiiDAStructure, -] - # should after task_list, otherwise circular import -task_pool = get_entries(entry_point_name="aiida_workgraph.task") +task_pool = { + **get_entries(entry_point_name="aiida_workgraph.task"), + **get_entries(entry_point_name="node_graph.task"), +} diff --git a/aiida_workgraph/tasks/builtin.py b/aiida_workgraph/tasks/builtin.py deleted file mode 100644 index f3c37caf..00000000 --- a/aiida_workgraph/tasks/builtin.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import Dict -from aiida_workgraph.task import Task - - -class AiiDAGather(Task): - """AiiDAGather""" - - identifier = "AiiDAGather" - name = "AiiDAGather" - node_type = "WORKCHAIN" - catalog = "AiiDA" - kwargs = ["datas"] - - def create_sockets(self) -> None: - self.inputs.clear() - self.outputs.clear() - inp = self.inputs.new("Any", "datas") - inp.link_limit = 100000 - self.outputs.new("Any", "result") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida_workgraph.executors.builtin", - "name": "GatherWorkChain", - } - - -class AiiDAToCtx(Task): - """AiiDAToCtx""" - - identifier = "ToCtx" - name = "ToCtx" - node_type = "Control" - catalog = "AiiDA" - args = ["key", "value"] - - def create_sockets(self) -> None: - self.inputs.clear() - self.outputs.clear() - self.inputs.new("Any", "key") - self.inputs.new("Any", "value") - self.outputs.new("Any", "result") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "builtins", - "name": "setattr", - } - - -class AiiDAFromCtx(Task): - """AiiDAFromCtx""" - - identifier = "FromCtx" - name = "FromCtx" - node_type = "Control" - catalog = "AiiDA" - args = ["key"] - - def create_sockets(self) -> None: - self.inputs.clear() - self.outputs.clear() - self.inputs.new("Any", "key") - self.outputs.new("Any", "result") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "builtins", - "name": "getattr", - } diff --git a/aiida_workgraph/tasks/builtins.py b/aiida_workgraph/tasks/builtins.py new file mode 100644 index 00000000..86bab6f6 --- /dev/null +++ b/aiida_workgraph/tasks/builtins.py @@ -0,0 +1,243 @@ +from typing import Dict +from aiida_workgraph.task import Task + + +class While(Task): + """While""" + + identifier = "workgraph.while" + name = "While" + node_type = "WHILE" + catalog = "Control" + kwargs = ["max_iterations", "conditions", "tasks"] + + def create_sockets(self) -> None: + self.inputs.clear() + self.outputs.clear() + inp = self.inputs.new("workgraph.any", "_wait") + inp.link_limit = 100000 + self.inputs.new("node_graph.int", "max_iterations") + self.inputs.new("workgraph.any", "tasks") + self.inputs.new("workgraph.any", "conditions") + self.outputs.new("workgraph.any", "_wait") + + +class Gather(Task): + """Gather""" + + identifier = "workgraph.aiida_gather" + name = "Gather" + node_type = "WORKCHAIN" + catalog = "Control" + kwargs = ["datas"] + + def create_sockets(self) -> None: + self.inputs.clear() + self.outputs.clear() + inp = self.inputs.new("workgraph.any", "datas") + inp.link_limit = 100000 + self.outputs.new("workgraph.any", "result") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "aiida_workgraph.executors.builtin", + "name": "GatherWorkChain", + } + + +class ToCtx(Task): + """ToCtx""" + + identifier = "workgraph.to_ctx" + name = "ToCtx" + node_type = "Control" + catalog = "Control" + args = ["key", "value"] + + def create_sockets(self) -> None: + self.inputs.clear() + self.outputs.clear() + self.inputs.new("workgraph.any", "key") + self.inputs.new("workgraph.any", "value") + self.outputs.new("workgraph.any", "result") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "builtins", + "name": "setattr", + } + + +class FromCtx(Task): + """FromCtx""" + + identifier = "workgraph.from_ctx" + name = "FromCtx" + node_type = "Control" + catalog = "Control" + args = ["key"] + + def create_sockets(self) -> None: + self.inputs.clear() + self.outputs.clear() + self.inputs.new("workgraph.any", "key") + self.outputs.new("workgraph.any", "result") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "builtins", + "name": "getattr", + } + + +class AiiDAInt(Task): + identifier = "workgraph.aiida_int" + name = "AiiDAInt" + node_type = "data" + catalog = "Test" + + args = ["value"] + + def create_sockets(self) -> None: + inp = self.inputs.new("workgraph.any", "value", default=0.0) + inp.add_property("workgraph.aiida_int", default=1.0) + self.outputs.new("workgraph.aiida_int", "result") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "aiida.orm", + "name": "Int", + } + + +class AiiDAFloat(Task): + identifier = "workgraph.aiida_float" + name = "AiiDAFloat" + node_type = "data" + catalog = "Test" + + args = ["value"] + + def create_sockets(self) -> None: + self.inputs.new("workgraph.aiida_float", "value", default=0.0) + self.outputs.new("workgraph.aiida_float", "result") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "aiida.orm", + "name": "Float", + } + + +class AiiDAString(Task): + identifier = "workgraph.aiida_string" + name = "AiiDAString" + node_type = "data" + catalog = "Test" + + args = ["value"] + + def create_sockets(self) -> None: + self.inputs.new("AiiDAString", "value", default="") + self.outputs.new("AiiDAString", "result") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "aiida.orm", + "name": "Str", + } + + +class AiiDAList(Task): + identifier = "workgraph.aiida_list" + name = "AiiDAList" + node_type = "data" + catalog = "Test" + + args = ["value"] + + def create_properties(self) -> None: + self.properties.new("BaseList", "value", default=[]) + + def create_sockets(self) -> None: + self.outputs.new("workgraph.any", "Parameters") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "aiida.orm", + "name": "List", + } + + +class AiiDADict(Task): + identifier = "workgraph.aiida_dict" + name = "AiiDADict" + node_type = "data" + catalog = "Test" + + args = ["value"] + + def create_properties(self) -> None: + self.properties.new("BaseDict", "value", default={}) + + def create_sockets(self) -> None: + self.outputs.new("workgraph.any", "Parameters") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "aiida.orm", + "name": "Dict", + } + + +class AiiDANode(Task): + """AiiDANode""" + + identifier = "workgraph.aiida_node" + name = "AiiDANode" + node_type = "node" + catalog = "Test" + kwargs = ["identifier", "pk", "uuid", "label"] + + def create_properties(self) -> None: + pass + + def create_sockets(self) -> None: + self.inputs.clear() + self.outputs.clear() + self.inputs.new("workgraph.any", "identifier") + self.inputs.new("workgraph.any", "pk") + self.inputs.new("workgraph.any", "uuid") + self.inputs.new("workgraph.any", "label") + self.outputs.new("workgraph.any", "node") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "aiida.orm", + "name": "load_node", + } + + +class AiiDACode(Task): + """AiiDACode""" + + identifier = "workgraph.aiida_code" + name = "AiiDACode" + node_type = "node" + catalog = "Test" + kwargs = ["identifier", "pk", "uuid", "label"] + + def create_sockets(self) -> None: + self.inputs.clear() + self.outputs.clear() + self.inputs.new("workgraph.any", "identifier") + self.inputs.new("workgraph.any", "pk") + self.inputs.new("workgraph.any", "uuid") + self.inputs.new("workgraph.any", "label") + self.outputs.new("workgraph.any", "Code") + + def get_executor(self) -> Dict[str, str]: + return { + "path": "aiida.orm", + "name": "load_code", + } diff --git a/aiida_workgraph/tasks/qe.py b/aiida_workgraph/tasks/qe.py deleted file mode 100644 index 8abc36c1..00000000 --- a/aiida_workgraph/tasks/qe.py +++ /dev/null @@ -1,75 +0,0 @@ -from typing import Dict -from aiida_workgraph.task import Task - - -class AiiDAKpoint(Task): - identifier = "AiiDAKpoint" - name = "AiiDAKpoint" - node_type = "data" - catalog = "Test" - - kwargs = ["mesh", "offset"] - - def create_properties(self) -> None: - self.properties.new("AiiDAIntVector", "mesh", default=[1, 1, 1], size=3) - self.properties.new("AiiDAIntVector", "offset", default=[0, 0, 0], size=3) - - def create_sockets(self) -> None: - self.outputs.new("Any", "Kpoint") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "KpointsData", - } - - -class AiiDAStructure(Task): - identifier = "AiiDAStructure" - name = "AiiDAStructure" - node_type = "data" - catalog = "Test" - - kwargs = ["cell", "kinds", "pbc1", "pbc2", "pbc3", "sites"] - - def create_properties(self) -> None: - self.properties.new("BaseList", "cell", default=[]) - self.properties.new("BaseList", "kinds", default=[]) - self.properties.new("Bool", "pbc1", default=True) - self.properties.new("Bool", "pbc2", default=True) - self.properties.new("Bool", "pbc3", default=True) - self.properties.new("BaseList", "sites", default=[]) - - def create_sockets(self) -> None: - self.outputs.new("Any", "Structure") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "StructureData", - } - - -class AiiDAPWPseudo(Task): - identifier = "AiiDAPWPseudo" - name = "AiiDAPWPseudo" - node_type = "Normal" - catalog = "Test" - - args = ["psuedo_familay", "structure"] - - def create_properties(self) -> None: - self.properties.new( - "AiiDAString", "psuedo_familay", default="SSSP/1.3/PBEsol/efficiency" - ) - - def create_sockets(self) -> None: - self.inputs.new("Any", "structure") - self.outputs.new("Any", "Pseudo") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida_workgraph.executors.qe", - "name": "get_pseudo_from_structure", - "type": "function", - } diff --git a/aiida_workgraph/tasks/test.py b/aiida_workgraph/tasks/test.py index 7ea58597..fde54802 100644 --- a/aiida_workgraph/tasks/test.py +++ b/aiida_workgraph/tasks/test.py @@ -2,175 +2,10 @@ from aiida_workgraph.task import Task -class AiiDAInt(Task): - identifier = "AiiDAInt" - name = "AiiDAInt" - node_type = "data" - catalog = "Test" - - args = ["value"] - kwargs = ["t"] - - def create_properties(self) -> None: - self.properties.new("AiiDAFloat", "t", default=1.0) - - def create_sockets(self) -> None: - inp = self.inputs.new("Any", "value", default=0.0) - inp.add_property("AiiDAInt", default=1.0) - self.outputs.new("AiiDAInt", "result") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "Int", - } - - -class AiiDAFloat(Task): - identifier = "AiiDAFloat" - name = "AiiDAFloat" - node_type = "data" - catalog = "Test" - - args = ["value"] - kwargs = ["t"] - - def create_properties(self) -> None: - self.properties.new("AiiDAFloat", "t", default=1.0) - - def create_sockets(self) -> None: - self.inputs.new("AiiDAFloat", "value", default=0.0) - self.outputs.new("AiiDAFloat", "result") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "Float", - } - - -class AiiDAString(Task): - identifier = "AiiDAString" - name = "AiiDAString" - node_type = "data" - catalog = "Test" - - args = ["value"] - kwargs = ["t"] - - def create_properties(self) -> None: - self.properties.new("AiiDAFloat", "t", default=1.0) - - def create_sockets(self) -> None: - self.inputs.new("AiiDAString", "value", default="") - self.outputs.new("AiiDAString", "result") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "Str", - } - - -class AiiDAList(Task): - identifier = "AiiDAList" - name = "AiiDAList" - node_type = "data" - catalog = "Test" - - args = ["value"] - - def create_properties(self) -> None: - self.properties.new("BaseList", "value", default=[]) - - def create_sockets(self) -> None: - self.outputs.new("Any", "Parameters") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "List", - } - - -class AiiDADict(Task): - identifier = "AiiDADict" - name = "AiiDADict" - node_type = "data" - catalog = "Test" - - args = ["value"] - - def create_properties(self) -> None: - self.properties.new("BaseDict", "value", default={}) - - def create_sockets(self) -> None: - self.outputs.new("Any", "Parameters") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "Dict", - } - - -class AiiDANode(Task): - """AiiDANode""" - - identifier = "AiiDANode" - name = "AiiDANode" - node_type = "node" - catalog = "Test" - kwargs = ["identifier", "pk", "uuid", "label"] - - def create_properties(self) -> None: - pass - - def create_sockets(self) -> None: - self.inputs.clear() - self.outputs.clear() - self.inputs.new("Any", "identifier") - self.inputs.new("Any", "pk") - self.inputs.new("Any", "uuid") - self.inputs.new("Any", "label") - self.outputs.new("Any", "node") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "load_node", - } - - -class AiiDACode(Task): - """AiiDACode""" - - identifier = "AiiDACode" - name = "AiiDACode" - node_type = "node" - catalog = "Test" - kwargs = ["identifier", "pk", "uuid", "label"] - - def create_sockets(self) -> None: - self.inputs.clear() - self.outputs.clear() - self.inputs.new("Any", "identifier") - self.inputs.new("Any", "pk") - self.inputs.new("Any", "uuid") - self.inputs.new("Any", "label") - self.outputs.new("Any", "Code") - - def get_executor(self) -> Dict[str, str]: - return { - "path": "aiida.orm", - "name": "load_code", - } - - -class AiiDAAdd(Task): +class TestAdd(Task): - identifier: str = "AiiDAAdd" - name = "AiiDAAdd" + identifier: str = "workgraph.test_add" + name = "TestAAdd" node_type = "CALCFUNCTION" catalog = "Test" @@ -178,16 +13,16 @@ class AiiDAAdd(Task): kwargs = ["t"] def create_properties(self) -> None: - self.properties.new("AiiDAFloat", "t", default=1.0) + self.properties.new("workgraph.aiida_float", "t", default=1.0) def create_sockets(self) -> None: self.inputs.clear() self.outputs.clear() - inp = self.inputs.new("AiiDAFloat", "x") - inp.add_property("AiiDAFloat", "x", default=0.0) - inp = self.inputs.new("AiiDAFloat", "y") - inp.add_property("AiiDAFloat", "y", default=0.0) - self.outputs.new("AiiDAFloat", "sum") + inp = self.inputs.new("workgraph.aiida_float", "x") + inp.add_property("workgraph.aiida_float", "x", default=0.0) + inp = self.inputs.new("workgraph.aiida_float", "y") + inp.add_property("workgraph.aiida_float", "y", default=0.0) + self.outputs.new("workgraph.aiida_float", "sum") def get_executor(self) -> Dict[str, str]: return { @@ -196,10 +31,10 @@ def get_executor(self) -> Dict[str, str]: } -class AiiDAGreater(Task): +class TestGreater(Task): - identifier: str = "AiiDAGreater" - name = "AiiDAGreater" + identifier: str = "workgraph.test_greater" + name = "TestGreater" node_type = "CALCFUNCTION" catalog = "Test" kwargs = ["x", "y"] @@ -210,8 +45,8 @@ def create_properties(self) -> None: def create_sockets(self) -> None: self.inputs.clear() self.outputs.clear() - self.inputs.new("AiiDAFloat", "x") - self.inputs.new("AiiDAFloat", "y") + self.inputs.new("workgraph.aiida_float", "x") + self.inputs.new("workgraph.aiida_float", "y") self.outputs.new("AiiDABool", "result") def get_executor(self) -> Dict[str, str]: @@ -221,10 +56,10 @@ def get_executor(self) -> Dict[str, str]: } -class AiiDASumDiff(Task): +class TestSumDiff(Task): - identifier: str = "AiiDASumDiff" - name = "AiiDASumDiff" + identifier: str = "workgraph.test_sum_diff" + name = "TestSumDiff" node_type = "CALCFUNCTION" catalog = "Test" @@ -232,17 +67,17 @@ class AiiDASumDiff(Task): kwargs = ["t"] def create_properties(self) -> None: - self.properties.new("AiiDAFloat", "t", default=1.0) + self.properties.new("workgraph.aiida_float", "t", default=1.0) def create_sockets(self) -> None: self.inputs.clear() self.outputs.clear() - inp = self.inputs.new("AiiDAFloat", "x") - inp.add_property("AiiDAFloat", "x", default=0.0) - inp = self.inputs.new("AiiDAFloat", "y") - inp.add_property("AiiDAFloat", "y", default=0.0) - self.outputs.new("AiiDAFloat", "sum") - self.outputs.new("AiiDAFloat", "diff") + inp = self.inputs.new("workgraph.aiida_float", "x") + inp.add_property("workgraph.aiida_float", "x", default=0.0) + inp = self.inputs.new("workgraph.aiida_float", "y") + inp.add_property("workgraph.aiida_float", "y", default=0.0) + self.outputs.new("workgraph.aiida_float", "sum") + self.outputs.new("workgraph.aiida_float", "diff") def get_executor(self) -> Dict[str, str]: return { @@ -251,10 +86,10 @@ def get_executor(self) -> Dict[str, str]: } -class AiiDAArithmeticMultiplyAdd(Task): +class TestArithmeticMultiplyAdd(Task): - identifier: str = "AiiDAArithmeticMultiplyAdd" - name = "AiiDAArithmeticMultiplyAdd" + identifier: str = "workgraph.test_arithmetic_multiply_add" + name = "TestArithmeticMultiplyAdd" node_type = "WORKCHAIN" catalog = "Test" kwargs = ["code", "x", "y", "z"] @@ -265,14 +100,14 @@ def create_properties(self) -> None: def create_sockets(self) -> None: self.inputs.clear() self.outputs.clear() - self.inputs.new("Any", "code") - inp = self.inputs.new("AiiDAInt", "x") - inp.add_property("AiiDAInt", "x", default=0.0) - inp = self.inputs.new("AiiDAInt", "y") - inp.add_property("AiiDAInt", "y", default=0.0) - inp = self.inputs.new("AiiDAInt", "z") - inp.add_property("AiiDAInt", "z", default=0.0) - self.outputs.new("AiiDAInt", "result") + self.inputs.new("workgraph.any", "code") + inp = self.inputs.new("workgraph.aiida_int", "x") + inp.add_property("workgraph.aiida_int", "x", default=0.0) + inp = self.inputs.new("workgraph.aiida_int", "y") + inp.add_property("workgraph.aiida_int", "y", default=0.0) + inp = self.inputs.new("workgraph.aiida_int", "z") + inp.add_property("workgraph.aiida_int", "z", default=0.0) + self.outputs.new("workgraph.aiida_int", "result") def get_executor(self) -> Dict[str, str]: return { diff --git a/aiida_workgraph/utils/__init__.py b/aiida_workgraph/utils/__init__.py index 2ca49c2e..7d055164 100644 --- a/aiida_workgraph/utils/__init__.py +++ b/aiida_workgraph/utils/__init__.py @@ -27,6 +27,8 @@ def get_executor(data: Dict[str, Any]) -> Union[Process, Any]: executor = CalculationFactory(data["name"]) elif type == "DataFactory": executor = DataFactory(data["name"]) + elif data["name"] == "" and data["path"] == "": + executor = None else: module = importlib.import_module("{}".format(data.get("path", ""))) executor = getattr(module, data["name"]) @@ -52,16 +54,20 @@ def create_data_node(executor: orm.Data, args: list, kwargs: dict) -> orm.Node: return data_node -def get_nested_dict(d: Dict, name: str, allow_none: bool = False) -> Any: - """ +def get_nested_dict(d: Dict, name: str, **kwargs) -> Any: + """Get the value from a nested dictionary. + If default is provided, return the default value if the key is not found. + Otherwise, raise ValueError. + For example: + d = {"base": {"pw": {"parameters": 2}}} name = "base.pw.parameters" """ keys = name.split(".") current = d for key in keys: if key not in current: - if allow_none: - return None + if "default" in kwargs: + return kwargs.get("default") else: raise ValueError(f"{name} not exist in {d}") current = current[key] @@ -573,31 +579,37 @@ def serialize_function(func: Callable) -> Dict[str, Any]: import textwrap import cloudpickle as pickle - # we need save the source code explicitly, because in the case of jupyter notebook, - # the source code is not saved in the pickle file - source_code = inspect.getsource(func) - # Split the source into lines for processing - source_code_lines = source_code.split("\n") - function_source_code = "\n".join(source_code_lines) - # Find the first line of the actual function definition - for i, line in enumerate(source_code_lines): - if line.strip().startswith("def "): - break - function_source_code_without_decorator = "\n".join(source_code_lines[i:]) - function_source_code_without_decorator = textwrap.dedent( - function_source_code_without_decorator - ) - # we also need to include the necessary imports for the types used in the type hints. try: - required_imports = get_required_imports(func) + # we need save the source code explicitly, because in the case of jupyter notebook, + # the source code is not saved in the pickle file + source_code = inspect.getsource(func) + # Split the source into lines for processing + source_code_lines = source_code.split("\n") + function_source_code = "\n".join(source_code_lines) + # Find the first line of the actual function definition + for i, line in enumerate(source_code_lines): + if line.strip().startswith("def "): + break + function_source_code_without_decorator = "\n".join(source_code_lines[i:]) + function_source_code_without_decorator = textwrap.dedent( + function_source_code_without_decorator + ) + # we also need to include the necessary imports for the types used in the type hints. + try: + required_imports = get_required_imports(func) + except Exception as e: + required_imports = {} + print(f"Failed to get required imports for function {func.__name__}: {e}") + # Generate import statements + import_statements = "\n".join( + f"from {module} import {', '.join(types)}" + for module, types in required_imports.items() + ) except Exception as e: - required_imports = {} - print(f"Failed to get required imports for function {func.__name__}: {e}") - # Generate import statements - import_statements = "\n".join( - f"from {module} import {', '.join(types)}" - for module, types in required_imports.items() - ) + print(f"Failed to serialize function {func.__name__}: {e}") + function_source_code = "" + function_source_code_without_decorator = "" + import_statements = "" return { "executor": pickle.dumps(func), "type": "function", @@ -630,10 +642,14 @@ def workgraph_to_short_json( ] wgdata_short["nodes"][name] = { "label": task["name"], + "node_type": task["metadata"]["node_type"], "inputs": inputs, "outputs": [], "position": task["position"], } + # Add properties to nodes if it is a While task + if task["metadata"]["node_type"].upper() == "WHILE": + wgdata_short["nodes"][name]["properties"] = task["properties"] # Add links to nodes for link in wgdata["links"]: wgdata_short["nodes"][link["to_node"]]["inputs"].append( diff --git a/aiida_workgraph/utils/analysis.py b/aiida_workgraph/utils/analysis.py index 8d5c023d..b1c14521 100644 --- a/aiida_workgraph/utils/analysis.py +++ b/aiida_workgraph/utils/analysis.py @@ -60,6 +60,7 @@ def save(self) -> None: """ self.build_task_link() self.build_connectivity() + self.assign_while_zone() if self.exist_in_db() or self.restart_process is not None: new_tasks, modified_tasks, update_metadata = self.check_diff( self.restart_process @@ -94,6 +95,28 @@ def build_task_link(self) -> None: to_socket["links"].append(link) from_socket["links"].append(link) + def assign_while_zone(self) -> None: + """Assign while zone for each task.""" + self.wgdata["connectivity"]["while"] = {} + # assign parent_task for each task + for name, task in self.wgdata["tasks"].items(): + if task["metadata"]["node_type"].upper() == "WHILE": + input_tasks = [] + for name in task["properties"]["tasks"]["value"]: + self.wgdata["tasks"][name]["parent_task"] = task["name"] + # find all the input tasks which outside the while zone + for input in self.wgdata["tasks"][name]["inputs"]: + for link in input["links"]: + if ( + link["from_node"] + not in task["properties"]["tasks"]["value"] + ): + input_tasks.append(link["from_node"]) + task["execution_count"] = 0 + self.wgdata["connectivity"]["while"][task["name"]] = { + "input_tasks": input_tasks + } + def insert_workgraph_to_db(self) -> None: """Save a new workgraph in the database. diff --git a/aiida_workgraph/web/frontend/package-lock.json b/aiida_workgraph/web/frontend/package-lock.json index 8e02210e..ec8aae70 100644 --- a/aiida_workgraph/web/frontend/package-lock.json +++ b/aiida_workgraph/web/frontend/package-lock.json @@ -21,7 +21,7 @@ "@types/three": "^0.156.0", "antd": "^5.12.1", "d3": "^7.8.5", - "elkjs": "^0.8.2", + "elkjs": "^0.9.2", "fs": "^0.0.1-security", "mathjs": "^12.3.0", "moment": "^2.29.4", @@ -34,18 +34,18 @@ "react-scripts": "5.0.1", "react-syntax-highlighter": "^15.5.0", "react-toastify": "^9.1.3", - "rete": "^2.0.2", - "rete-area-3d-plugin": "^2.0.3", - "rete-area-plugin": "^2.0.1", - "rete-auto-arrange-plugin": "^2.0.0", - "rete-connection-plugin": "^2.0.0", - "rete-connection-reroute-plugin": "^2.0.0", - "rete-context-menu-plugin": "^2.0.0", - "rete-minimap-plugin": "^2.0.1", - "rete-react-plugin": "^2.0.4", - "rete-readonly-plugin": "^2.0.0", - "rete-render-utils": "^2.0.1", - "styled-components": "^5.3.11", + "rete": "2.0.3", + "rete-area-plugin": "2.0.3", + "rete-auto-arrange-plugin": "2.0.1", + "rete-connection-plugin": "2.0.1", + "rete-connection-reroute-plugin": "2.0.0", + "rete-context-menu-plugin": "2.0.0", + "rete-minimap-plugin": "2.0.1", + "rete-react-plugin": "2.0.5", + "rete-readonly-plugin": "2.0.0", + "rete-render-utils": "2.0.2", + "rete-scopes-plugin": "2.1.0", + "styled-components": "6.1.8", "three": "^0.156.1", "typescript": "^4.9.5", "vis-timeline": "^7.7.3", @@ -2437,11 +2437,6 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, - "node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" - }, "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", @@ -4586,6 +4581,11 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -5782,21 +5782,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-styled-components": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", - "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "lodash": "^4.17.21", - "picomatch": "^2.3.1" - }, - "peerDependencies": { - "styled-components": ">= 2" - } - }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -7856,9 +7841,9 @@ } }, "node_modules/elkjs": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", - "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==" + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==" }, "node_modules/emittery": { "version": "0.8.1", @@ -9800,6 +9785,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, "dependencies": { "react-is": "^16.7.0" } @@ -9807,7 +9793,8 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true }, "node_modules/hoopy": { "version": "0.1.4", @@ -14171,9 +14158,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -14191,7 +14178,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -16895,32 +16882,18 @@ } }, "node_modules/rete": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/rete/-/rete-2.0.2.tgz", - "integrity": "sha512-VZhyWl0E3dzcRRiN5OIVK4CIVcADZHO4XCFs85fjoi4ZYCPcB3P608wzq5MbdlYOfptPyuvKOrRqgFCtILdKIw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/rete/-/rete-2.0.3.tgz", + "integrity": "sha512-/xzcyEBhVXhMZVZHElnYaLKOmTEuwlnul9Wfjvxw5sdl/+6Nqn2nyqIaW4koefrFpIWZy9aitnjnP3zeCMVDuw==", "hasInstallScript": true, "dependencies": { "@babel/runtime": "^7.21.0" } }, - "node_modules/rete-area-3d-plugin": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/rete-area-3d-plugin/-/rete-area-3d-plugin-2.0.3.tgz", - "integrity": "sha512-1Pc6jfmtghj0pUKz3TVD3wVOYS8nnqJvaZJ9zGqPYufTsAqdV6idOq2K1AWUayoeqleTCymY2YZ4Nj/0TmCTDA==", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "peerDependencies": { - "rete": "^2.0.1", - "rete-area-plugin": "^2.0.0", - "rete-render-utils": "^2.0.0", - "three": ">= 0.152.2 < 0.157.0" - } - }, "node_modules/rete-area-plugin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.0.1.tgz", - "integrity": "sha512-H7IGv2Tfm1Tk928Hl6O9pS3JCmKboCFY4xqGm2TCXvzVAlHvmUV7mtkxlT1fCAZQMNin6ktwthCdQ455euHDgQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.0.3.tgz", + "integrity": "sha512-RWHCoMh0HJ7arnBEaU51j1J4AuK+qWYR7tadVom8uiwkXK7Xv9VeJkKy8xWT+Ckw+g2DlsI4SWrQkMG7wfWtug==", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -16929,9 +16902,9 @@ } }, "node_modules/rete-auto-arrange-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rete-auto-arrange-plugin/-/rete-auto-arrange-plugin-2.0.0.tgz", - "integrity": "sha512-wyrJ+DW94J1E4ceTX6XVHt7lnp0eNXdwHKnuREY8ePl3BO8buLutvVpuMFwJEVW6AbnUVcKY38/huAwp4wDGoQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/rete-auto-arrange-plugin/-/rete-auto-arrange-plugin-2.0.1.tgz", + "integrity": "sha512-vHxsrI+l3wxZzxPxG7hcgUbacXQfEc1ZEE28r08O1kEy0kUyNkJR5OeCiSizZ4VucsDmu21WUtFVa1rl5h+e1A==", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -16943,9 +16916,9 @@ } }, "node_modules/rete-connection-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rete-connection-plugin/-/rete-connection-plugin-2.0.0.tgz", - "integrity": "sha512-8M+UC6gcWwTi0PEICprmCaoGxwEA4x42z0ywx3O5NQSILvkhWcvQXzHcyvwGx/LTsJT/UPzNjCfixu/TbRyTEw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/rete-connection-plugin/-/rete-connection-plugin-2.0.1.tgz", + "integrity": "sha512-KE1IcjeOQtHgkByODtWS5hgRJDGhR3Z9sZyJAEd7YMgI6o+KUIflcNjbkvhJvPeIAv6WlEAh7ZkwdLhF9bkr4w==", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -16992,9 +16965,9 @@ } }, "node_modules/rete-react-plugin": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/rete-react-plugin/-/rete-react-plugin-2.0.4.tgz", - "integrity": "sha512-t+rsaZ6wUFVbO1krfzUl8GInHI+V9Zp8q2fLj8NnDLVKRxQzq1W0sfpBvBK262dkt2TEZvHyeX9M+eqTlaowHQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/rete-react-plugin/-/rete-react-plugin-2.0.5.tgz", + "integrity": "sha512-xoui2+Mv6iqpRTxccAu3MZv3+l5LYk4AmtqGWEqlCIwZjplrsAoVeOLYq235spwf+vd3ujzapnycEzYF9aj3cA==", "dependencies": { "@babel/runtime": "^7.21.0", "usehooks-ts": "^2.9.1" @@ -17022,9 +16995,9 @@ } }, "node_modules/rete-render-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/rete-render-utils/-/rete-render-utils-2.0.1.tgz", - "integrity": "sha512-mzNVADCE1iV0AlkVyz1Pai34GG55VYBIWWOv9MqHUl7jlnpNIIkx+hARIc3wgabcye46IdswQPUApuARhvjbmA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rete-render-utils/-/rete-render-utils-2.0.2.tgz", + "integrity": "sha512-f4kj+dFL5QrebOkjCdwi8htHteDFbKyqrVdFDToEUvGuGod1sdLeKxOPBOhwyYDB4Zxd3Cq84I93vD2etrTL9g==", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -17033,6 +17006,18 @@ "rete-area-plugin": "^2.0.0" } }, + "node_modules/rete-scopes-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/rete-scopes-plugin/-/rete-scopes-plugin-2.1.0.tgz", + "integrity": "sha512-qpbTvpPlKb52vPX56XA41RjK4JARWE07k3RnjvM4sNYfrdEszX8m0ZaTAeWGaslEuVdlY/rAOl6NxCbiU6E0sg==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.1", + "rete-area-plugin": "^2.0.0" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -17614,9 +17599,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -18043,23 +18028,22 @@ } }, "node_modules/styled-components": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", - "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" }, "engines": { - "node": ">=10" + "node": ">= 16" }, "funding": { "type": "opencollective", @@ -18067,10 +18051,56 @@ }, "peerDependencies": { "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-is": ">= 16.8.0" + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "node_modules/styled-components/node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -18087,9 +18117,9 @@ } }, "node_modules/stylis": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", - "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" }, "node_modules/sucrase": { "version": "3.34.0", @@ -21603,11 +21633,6 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, - "@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" - }, "@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", @@ -23212,6 +23237,11 @@ "csstype": "^3.0.2" } }, + "@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -24094,18 +24124,6 @@ "@babel/helper-define-polyfill-provider": "^0.4.3" } }, - "babel-plugin-styled-components": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", - "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "lodash": "^4.17.21", - "picomatch": "^2.3.1" - } - }, "babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -25605,9 +25623,9 @@ } }, "elkjs": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", - "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==" + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==" }, "emittery": { "version": "0.8.1", @@ -27017,6 +27035,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, "requires": { "react-is": "^16.7.0" }, @@ -27024,7 +27043,8 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true } } }, @@ -30177,13 +30197,13 @@ } }, "postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "postcss-attribute-case-insensitive": { @@ -31942,41 +31962,33 @@ "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==" }, "rete": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/rete/-/rete-2.0.2.tgz", - "integrity": "sha512-VZhyWl0E3dzcRRiN5OIVK4CIVcADZHO4XCFs85fjoi4ZYCPcB3P608wzq5MbdlYOfptPyuvKOrRqgFCtILdKIw==", - "requires": { - "@babel/runtime": "^7.21.0" - } - }, - "rete-area-3d-plugin": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/rete-area-3d-plugin/-/rete-area-3d-plugin-2.0.3.tgz", - "integrity": "sha512-1Pc6jfmtghj0pUKz3TVD3wVOYS8nnqJvaZJ9zGqPYufTsAqdV6idOq2K1AWUayoeqleTCymY2YZ4Nj/0TmCTDA==", + "resolved": "https://registry.npmjs.org/rete/-/rete-2.0.3.tgz", + "integrity": "sha512-/xzcyEBhVXhMZVZHElnYaLKOmTEuwlnul9Wfjvxw5sdl/+6Nqn2nyqIaW4koefrFpIWZy9aitnjnP3zeCMVDuw==", "requires": { "@babel/runtime": "^7.21.0" } }, "rete-area-plugin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.0.1.tgz", - "integrity": "sha512-H7IGv2Tfm1Tk928Hl6O9pS3JCmKboCFY4xqGm2TCXvzVAlHvmUV7mtkxlT1fCAZQMNin6ktwthCdQ455euHDgQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.0.3.tgz", + "integrity": "sha512-RWHCoMh0HJ7arnBEaU51j1J4AuK+qWYR7tadVom8uiwkXK7Xv9VeJkKy8xWT+Ckw+g2DlsI4SWrQkMG7wfWtug==", "requires": { "@babel/runtime": "^7.21.0" } }, "rete-auto-arrange-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rete-auto-arrange-plugin/-/rete-auto-arrange-plugin-2.0.0.tgz", - "integrity": "sha512-wyrJ+DW94J1E4ceTX6XVHt7lnp0eNXdwHKnuREY8ePl3BO8buLutvVpuMFwJEVW6AbnUVcKY38/huAwp4wDGoQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/rete-auto-arrange-plugin/-/rete-auto-arrange-plugin-2.0.1.tgz", + "integrity": "sha512-vHxsrI+l3wxZzxPxG7hcgUbacXQfEc1ZEE28r08O1kEy0kUyNkJR5OeCiSizZ4VucsDmu21WUtFVa1rl5h+e1A==", "requires": { "@babel/runtime": "^7.21.0" } }, "rete-connection-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rete-connection-plugin/-/rete-connection-plugin-2.0.0.tgz", - "integrity": "sha512-8M+UC6gcWwTi0PEICprmCaoGxwEA4x42z0ywx3O5NQSILvkhWcvQXzHcyvwGx/LTsJT/UPzNjCfixu/TbRyTEw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/rete-connection-plugin/-/rete-connection-plugin-2.0.1.tgz", + "integrity": "sha512-KE1IcjeOQtHgkByODtWS5hgRJDGhR3Z9sZyJAEd7YMgI6o+KUIflcNjbkvhJvPeIAv6WlEAh7ZkwdLhF9bkr4w==", "requires": { "@babel/runtime": "^7.21.0" } @@ -32006,9 +32018,9 @@ } }, "rete-react-plugin": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/rete-react-plugin/-/rete-react-plugin-2.0.4.tgz", - "integrity": "sha512-t+rsaZ6wUFVbO1krfzUl8GInHI+V9Zp8q2fLj8NnDLVKRxQzq1W0sfpBvBK262dkt2TEZvHyeX9M+eqTlaowHQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/rete-react-plugin/-/rete-react-plugin-2.0.5.tgz", + "integrity": "sha512-xoui2+Mv6iqpRTxccAu3MZv3+l5LYk4AmtqGWEqlCIwZjplrsAoVeOLYq235spwf+vd3ujzapnycEzYF9aj3cA==", "requires": { "@babel/runtime": "^7.21.0", "usehooks-ts": "^2.9.1" @@ -32023,9 +32035,17 @@ } }, "rete-render-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/rete-render-utils/-/rete-render-utils-2.0.1.tgz", - "integrity": "sha512-mzNVADCE1iV0AlkVyz1Pai34GG55VYBIWWOv9MqHUl7jlnpNIIkx+hARIc3wgabcye46IdswQPUApuARhvjbmA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rete-render-utils/-/rete-render-utils-2.0.2.tgz", + "integrity": "sha512-f4kj+dFL5QrebOkjCdwi8htHteDFbKyqrVdFDToEUvGuGod1sdLeKxOPBOhwyYDB4Zxd3Cq84I93vD2etrTL9g==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, + "rete-scopes-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/rete-scopes-plugin/-/rete-scopes-plugin-2.1.0.tgz", + "integrity": "sha512-qpbTvpPlKb52vPX56XA41RjK4JARWE07k3RnjvM4sNYfrdEszX8m0ZaTAeWGaslEuVdlY/rAOl6NxCbiU6E0sg==", "requires": { "@babel/runtime": "^7.21.0" } @@ -32460,9 +32480,9 @@ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" }, "source-map-loader": { "version": "3.0.2", @@ -32779,20 +32799,51 @@ "requires": {} }, "styled-components": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", - "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "requires": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "dependencies": { + "@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + } } }, "stylehacks": { @@ -32805,9 +32856,9 @@ } }, "stylis": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", - "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" }, "sucrase": { "version": "3.34.0", diff --git a/aiida_workgraph/web/frontend/package.json b/aiida_workgraph/web/frontend/package.json index 6607055e..31ebcffd 100644 --- a/aiida_workgraph/web/frontend/package.json +++ b/aiida_workgraph/web/frontend/package.json @@ -29,18 +29,18 @@ "react-scripts": "5.0.1", "react-syntax-highlighter": "^15.5.0", "react-toastify": "^9.1.3", - "rete": "^2.0.2", - "rete-area-3d-plugin": "^2.0.3", - "rete-area-plugin": "^2.0.1", - "rete-auto-arrange-plugin": "^2.0.0", - "rete-connection-plugin": "^2.0.0", - "rete-connection-reroute-plugin": "^2.0.0", - "rete-context-menu-plugin": "^2.0.0", - "rete-minimap-plugin": "^2.0.1", - "rete-react-plugin": "^2.0.4", - "rete-readonly-plugin": "^2.0.0", - "rete-render-utils": "^2.0.1", - "styled-components": "^5.3.11", + "rete": "2.0.3", + "rete-area-plugin": "2.0.3", + "rete-auto-arrange-plugin": "2.0.1", + "rete-scopes-plugin": "2.1.0", + "rete-connection-plugin": "2.0.1", + "rete-connection-reroute-plugin": "2.0.0", + "rete-context-menu-plugin": "2.0.0", + "rete-minimap-plugin": "2.0.1", + "rete-react-plugin": "2.0.5", + "rete-readonly-plugin": "2.0.0", + "rete-render-utils": "2.0.2", + "styled-components": "6.1.8", "three": "^0.156.1", "typescript": "^4.9.5", "vis-timeline": "^7.7.3", diff --git a/aiida_workgraph/web/frontend/src/App.js b/aiida_workgraph/web/frontend/src/App.js index afd69471..54170bee 100644 --- a/aiida_workgraph/web/frontend/src/App.js +++ b/aiida_workgraph/web/frontend/src/App.js @@ -1,4 +1,3 @@ -import React from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import Home from './components/Home'; import WorkGraphTable from './components/WorkGraphTable'; diff --git a/aiida_workgraph/web/frontend/src/rete/default.ts b/aiida_workgraph/web/frontend/src/rete/default.ts index c86d3c13..412e9649 100644 --- a/aiida_workgraph/web/frontend/src/rete/default.ts +++ b/aiida_workgraph/web/frontend/src/rete/default.ts @@ -5,6 +5,7 @@ import { Presets as ConnectionPresets } from "rete-connection-plugin"; import { ReactPlugin, Presets, ReactArea2D } from "rete-react-plugin"; +import { ScopesPlugin, Presets as ScopesPresets } from "rete-scopes-plugin"; import { MinimapExtra, MinimapPlugin } from "rete-minimap-plugin"; import { ContextMenuPlugin, @@ -47,10 +48,52 @@ interface NodeMap { } +export async function loadJSON(editor: NodeEditor, area: any, workgraphData: any) { + + // Adding nodes based on workgraphData + const nodeMap: NodeMap = {}; // To keep track of created nodes for linking + for (const nodeId in workgraphData.nodes) { + const nodeData = workgraphData.nodes[nodeId]; + const node = createDynamicNode(nodeData); + await editor.addNode(node); + nodeMap[nodeId] = node; // Storing reference to the node + } + // Adding connections based on workgraphData + workgraphData.links.forEach(async (link: LinkData) => { // Specify the type of link here + const fromNode = nodeMap[link.from_node]; + const toNode = nodeMap[link.to_node]; + if (fromNode && toNode) { + await editor.addConnection(new Connection(fromNode, link.from_socket, toNode, link.to_socket)); + } + }); + + // Add while zones + console.log("Adding while zone: "); + for (const nodeId in workgraphData.nodes) { + const nodeData = workgraphData.nodes[nodeId]; + // if node_type is "WHILE", find all + console.log("Node type: ", nodeData['node_type']); + if (nodeData['node_type'] === "WHILE") { + // find the node + const node = nodeMap[nodeData.label]; + const tasks = nodeData['properties']['tasks']['value']; + // find the id of all nodes in the editor that has a label in while_zone + for (const nodeId in tasks) { + const node1 = nodeMap[tasks[nodeId]]; + console.log("Setting parent of node", node1, "to", node); + node1.parent = node.id; + area.update('node', node1.id); + } + area.update('node', node.id); + } + } +} + class Node extends ClassicPreset.Node { width = 180; height = 100; + parent?: string; } class Connection extends ClassicPreset.Connection {} @@ -90,6 +133,7 @@ export async function createEditor(container: HTMLElement, workgraphData: any) { const area = new AreaPlugin(container); const connection = new ConnectionPlugin(); const render = new ReactPlugin(); + const scopes = new ScopesPlugin(); const arrange = new AutoArrangePlugin(); const contextMenu = new ContextMenuPlugin({ items: ContextMenuPresets.classic.setup([ @@ -108,6 +152,7 @@ export async function createEditor(container: HTMLElement, workgraphData: any) { render.addPreset(Presets.minimap.setup({ size: 200 })); connection.addPreset(ConnectionPresets.classic.setup()); + scopes.addPreset(ScopesPresets.classic.setup()); const applier = new ArrangeAppliers.TransitionApplier({ duration: 500, @@ -122,28 +167,14 @@ export async function createEditor(container: HTMLElement, workgraphData: any) { editor.use(area); // area.use(connection); area.use(render); + area.use(scopes); area.use(arrange); area.use(contextMenu); area.use(minimap); AreaExtensions.simpleNodesOrder(area); - // Adding nodes based on workgraphData - const nodeMap: NodeMap = {}; // To keep track of created nodes for linking - for (const nodeId in workgraphData.nodes) { - const nodeData = workgraphData.nodes[nodeId]; - const node = createDynamicNode(nodeData); - await editor.addNode(node); - nodeMap[nodeId] = node; // Storing reference to the node - } - // Adding connections based on workgraphData - workgraphData.links.forEach(async (link: LinkData) => { // Specify the type of link here - const fromNode = nodeMap[link.from_node]; - const toNode = nodeMap[link.to_node]; - if (fromNode && toNode) { - await editor.addConnection(new Connection(fromNode, link.from_socket, toNode, link.to_socket)); - } - }); + await loadJSON(editor, area, workgraphData); async function layout(animate: boolean) { await arrange.layout({ applier: animate ? applier : undefined }); diff --git a/aiida_workgraph/widget/js/default_rete.ts b/aiida_workgraph/widget/js/default_rete.ts index 261efc66..04fe9aac 100644 --- a/aiida_workgraph/widget/js/default_rete.ts +++ b/aiida_workgraph/widget/js/default_rete.ts @@ -5,6 +5,7 @@ import { Presets as ConnectionPresets } from "rete-connection-plugin"; import { ReactPlugin, Presets, ReactArea2D } from "rete-react-plugin"; +import { ScopesPlugin, Presets as ScopesPresets } from "rete-scopes-plugin"; import { MinimapExtra, MinimapPlugin } from "rete-minimap-plugin"; import { ContextMenuPlugin, @@ -90,6 +91,23 @@ export async function loadJSON(editor, area, layout, workgraphData) { workgraphData.links.forEach(async (link: LinkData) => { // Specify the type of link here await addLink(editor, area, layout, link); }); + + // Add while zones + console.log("Adding while zone: "); + for (const nodeId in workgraphData.nodes) { + const nodeData = workgraphData.nodes[nodeId]; + // if node_type is "WHILE", find all + if (nodeData['node_type'] === "WHILE") { + // find the node + const node = editor.nodeMap[nodeData.label]; + const tasks = nodeData['properties']['tasks']['value']; + // find the id of all nodes in the editor that has a label in while_zone + for (const nodeId in tasks) { + const node1 = editor.nodeMap[tasks[nodeId]]; + node1.parent = node.id; + } + } + } } export async function addNode(editor, area, nodeData) { @@ -161,6 +179,7 @@ export async function createEditor(container: HTMLElement, settings: any) { const area = new AreaPlugin(container); const connection = new ConnectionPlugin(); const render = new ReactPlugin(); + const scopes = new ScopesPlugin(); const arrange = new AutoArrangePlugin(); const contextMenu = new ContextMenuPlugin({ items: ContextMenuPresets.classic.setup([ @@ -170,15 +189,17 @@ export async function createEditor(container: HTMLElement, settings: any) { boundViewport: true }); - AreaExtensions.selectableNodes(area, AreaExtensions.selector(), { - accumulating: AreaExtensions.accumulateOnCtrl() - }); + const selector = AreaExtensions.selector(); + const accumulating = AreaExtensions.accumulateOnCtrl(); + + AreaExtensions.selectableNodes(area, selector, { accumulating }); render.addPreset(Presets.classic.setup()); render.addPreset(Presets.contextMenu.setup()); render.addPreset(Presets.minimap.setup({ size: 200 })); connection.addPreset(ConnectionPresets.classic.setup()); + scopes.addPreset(ScopesPresets.classic.setup()); const applier = new ArrangeAppliers.TransitionApplier({ duration: 500, @@ -193,6 +214,7 @@ export async function createEditor(container: HTMLElement, settings: any) { editor.use(area); // area.use(connection); area.use(render); + area.use(scopes); area.use(arrange); area.use(contextMenu); if (settings.minimap) { diff --git a/aiida_workgraph/widget/package-lock.json b/aiida_workgraph/widget/package-lock.json index 282b32d8..95ff827b 100644 --- a/aiida_workgraph/widget/package-lock.json +++ b/aiida_workgraph/widget/package-lock.json @@ -19,7 +19,8 @@ "rete-minimap-plugin": "^2.0.1", "rete-react-plugin": "^2.0.4", "rete-readonly-plugin": "^2.0.0", - "rete-render-utils": "^2.0.1" + "rete-render-utils": "^2.0.1", + "rete-scopes-plugin": "2.1.0" }, "devDependencies": { "@types/react": "^18.2.61", @@ -2162,6 +2163,18 @@ "rete-area-plugin": "^2.0.0" } }, + "node_modules/rete-scopes-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/rete-scopes-plugin/-/rete-scopes-plugin-2.1.0.tgz", + "integrity": "sha512-qpbTvpPlKb52vPX56XA41RjK4JARWE07k3RnjvM4sNYfrdEszX8m0ZaTAeWGaslEuVdlY/rAOl6NxCbiU6E0sg==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.1", + "rete-area-plugin": "^2.0.0" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -3928,6 +3941,14 @@ "@babel/runtime": "^7.21.0" } }, + "rete-scopes-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/rete-scopes-plugin/-/rete-scopes-plugin-2.1.0.tgz", + "integrity": "sha512-qpbTvpPlKb52vPX56XA41RjK4JARWE07k3RnjvM4sNYfrdEszX8m0ZaTAeWGaslEuVdlY/rAOl6NxCbiU6E0sg==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", diff --git a/aiida_workgraph/widget/package.json b/aiida_workgraph/widget/package.json index e51a6aae..18e9a5c0 100644 --- a/aiida_workgraph/widget/package.json +++ b/aiida_workgraph/widget/package.json @@ -13,6 +13,7 @@ "rete-area-3d-plugin": "^2.0.3", "rete-area-plugin": "^2.0.1", "rete-auto-arrange-plugin": "^2.0.0", + "rete-scopes-plugin": "2.1.0", "rete-connection-plugin": "^2.0.0", "rete-connection-reroute-plugin": "^2.0.0", "rete-context-menu-plugin": "^2.0.0", diff --git a/aiida_workgraph/widget/src/widget/html_template.py b/aiida_workgraph/widget/src/widget/html_template.py index 28524101..baa8e62f 100644 --- a/aiida_workgraph/widget/src/widget/html_template.py +++ b/aiida_workgraph/widget/src/widget/html_template.py @@ -21,6 +21,7 @@ +