diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py index 5c924196..e1851773 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/task.py @@ -115,8 +115,8 @@ class Task: process_model_display_name: Union[str, None] = None, process_group_identifier: Union[str, None] = None, process_model_identifier: Union[str, None] = None, - form_schema: Union[str, None] = None, - form_ui_schema: Union[str, None] = None, + form_schema: Union[dict, None] = None, + form_ui_schema: Union[dict, None] = None, parent: Optional[str] = None, event_definition: Union[dict[str, Any], None] = None, call_activity_process_identifier: Optional[str] = None, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index 6f272287..79cd8424 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -170,6 +170,25 @@ def task_list_for_my_groups( ) +def _munge_form_ui_schema_based_on_hidden_fields_in_task_data(task: Task) -> None: + if task.form_ui_schema is None: + task.form_ui_schema = {} + + if task.data and "form_ui_hidden_fields" in task.data: + hidden_fields = task.data["form_ui_hidden_fields"] + for hidden_field in hidden_fields: + hidden_field_parts = hidden_field.split(".") + relevant_depth_of_ui_schema = task.form_ui_schema + for ii, hidden_field_part in enumerate(hidden_field_parts): + if hidden_field_part not in relevant_depth_of_ui_schema: + relevant_depth_of_ui_schema[hidden_field_part] = {} + relevant_depth_of_ui_schema = relevant_depth_of_ui_schema[ + hidden_field_part + ] + if len(hidden_field_parts) == ii + 1: + relevant_depth_of_ui_schema["ui:widget"] = "hidden" + + def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response: """Task_show.""" process_instance = _find_process_instance_by_id_or_raise(process_instance_id) @@ -185,20 +204,7 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response process_instance.process_model_identifier, ) - human_task = HumanTaskModel.query.filter_by( - process_instance_id=process_instance_id, task_id=task_id - ).first() - if human_task is None: - raise ( - ApiError( - error_code="no_human_task", - message=( - f"Cannot find a task to complete for task id '{task_id}' and" - f" process instance {process_instance_id}." - ), - status_code=500, - ) - ) + _find_human_task_or_raise(process_instance_id, task_id) form_schema_file_name = "" form_ui_schema_file_name = "" @@ -253,31 +259,16 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response ) ) - form_contents = _prepare_form_data( + form_dict = _prepare_form_data( form_schema_file_name, spiff_task, process_model_with_form, ) - try: - # form_contents is a str - form_dict = json.loads(form_contents) - except Exception as exception: - raise ( - ApiError( - error_code="error_loading_form", - message=( - f"Could not load form schema from: {form_schema_file_name}." - f" Error was: {str(exception)}" - ), - status_code=400, - ) - ) from exception - if task.data: _update_form_schema_with_task_data_as_needed(form_dict, task, spiff_task) - if form_contents: + if form_dict: task.form_schema = form_dict if form_ui_schema_file_name: @@ -289,6 +280,8 @@ def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response if ui_form_contents: task.form_ui_schema = ui_form_contents + _munge_form_ui_schema_based_on_hidden_fields_in_task_data(task) + if task.properties and task.data and "instructionsForEndUser" in task.properties: if task.properties["instructionsForEndUser"]: try: @@ -365,20 +358,11 @@ def task_submit_shared( if terminate_loop and spiff_task.is_looping(): spiff_task.terminate_loop() - human_task = HumanTaskModel.query.filter_by( - process_instance_id=process_instance_id, task_id=task_id, completed=False - ).first() - if human_task is None: - raise ( - ApiError( - error_code="no_human_task", - message=( - f"Cannot find a task to complete for task id '{task_id}' and" - f" process instance {process_instance_id}." - ), - status_code=500, - ) - ) + human_task = _find_human_task_or_raise( + process_instance_id=process_instance_id, + task_id=task_id, + only_tasks_that_can_be_completed=True, + ) with sentry_sdk.start_span(op="task", description="complete_form_task"): processor.lock_process_instance("Web") @@ -525,14 +509,29 @@ def _get_tasks( def _prepare_form_data( form_file: str, spiff_task: SpiffTask, process_model: ProcessModelInfo -) -> str: +) -> dict: """Prepare_form_data.""" if spiff_task.data is None: - return "" + return {} file_contents = SpecFileService.get_data(process_model, form_file).decode("utf-8") try: - return _render_jinja_template(file_contents, spiff_task) + form_contents = _render_jinja_template(file_contents, spiff_task) + try: + # form_contents is a str + hot_dict: dict = json.loads(form_contents) + return hot_dict + except Exception as exception: + raise ( + ApiError( + error_code="error_loading_form", + message=( + f"Could not load form schema from: {form_file}." + f" Error was: {str(exception)}" + ), + status_code=400, + ) + ) from exception except WorkflowTaskException as wfe: wfe.add_note(f"Error in Json Form File '{form_file}'") api_error = ApiError.from_workflow_exception( @@ -670,3 +669,32 @@ def _get_potential_owner_usernames(assigned_user: AliasedClass) -> Any: ).label("potential_owner_usernames") return potential_owner_usernames_from_group_concat_or_similar + + +def _find_human_task_or_raise( + process_instance_id: int, + task_id: str, + only_tasks_that_can_be_completed: bool = False, +) -> HumanTaskModel: + if only_tasks_that_can_be_completed: + human_task_query = HumanTaskModel.query.filter_by( + process_instance_id=process_instance_id, task_id=task_id, completed=False + ) + else: + human_task_query = HumanTaskModel.query.filter_by( + process_instance_id=process_instance_id, task_id=task_id + ) + + human_task: HumanTaskModel = human_task_query.first() + if human_task is None: + raise ( + ApiError( + error_code="no_human_task", + message=( + f"Cannot find a task to complete for task id '{task_id}' and" + f" process instance {process_instance_id}." + ), + status_code=500, + ) + ) + return human_task diff --git a/spiffworkflow-backend/tests/data/dynamic_enum_select_fields/color_question.json b/spiffworkflow-backend/tests/data/dynamic_enum_select_fields/color_question.json index 20ea1c12..1ce7072b 100644 --- a/spiffworkflow-backend/tests/data/dynamic_enum_select_fields/color_question.json +++ b/spiffworkflow-backend/tests/data/dynamic_enum_select_fields/color_question.json @@ -13,6 +13,18 @@ "selectedColor": { "$ref": "#/definitions/Color", "title": "Select color" + }, + "veryImportantFieldButOnlySometimes": { + "title": "Very important field", + "type": "string" + }, + "building": { + "properties": { + "floor": { + "title": "Floor", + "type": "number" + } + } } } } diff --git a/spiffworkflow-backend/tests/data/dynamic_enum_select_fields/dynamic_enums_ask_for_color.bpmn b/spiffworkflow-backend/tests/data/dynamic_enum_select_fields/dynamic_enums_ask_for_color.bpmn index 7d21851b..d4f1aa5d 100644 --- a/spiffworkflow-backend/tests/data/dynamic_enum_select_fields/dynamic_enums_ask_for_color.bpmn +++ b/spiffworkflow-backend/tests/data/dynamic_enum_select_fields/dynamic_enums_ask_for_color.bpmn @@ -13,7 +13,9 @@ Flow_1my9ag5 Flow_0b04rbg - awesome_color_options = [{"value": "blue", "label": "Blue"}, {"value": "green", "label": "Green"}] + awesome_color_options = [{"value": "blue", "label": "Blue"}, {"value": "green", "label": "Green"}] +form_ui_hidden_fields = ["veryImportantFieldButOnlySometimes", "building.floor"] + 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 f52cbc43..c8fd5f6a 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -1687,6 +1687,14 @@ class TestProcessApi(BaseTest): == "Green" ) + # if you set this in task data: + # form_ui_hidden_fields = ["veryImportantFieldButOnlySometimes", "building.floor"] + # you will get this ui schema: + assert response.json["form_ui_schema"] == { + "building": {"floor": {"ui:widget": "hidden"}}, + "veryImportantFieldButOnlySometimes": {"ui:widget": "hidden"}, + } + def test_process_instance_list_with_default_list( self, app: Flask, diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index a20e7b7b..48d8e9bb 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -200,7 +200,7 @@ export default function TaskShow() { }, }; } else if (task.form_ui_schema) { - formUiSchema = JSON.parse(task.form_ui_schema); + formUiSchema = task.form_ui_schema; } if (task.state !== 'READY') { formUiSchema = Object.assign(formUiSchema || {}, {