diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_group.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_group.py
index 14ab3c74..1439b045 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_group.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_group.py
@@ -29,6 +29,7 @@ class ProcessGroup:
default_factory=list[ProcessModelInfo]
)
process_groups: list[ProcessGroup] = field(default_factory=list["ProcessGroup"])
+ parent_groups: list[dict] | None = None
def __post_init__(self) -> None:
"""__post_init__."""
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_model.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_model.py
index 3ab55d07..7aee257f 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_model.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_model.py
@@ -38,6 +38,7 @@ class ProcessModelInfo:
files: list[File] | None = field(default_factory=list[File])
fault_or_suspend_on_exception: str = NotificationType.fault.value
exception_notification_addresses: list[str] = field(default_factory=list)
+ parent_groups: list[dict] | None = None
def __post_init__(self) -> None:
"""__post_init__."""
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
index 1f2e776e..fdc172e9 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py
@@ -233,6 +233,10 @@ def process_group_show(
status_code=400,
)
) from exception
+
+ process_group.parent_groups = ProcessModelService.get_parent_group_array(
+ process_group.id
+ )
return make_response(jsonify(process_group), 200)
@@ -331,8 +335,11 @@ def process_model_show(modified_process_model_identifier: str) -> Any:
process_model.files = files
for file in process_model.files:
file.references = SpecFileService.get_references_for_file(file, process_model)
- process_model_json = ProcessModelInfoSchema().dump(process_model)
- return process_model_json
+
+ process_model.parent_groups = ProcessModelService.get_parent_group_array(
+ process_model.id
+ )
+ return make_response(jsonify(process_model), 200)
def process_model_move(
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py
index aa7da799..ba3039cd 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py
@@ -158,26 +158,6 @@ class ProcessModelService(FileSystemService):
if self.is_model(model_path):
process_model = self.get_process_model_from_relative_path(process_model_id)
return process_model
-
- # group_path, model_id = os.path.split(process_model_id)
- # if group_path is not None:
- # process_group = self.get_process_group(group_path)
- # if process_group is not None:
- # for process_model in process_group.process_models:
- # if process_model_id == process_model.id:
- # return process_model
- # with os.scandir(FileSystemService.root_path()) as process_group_dirs:
- # for item in process_group_dirs:
- # process_group_dir = item
- # if item.is_dir():
- # with os.scandir(item.path) as spec_dirs:
- # for sd in spec_dirs:
- # if sd.name == process_model_id:
- # # Now we have the process_group directory, and spec directory
- # process_group = self.__scan_process_group(
- # process_group_dir
- # )
- # return self.__scan_process_model(sd.path, sd.name, process_group)
raise ProcessEntityNotFoundError("process_model_not_found")
def get_process_models(
@@ -221,6 +201,23 @@ class ProcessModelService(FileSystemService):
return process_models
+ @classmethod
+ def get_parent_group_array(cls, process_identifier: str) -> list[dict]:
+ """Get_parent_group_array."""
+ full_group_id_path = None
+ parent_group_array = []
+ for process_group_id_segment in process_identifier.split("/")[0:-1]:
+ if full_group_id_path is None:
+ full_group_id_path = process_group_id_segment
+ else:
+ full_group_id_path = f"{full_group_id_path}/{process_group_id_segment}" # type: ignore
+ parent_group = ProcessModelService().get_process_group(full_group_id_path)
+ if parent_group:
+ parent_group_array.append(
+ {"id": parent_group.id, "display_name": parent_group.display_name}
+ )
+ return parent_group_array
+
def get_process_groups(
self, process_group_id: Optional[str] = None
) -> list[ProcessGroup]:
@@ -229,7 +226,9 @@ class ProcessModelService(FileSystemService):
process_groups.sort()
return process_groups
- def get_process_group(self, process_group_id: str) -> ProcessGroup:
+ def get_process_group(
+ self, process_group_id: str, find_direct_nested_items: bool = True
+ ) -> ProcessGroup:
"""Look for a given process_group, and return it."""
if os.path.exists(FileSystemService.root_path()):
process_group_path = os.path.abspath(
@@ -239,20 +238,10 @@ class ProcessModelService(FileSystemService):
)
)
if self.is_group(process_group_path):
- return self.__scan_process_group(process_group_path)
- # nested_groups = []
- # process_group_dir = os.scandir(process_group_path)
- # for item in process_group_dir:
- # if self.is_group(item.path):
- # nested_group = self.get_process_group(os.path.join(process_group_path, item.path))
- # nested_groups.append(nested_group)
- # elif self.is_model(item.path):
- # print("get_process_group: ")
- # return self.__scan_process_group(process_group_path)
- # with os.scandir(FileSystemService.root_path()) as directory_items:
- # for item in directory_items:
- # if item.is_dir() and item.name == process_group_id:
- # return self.__scan_process_group(item)
+ return self.find_or_create_process_group(
+ process_group_path,
+ find_direct_nested_items=find_direct_nested_items,
+ )
raise ProcessEntityNotFoundError(
"process_group_not_found", f"Process Group Id: {process_group_id}"
@@ -348,11 +337,13 @@ class ProcessModelService(FileSystemService):
for item in directory_items:
# if item.is_dir() and not item.name[0] == ".":
if item.is_dir() and self.is_group(item): # type: ignore
- scanned_process_group = self.__scan_process_group(item.path)
+ scanned_process_group = self.find_or_create_process_group(item.path)
process_groups.append(scanned_process_group)
return process_groups
- def __scan_process_group(self, dir_path: str) -> ProcessGroup:
+ def find_or_create_process_group(
+ self, dir_path: str, find_direct_nested_items: bool = True
+ ) -> ProcessGroup:
"""Reads the process_group.json file, and any nested directories."""
cat_path = os.path.join(dir_path, self.PROCESS_GROUP_JSON_FILE)
if os.path.exists(cat_path):
@@ -378,27 +369,29 @@ class ProcessModelService(FileSystemService):
self.write_json_file(cat_path, self.GROUP_SCHEMA.dump(process_group))
# we don't store `id` in the json files, so we add it in here
process_group.id = process_group_id
- with os.scandir(dir_path) as nested_items:
- process_group.process_models = []
- process_group.process_groups = []
- for nested_item in nested_items:
- if nested_item.is_dir():
- # TODO: check whether this is a group or model
- if self.is_group(nested_item.path):
- # This is a nested group
- process_group.process_groups.append(
- self.__scan_process_group(nested_item.path)
- )
- elif self.is_model(nested_item.path):
- process_group.process_models.append(
- self.__scan_process_model(
- nested_item.path,
- nested_item.name,
- process_group=process_group,
+
+ if find_direct_nested_items:
+ with os.scandir(dir_path) as nested_items:
+ process_group.process_models = []
+ process_group.process_groups = []
+ for nested_item in nested_items:
+ if nested_item.is_dir():
+ # TODO: check whether this is a group or model
+ if self.is_group(nested_item.path):
+ # This is a nested group
+ process_group.process_groups.append(
+ self.find_or_create_process_group(nested_item.path)
)
- )
- process_group.process_models.sort()
- # process_group.process_groups.sort()
+ elif self.is_model(nested_item.path):
+ process_group.process_models.append(
+ self.__scan_process_model(
+ nested_item.path,
+ nested_item.name,
+ process_group=process_group,
+ )
+ )
+ process_group.process_models.sort()
+ # process_group.process_groups.sort()
return process_group
def __scan_process_model(
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
index eba7399e..6e9858b3 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
@@ -979,6 +979,43 @@ class TestProcessApi(BaseTest):
assert response.json is not None
assert response.json["id"] == process_group_id
assert response.json["process_models"][0]["id"] == process_model_identifier
+ assert response.json["parent_groups"] == []
+
+ def test_get_process_group_show_when_nested(
+ self,
+ app: Flask,
+ client: FlaskClient,
+ with_db_and_bpmn_file_cleanup: None,
+ with_super_admin_user: UserModel,
+ ) -> None:
+ """Test_get_process_group_show_when_nested."""
+ self.create_group_and_model_with_bpmn(
+ client=client,
+ user=with_super_admin_user,
+ process_group_id="test_group_one",
+ process_model_id="simple_form",
+ bpmn_file_location="simple_form",
+ )
+
+ self.create_group_and_model_with_bpmn(
+ client=client,
+ user=with_super_admin_user,
+ process_group_id="test_group_one/test_group_two",
+ process_model_id="call_activity_nested",
+ bpmn_file_location="call_activity_nested",
+ )
+
+ response = client.get(
+ "/v1.0/process-groups/test_group_one:test_group_two",
+ headers=self.logged_in_headers(with_super_admin_user),
+ )
+
+ assert response.status_code == 200
+ assert response.json is not None
+ assert response.json["id"] == "test_group_one/test_group_two"
+ assert response.json["parent_groups"] == [
+ {"display_name": "test_group_one", "id": "test_group_one"}
+ ]
def test_get_process_model_when_found(
self,
@@ -997,11 +1034,15 @@ class TestProcessApi(BaseTest):
f"/v1.0/process-models/{modified_process_model_identifier}",
headers=self.logged_in_headers(with_super_admin_user),
)
+
assert response.status_code == 200
assert response.json is not None
assert response.json["id"] == process_model_identifier
assert len(response.json["files"]) == 1
assert response.json["files"][0]["name"] == "random_fact.bpmn"
+ assert response.json["parent_groups"] == [
+ {"display_name": "test_group", "id": "test_group"}
+ ]
def test_get_process_model_when_not_found(
self,
diff --git a/spiffworkflow-frontend/src/components/ProcessBreadcrumb.test.tsx b/spiffworkflow-frontend/src/components/ProcessBreadcrumb.test.tsx
index 49400e8e..9be27432 100644
--- a/spiffworkflow-frontend/src/components/ProcessBreadcrumb.test.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessBreadcrumb.test.tsx
@@ -3,13 +3,13 @@ import { BrowserRouter } from 'react-router-dom';
import ProcessBreadcrumb from './ProcessBreadcrumb';
test('renders home link', () => {
- render(
-
No completed instances
} />