diff --git a/spiffworkflow-backend/keycloak/bin/add_test_users_to_keycloak b/spiffworkflow-backend/keycloak/bin/add_test_users_to_keycloak index 4196add0..c53fe438 100755 --- a/spiffworkflow-backend/keycloak/bin/add_test_users_to_keycloak +++ b/spiffworkflow-backend/keycloak/bin/add_test_users_to_keycloak @@ -89,6 +89,12 @@ while read -r input_line; do echo "Importing: $input_line" user_email=$(awk -F ',' '{print $1}' <<<"$input_line") username=$(awk -F '@' '{print $1}' <<<"$user_email") + + if [[ "$username" == "$ADMIN_USERNAME" || "$user_email" == "$ADMIN_USERNAME" ]]; then + >&2 echo "ERROR: The user used as the admin user matches a user in the current import list. This should not happen. Comment out that user from the list or use a different admin user: ${ADMIN_USERNAME}" + exit 1 + fi + user_attribute_one=$(awk -F ',' '{print $2}' <<<"$input_line") http_code=$(add_user "$user_email" "$username" "$user_attribute_one") diff --git a/spiffworkflow-backend/keycloak/realm_exports/spiffworkflow-realm.json b/spiffworkflow-backend/keycloak/realm_exports/spiffworkflow-realm.json index c7781b81..27239bca 100644 --- a/spiffworkflow-backend/keycloak/realm_exports/spiffworkflow-realm.json +++ b/spiffworkflow-backend/keycloak/realm_exports/spiffworkflow-realm.json @@ -2352,26 +2352,6 @@ "realmRoles" : [ "default-roles-spiffworkflow" ], "notBefore" : 0, "groups" : [ ] - }, { - "id" : "058b60f8-799e-48b0-a2b7-2e65e7a35724", - "createdTimestamp" : 1675718484672, - "username" : "mike", - "enabled" : true, - "totp" : false, - "emailVerified" : false, - "email" : "mike@sartography.com", - "credentials" : [ { - "id" : "669f5421-843d-411d-9f24-1be41e545e52", - "type" : "password", - "createdDate" : 1675718484715, - "secretData" : "{\"value\":\"YILRiRdrsy8CA716ZQazpQOf7mpiXGaYnR26ra3pSjmHkZS9tsePTRwU2OIGPwbN1LKJcIzrpfEP7cVW2Lm17w==\",\"salt\":\"7mfD1X7Hns/5pPgHb9uZ1Q==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" - } ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "default-roles-spiffworkflow" ], - "notBefore" : 0, - "groups" : [ ] }, { "id" : "97843876-e1b6-469a-bab4-f9bce4aa5936", "createdTimestamp" : 1678461819014, @@ -2395,26 +2375,6 @@ "realmRoles" : [ "default-roles-spiffworkflow" ], "notBefore" : 0, "groups" : [ ] - }, { - "id" : "9d23748e-23a7-4c48-956c-64da75871277", - "createdTimestamp" : 1675718484779, - "username" : "natalia", - "enabled" : true, - "totp" : false, - "emailVerified" : false, - "email" : "natalia@sartography.com", - "credentials" : [ { - "id" : "476024e5-62e4-48b6-afbb-cc2834fae4c7", - "type" : "password", - "createdDate" : 1675718484823, - "secretData" : "{\"value\":\"FfrpgES+XI2w4NRe1aBmolPFcERbEUDXZcFtUWucrbhBspQLYNaN2VLmeDRV0VcT47Bn8dqjU11ct64WDtffWA==\",\"salt\":\"7rZd3fqY54i1eoNyXCcZ1w==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" - } ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "default-roles-spiffworkflow" ], - "notBefore" : 0, - "groups" : [ ] }, { "id" : "7f34beba-e1e1-458a-8d23-eb07d6e3800c", "createdTimestamp" : 1678126023154, diff --git a/spiffworkflow-backend/keycloak/test_user_lists/admin b/spiffworkflow-backend/keycloak/test_user_lists/admin index aa676cd9..a764901c 100644 --- a/spiffworkflow-backend/keycloak/test_user_lists/admin +++ b/spiffworkflow-backend/keycloak/test_user_lists/admin @@ -1,2 +1,4 @@ email,spiffworkflow-employeeid admin@spiffworkflow.org +jason@sartography.com +kevin@sartography.com diff --git a/spiffworkflow-backend/keycloak/test_user_lists/sartography b/spiffworkflow-backend/keycloak/test_user_lists/sartography index 1e280bae..17c5e688 100644 --- a/spiffworkflow-backend/keycloak/test_user_lists/sartography +++ b/spiffworkflow-backend/keycloak/test_user_lists/sartography @@ -1,15 +1,10 @@ email,spiffworkflow-employeeid -admin@spiffworkflow.org alex@sartography.com,111 dan@sartography.com,115 daniel@sartography.com elizabeth@sartography.com j@sartography.com -jason@sartography.com jon@sartography.com kb@sartography.com -kevin@sartography.com madhurya@sartography.com,160 madhurya@ymail.com,161 -mike@sartography.com -natalia@sartography.com diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock index a8d70db3..b96b8d78 100644 --- a/spiffworkflow-backend/poetry.lock +++ b/spiffworkflow-backend/poetry.lock @@ -1895,7 +1895,7 @@ lxml = "*" type = "git" url = "https://github.com/sartography/SpiffWorkflow" reference = "main" -resolved_reference = "f162aac43af3af18d1a55186aeccea154fb8b05d" +resolved_reference = "3c3345c85dd7f3b7112ad04aaa6487abbd2e9414" [[package]] name = "SQLAlchemy" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index a4d8156c..06f482bc 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -1251,9 +1251,16 @@ paths: $ref: "#/components/schemas/OkTrue" /process-instances/reports/columns: + parameters: + - name: process_model_identifier + in: query + required: false + description: The process model identifier to filter by + schema: + type: string get: operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_column_list - summary: Returns all available columns for a process instance report. + summary: Returns all available columns for a process instance report, including custom metadata tags: - Process Instances responses: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py index 758a48d9..aa6b3cc8 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py @@ -390,15 +390,21 @@ def process_instance_list( return make_response(jsonify(response_json), 200) -def process_instance_report_column_list() -> flask.wrappers.Response: +def process_instance_report_column_list(process_model_identifier: Optional[str] = None) -> flask.wrappers.Response: """Process_instance_report_column_list.""" table_columns = ProcessInstanceReportService.builtin_column_options() - columns_for_metadata = ( + columns_for_metadata_query = ( db.session.query(ProcessInstanceMetadataModel.key) .order_by(ProcessInstanceMetadataModel.key) .distinct() # type: ignore - .all() ) + if process_model_identifier: + columns_for_metadata_query = columns_for_metadata_query.join(ProcessInstanceModel) + columns_for_metadata_query = columns_for_metadata_query.filter( + ProcessInstanceModel.process_model_identifier == process_model_identifier + ) + + columns_for_metadata = columns_for_metadata_query.all() columns_for_metadata_strings = [ {"Header": i[0], "accessor": i[0], "filterable": True} for i in columns_for_metadata ] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index 50a4402a..3d0eac40 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -175,7 +175,7 @@ def task_list_for_my_groups( def task_data_show( modified_process_model_identifier: str, process_instance_id: int, - task_guid: int = 0, + task_guid: str, ) -> flask.wrappers.Response: task_model = TaskModel.query.filter_by(guid=task_guid, process_instance_id=process_instance_id).first() if task_model is None: @@ -636,7 +636,7 @@ def _get_spiff_task_from_process_instance( if processor is None: processor = ProcessInstanceProcessor(process_instance) task_uuid = uuid.UUID(task_guid) - spiff_task = processor.bpmn_process_instance.get_task(task_uuid) + spiff_task = processor.bpmn_process_instance.get_task_from_id(task_uuid) if spiff_task is None: raise ( diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 99f60a2c..54ee33b2 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -1175,7 +1175,7 @@ class ProcessInstanceProcessor: """Mark the task complete optionally executing it.""" spiff_tasks_updated = {} start_in_seconds = time.time() - spiff_task = self.bpmn_process_instance.get_task(UUID(task_id)) + spiff_task = self.bpmn_process_instance.get_task_from_id(UUID(task_id)) event_type = ProcessInstanceEventType.task_skipped.value if execute: current_app.logger.info( diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index ed2ea918..37f77ac1 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -278,6 +278,9 @@ class ProcessInstanceService: for list_index, list_value in enumerate(value): if isinstance(list_value, str): yield (identifier, list_value, list_index) + if isinstance(list_value, dict) and len(list_value) == 1: + for v in list_value.values(): + yield (identifier, v, list_index) @classmethod def file_data_models_for_data( @@ -308,7 +311,11 @@ class ProcessInstanceService: if model.list_index is None: data[model.identifier] = digest_reference else: - data[model.identifier][model.list_index] = digest_reference + old_value = data[model.identifier][model.list_index] + new_value: Any = digest_reference + if isinstance(old_value, dict) and len(old_value) == 1: + new_value = {k: digest_reference for k in old_value.keys()} + data[model.identifier][model.list_index] = new_value @classmethod def save_file_data_and_replace_with_digest_references( diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py index 4b86eefc..4654ac2a 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py @@ -255,7 +255,7 @@ class TaskService: task_data_dict = task_properties.pop("data") state_int = task_properties["state"] - spiff_task = spiff_workflow.get_task(UUID(task_id)) + spiff_task = spiff_workflow.get_task_from_id(UUID(task_id)) task_model = TaskModel.query.filter_by(guid=task_id).first() if task_model is None: diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py index 5f483fdd..6b4d0143 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py @@ -78,7 +78,7 @@ class BaseTest: if bpmn_file_location is None: bpmn_file_location = process_model_id - self.create_process_group(client, user, process_group_description, process_group_display_name) + self.create_process_group_with_api(client, user, process_group_description, process_group_display_name) self.create_process_model_with_api( client, @@ -97,6 +97,15 @@ class BaseTest: return process_model_identifier def create_process_group( + self, + process_group_id: str, + display_name: str = "", + ) -> ProcessGroup: + """Create_process_group.""" + process_group = ProcessGroup(id=process_group_id, display_name=display_name, display_order=0, admin=False) + return ProcessModelService.add_process_group(process_group) + + def create_process_group_with_api( self, client: FlaskClient, user: Any, @@ -353,3 +362,20 @@ class BaseTest: def un_modify_modified_process_identifier_for_path_param(self, modified_identifier: str) -> str: """Un_modify_modified_process_model_id.""" return modified_identifier.replace(":", "/") + + def create_process_model_with_metadata(self) -> ProcessModelInfo: + self.create_process_group("test_group", "test_group") + process_model = load_test_spec( + "test_group/hello_world", + process_model_source_directory="nested-task-data-structure", + ) + ProcessModelService.update_process_model( + process_model, + { + "metadata_extraction_paths": [ + {"key": "awesome_var", "path": "outer.inner"}, + {"key": "invoice_number", "path": "invoice_number"}, + ] + }, + ) + return process_model diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_logging_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_logging_service.py index f79a3295..7890e156 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_logging_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_logging_service.py @@ -25,7 +25,7 @@ class TestLoggingService(BaseTest): with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel, ) -> None: - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") initiator_user = self.find_or_create_user("initiator_user") assert initiator_user.principal is not None AuthorizationService.import_permissions_from_yaml_file() @@ -85,7 +85,7 @@ class TestLoggingService(BaseTest): with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel, ) -> None: - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") initiator_user = self.find_or_create_user("initiator_user") assert initiator_user.principal is not None AuthorizationService.import_permissions_from_yaml_file() @@ -114,7 +114,7 @@ class TestLoggingService(BaseTest): process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first() processor = ProcessInstanceProcessor(process_instance) human_task_one = process_instance.active_human_tasks[0] - spiff_manual_task = processor.bpmn_process_instance.get_task(UUID(human_task_one.task_id)) + spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id)) ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one) headers = self.logged_in_headers(with_super_admin_user) 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 b0f355c8..89fda503 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -118,7 +118,7 @@ class TestProcessApi(BaseTest): process_group_id = "test_process_group" process_group_display_name = "Test Process Group" # creates the group directory, and the json file - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_display_name) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_display_name) process_model_id = "sample" model_display_name = "Sample" @@ -169,7 +169,7 @@ class TestProcessApi(BaseTest): process_group_description = "Test Process Group" process_model_id = "sample" process_model_identifier = f"{process_group_id}/{process_model_id}" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_description) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_description) text = "Create a Bug Tracker process model " text += "with a Bug Details form that collects summary, description, and priority" @@ -237,7 +237,9 @@ class TestProcessApi(BaseTest): process_model_identifier = f"{process_group_id}/{process_model_id}" initial_primary_process_id = "sample" terminal_primary_process_id = "new_process_id" - self.create_process_group(client=client, user=with_super_admin_user, process_group_id=process_group_id) + self.create_process_group_with_api( + client=client, user=with_super_admin_user, process_group_id=process_group_id + ) bpmn_file_name = f"{process_model_id}.bpmn" bpmn_file_source_directory = process_model_id @@ -281,7 +283,7 @@ class TestProcessApi(BaseTest): process_group_description = "Test Process Group" process_model_id = "sample" process_model_identifier = f"{process_group_id}/{process_model_id}" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_description) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_description) self.create_process_model_with_api( client, process_model_id=process_model_identifier, @@ -317,7 +319,7 @@ class TestProcessApi(BaseTest): bpmn_file_location = "sample" process_model_identifier = f"{test_process_group_id}/{test_process_model_id}" modified_process_model_identifier = process_model_identifier.replace("/", ":") - self.create_process_group(client, with_super_admin_user, test_process_group_id) + self.create_process_group_with_api(client, with_super_admin_user, test_process_group_id) self.create_process_model_with_api(client, process_model_identifier, user=with_super_admin_user) bpmn_file_data_bytes = self.get_test_data_file_contents(bpmn_file_name, bpmn_file_location) self.create_spec_file( @@ -362,7 +364,7 @@ class TestProcessApi(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_process_model_update.""" - self.create_process_group(client, with_super_admin_user, "test_process_group", "Test Process Group") + self.create_process_group_with_api(client, with_super_admin_user, "test_process_group", "Test Process Group") process_model_identifier = "test_process_group/make_cookies" self.create_process_model_with_api( client, @@ -403,7 +405,7 @@ class TestProcessApi(BaseTest): ) -> None: """Test_process_model_list_all.""" group_id = "test_group/test_sub_group" - self.create_process_group(client, with_super_admin_user, group_id) + self.create_process_group_with_api(client, with_super_admin_user, group_id) # add 5 models to the group for i in range(5): @@ -439,7 +441,7 @@ class TestProcessApi(BaseTest): """Test_process_model_list.""" # create a group group_id = "test_group" - self.create_process_group(client, with_super_admin_user, group_id) + self.create_process_group_with_api(client, with_super_admin_user, group_id) # add 5 models to the group for i in range(5): @@ -603,7 +605,7 @@ class TestProcessApi(BaseTest): process_group_id = "test" process_group_display_name = "My Process Group" - self.create_process_group( + self.create_process_group_with_api( client, with_super_admin_user, process_group_id, @@ -632,7 +634,7 @@ class TestProcessApi(BaseTest): group_id = "test_process_group" group_display_name = "Test Group" - self.create_process_group(client, with_super_admin_user, group_id, display_name=group_display_name) + self.create_process_group_with_api(client, with_super_admin_user, group_id, display_name=group_display_name) process_group = ProcessModelService.get_process_group(group_id) assert process_group.display_name == group_display_name @@ -662,7 +664,9 @@ class TestProcessApi(BaseTest): for i in range(5): group_id = f"test_process_group_{i}" group_display_name = f"Test Group {i}" - self.create_process_group(client, with_super_admin_user, group_id, display_name=group_display_name) + self.create_process_group_with_api( + client, with_super_admin_user, group_id, display_name=group_display_name + ) # get all groups response = client.get( @@ -787,7 +791,7 @@ class TestProcessApi(BaseTest): process_group_description = "Test Group" process_model_id = "random_fact" process_model_identifier = f"{process_group_id}/{process_model_id}" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_description) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_description) self.create_process_model_with_api( client, process_model_id=process_model_identifier, @@ -1091,7 +1095,7 @@ class TestProcessApi(BaseTest): ) -> None: """Test_get_process_model_when_not_found.""" process_model_dir_name = "THIS_NO_EXISTS" - group_id = self.create_process_group(client, with_super_admin_user, "my_group") + group_id = self.create_process_group_with_api(client, with_super_admin_user, "my_group") bad_process_model_id = f"{group_id}/{process_model_dir_name}" modified_bad_process_model_id = bad_process_model_id.replace("/", ":") response = client.get( @@ -2714,7 +2718,7 @@ class TestProcessApi(BaseTest): groups = ["group_a", "group_b", "group_b/group_bb"] # setup initial groups for group in groups: - self.create_process_group(client, with_super_admin_user, group, display_name=group) + self.create_process_group_with_api(client, with_super_admin_user, group, display_name=group) # make sure initial groups exist for group in groups: persisted = ProcessModelService.get_process_group(group) @@ -2783,7 +2787,7 @@ class TestProcessApi(BaseTest): sub_group_id = "sub_group" original_location = "group_a" original_sub_path = f"{original_location}/{sub_group_id}" - self.create_process_group(client, with_super_admin_user, original_sub_path, display_name=sub_group_id) + self.create_process_group_with_api(client, with_super_admin_user, original_sub_path, display_name=sub_group_id) # make sure original subgroup exists persisted = ProcessModelService.get_process_group(original_sub_path) assert persisted is not None @@ -2835,7 +2839,7 @@ class TestProcessApi(BaseTest): # ) # # process_group_id = "test_group" - # self.create_process_group( + # self.create_process_group_with_api( # client, with_super_admin_user, process_group_id, process_group_id # ) # @@ -3077,6 +3081,18 @@ class TestProcessApi(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_get_process_instance_list_with_report_metadata.""" + process_model = self.create_process_model_with_metadata() + process_instance = self.create_process_instance_from_process_model( + process_model=process_model, user=with_super_admin_user + ) + + processor = ProcessInstanceProcessor(process_instance) + processor.do_engine_steps(save=True) + process_instance_metadata = ProcessInstanceMetadataModel.query.filter_by( + process_instance_id=process_instance.id + ).all() + assert len(process_instance_metadata) == 2 + process_model = load_test_spec( process_model_id="save_process_instance_metadata/save_process_instance_metadata", bpmn_file_name="save_process_instance_metadata.bpmn", @@ -3115,11 +3131,35 @@ class TestProcessApi(BaseTest): "filterable": False, }, {"Header": "Status", "accessor": "status", "filterable": False}, + {"Header": "awesome_var", "accessor": "awesome_var", "filterable": True}, + {"Header": "invoice_number", "accessor": "invoice_number", "filterable": True}, {"Header": "key1", "accessor": "key1", "filterable": True}, {"Header": "key2", "accessor": "key2", "filterable": True}, {"Header": "key3", "accessor": "key3", "filterable": True}, ] + # pluck accessor from each dict in list + accessors = [column["accessor"] for column in response.json] + stock_columns = [ + "id", + "process_model_display_name", + "start_in_seconds", + "end_in_seconds", + "process_initiator_username", + "status", + ] + assert accessors == stock_columns + ["awesome_var", "invoice_number", "key1", "key2", "key3"] + + # expected columns are fewer if we filter by process_model_identifier + response = client.get( + "/v1.0/process-instances/reports/columns?process_model_identifier=save_process_instance_metadata/save_process_instance_metadata", + headers=self.logged_in_headers(with_super_admin_user), + ) + assert response.json is not None + assert response.status_code == 200 + accessors = [column["accessor"] for column in response.json] + assert accessors == stock_columns + ["key1", "key2", "key3"] + def test_process_instance_list_can_order_by_metadata( self, app: Flask, @@ -3128,7 +3168,7 @@ class TestProcessApi(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_process_instance_list_can_order_by_metadata.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( "test_group/hello_world", process_model_source_directory="nested-task-data-structure", diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_secret_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_secret_service.py index 3e19607d..e12a1dd5 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_secret_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_secret_service.py @@ -34,7 +34,7 @@ class SecretServiceTestHelpers(BaseTest): def add_test_process(self, client: FlaskClient, user: UserModel) -> ProcessModelInfo: """Add_test_process.""" - self.create_process_group( + self.create_process_group_with_api( client, user, self.test_process_group_id, diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_group_members.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_group_members.py index 3a128cff..685788c3 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_group_members.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_group_members.py @@ -38,7 +38,7 @@ class TestGetGroupMembers(BaseTest): UserService.add_user_to_group(testuser2, group_a) UserService.add_user_to_group(testuser3, group_b) - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( process_model_id="test_group/get_group_members", bpmn_file_name="get_group_members.bpmn", diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_last_user_completing_task.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_last_user_completing_task.py index 5f0e40d3..fcd8b641 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_last_user_completing_task.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_last_user_completing_task.py @@ -23,7 +23,7 @@ class TestGetLastUserCompletingTask(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_sets_permission_correctly_on_human_task.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") initiator_user = self.find_or_create_user("initiator_user") assert initiator_user.principal is not None AuthorizationService.import_permissions_from_yaml_file() diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_localtime.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_localtime.py index 31d2aa69..9595c948 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_localtime.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_localtime.py @@ -54,7 +54,7 @@ class TestGetLocaltime(BaseTest): target_uri="/v1.0/process-groups", permission_names=["read", "create"], ) - self.create_process_group(client=client, user=initiator_user, process_group_id="test_group") + self.create_process_group_with_api(client=client, user=initiator_user, process_group_id="test_group") process_model = load_test_spec( process_model_id="test_group/get_localtime", bpmn_file_name="get_localtime.bpmn", diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_process_initiator_user.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_process_initiator_user.py index 84ac7c27..60a93f9a 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_process_initiator_user.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_process_initiator_user.py @@ -23,7 +23,7 @@ class TestGetProcessInitiatorUser(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_sets_permission_correctly_on_human_task.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") initiator_user = self.find_or_create_user("initiator_user") assert initiator_user.principal is not None AuthorizationService.import_permissions_from_yaml_file() diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py index d0202a64..bf64b21d 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py @@ -24,7 +24,7 @@ class TestSaveProcessInstanceMetadata(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_save_process_instance_metadata.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( process_model_id="save_process_instance_metadata/save_process_instance_metadata", bpmn_file_name="save_process_instance_metadata.bpmn", diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_message_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_message_service.py index 2d2f7baa..403c2323 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_message_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_message_service.py @@ -153,7 +153,7 @@ class TestMessageService(BaseTest): group_name: str = "test_group", ) -> None: process_group_id = group_name - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id) process_model = load_test_spec( "test_group/message", @@ -222,7 +222,7 @@ class TestMessageService(BaseTest): ) -> None: """Test_can_send_message_to_multiple_process_models.""" process_group_id = "test_group_multi" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id) process_model_sender = load_test_spec( "test_group/message_sender", diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_permissions.py index b81164c1..f229bdf7 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_permissions.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_permissions.py @@ -33,7 +33,7 @@ class TestPermissions(BaseTest): ) -> None: """Test_user_can_be_given_permission_to_administer_process_group.""" process_group_id = "group-a" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id) load_test_spec( "group-a/timers_intermediate_catch_event", bpmn_file_name="timers_intermediate_catch_event.bpmn", diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py index 9ccda1cb..4cacfe80 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py @@ -72,7 +72,7 @@ class TestProcessInstanceProcessor(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_sets_permission_correctly_on_human_task.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") initiator_user = self.find_or_create_user("initiator_user") finance_user = self.find_or_create_user("testuser2") assert initiator_user.principal is not None @@ -140,7 +140,7 @@ class TestProcessInstanceProcessor(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_sets_permission_correctly_on_human_task_when_using_dict.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") initiator_user = self.find_or_create_user("initiator_user") finance_user_three = self.find_or_create_user("testuser3") finance_user_four = self.find_or_create_user("testuser4") @@ -264,7 +264,7 @@ class TestProcessInstanceProcessor(BaseTest): # with_db_and_bpmn_file_cleanup: None, # with_super_admin_user: UserModel, # ) -> None: - # self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + # self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") # initiator_user = self.find_or_create_user("initiator_user") # finance_user_three = self.find_or_create_user("testuser3") # assert initiator_user.principal is not None @@ -305,10 +305,10 @@ class TestProcessInstanceProcessor(BaseTest): # process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first() # processor = ProcessInstanceProcessor(process_instance) # human_task_one = process_instance.active_human_tasks[0] - # spiff_manual_task = processor.bpmn_process_instance.get_task(UUID(human_task_one.task_id)) + # spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id)) # ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one) # human_task_one = process_instance.active_human_tasks[0] - # spiff_manual_task = processor.bpmn_process_instance.get_task(UUID(human_task_one.task_id)) + # spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id)) # ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one) def test_properly_saves_tasks_when_running( @@ -318,7 +318,7 @@ class TestProcessInstanceProcessor(BaseTest): with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel, ) -> None: - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") initiator_user = self.find_or_create_user("initiator_user") finance_user_three = self.find_or_create_user("testuser3") assert initiator_user.principal is not None @@ -356,7 +356,7 @@ class TestProcessInstanceProcessor(BaseTest): process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first() processor = ProcessInstanceProcessor(process_instance) human_task_one = process_instance.active_human_tasks[0] - spiff_manual_task = processor.bpmn_process_instance.get_task(UUID(human_task_one.task_id)) + spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id)) ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one) # recreate variables to ensure all bpmn json was recreated from scratch from the db @@ -472,7 +472,7 @@ class TestProcessInstanceProcessor(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_does_not_recreate_human_tasks_on_multiple_saves.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") initiator_user = self.find_or_create_user("initiator_user") finance_user_three = self.find_or_create_user("testuser3") assert initiator_user.principal is not None diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_service.py index 436810cc..0c27a538 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_service.py @@ -89,7 +89,7 @@ class TestProcessInstanceService(BaseTest): self._check_sample_file_data_model("uploaded_files", 0, models[0]) self._check_sample_file_data_model("uploaded_files", 1, models[1]) - def test_can_create_file_data_models_for_fix_of_file_data_and_non_file_data_values( + def test_can_create_file_data_models_for_mix_of_file_data_and_non_file_data_values( self, app: Flask, with_db_and_bpmn_file_cleanup: None, @@ -122,6 +122,8 @@ class TestProcessInstanceService(BaseTest): ) -> None: data = { "not_a_file": "just a value", + "also_no_files": ["not a file", "also not a file"], + "still_no_files": [{"key": "value"}], } models = ProcessInstanceService.file_data_models_for_data(data, 111) @@ -189,3 +191,25 @@ class TestProcessInstanceService(BaseTest): ], "not_a_file3": "just a value3", } + + def test_can_create_file_data_models_for_mulitple_single_file_data_values( + self, + app: Flask, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + data = { + "File": [ + { + "supporting_files": self.SAMPLE_FILE_DATA, + }, + { + "supporting_files": self.SAMPLE_FILE_DATA, + }, + ], + } + models = ProcessInstanceService.file_data_models_for_data(data, 111) + + assert len(models) == 2 + self._check_sample_file_data_model("File", 0, models[0]) + self._check_sample_file_data_model("File", 1, models[1]) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model.py index 4d8e1b5b..22f92111 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model.py @@ -14,7 +14,6 @@ from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.services.process_instance_processor import ( ProcessInstanceProcessor, ) -from spiffworkflow_backend.services.process_model_service import ProcessModelService class TestProcessModel(BaseTest): @@ -33,7 +32,7 @@ class TestProcessModel(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_run_process_model_with_call_activities.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( "test_group/call_activity_test", # bpmn_file_name="call_activity_test.bpmn", @@ -53,7 +52,7 @@ class TestProcessModel(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_run_process_model_with_call_activities.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( "test_group/call_activity_nested", process_model_source_directory="call_activity_nested", @@ -84,7 +83,7 @@ class TestProcessModel(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_run_process_model_with_call_activities.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( "test_group/call_activity_nested", process_model_source_directory="call_activity_nested", @@ -120,20 +119,7 @@ class TestProcessModel(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_run_process_model_with_call_activities.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") - process_model = load_test_spec( - "test_group/hello_world", - process_model_source_directory="nested-task-data-structure", - ) - ProcessModelService.update_process_model( - process_model, - { - "metadata_extraction_paths": [ - {"key": "awesome_var", "path": "outer.inner"}, - {"key": "invoice_number", "path": "invoice_number"}, - ] - }, - ) + process_model = self.create_process_model_with_metadata() process_instance = self.create_process_instance_from_process_model(process_model) processor = ProcessInstanceProcessor(process_instance) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model_service.py index 79d52888..0ff8bf46 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_model_service.py @@ -19,7 +19,7 @@ class TestProcessModelService(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_update_specified_attributes.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( "test_group/hello_world", bpmn_file_name="hello_world.bpmn", diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_restricted_script_engine.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_restricted_script_engine.py index e0b1535d..330d115f 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_restricted_script_engine.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_restricted_script_engine.py @@ -23,7 +23,7 @@ class TestOpenFile(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_form_data_conversion_to_dot_dict.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( "test_group/dangerous", bpmn_file_name="read_etc_passwd.bpmn", @@ -50,7 +50,7 @@ class TestImportModule(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_form_data_conversion_to_dot_dict.""" - self.create_process_group(client, with_super_admin_user, "test_group", "test_group") + self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") process_model = load_test_spec( "test_group/dangerous", bpmn_file_name="read_env.bpmn", diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_script_unit_test_runner.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_script_unit_test_runner.py index 0fc3ee66..f5eef2e8 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_script_unit_test_runner.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_script_unit_test_runner.py @@ -26,7 +26,7 @@ class TestScriptUnitTestRunner(BaseTest): app.config["THREAD_LOCAL_DATA"].process_instance_id = None process_group_id = "test_logging_spiff_logger" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id) process_model_id = "simple_script" process_model_identifier = f"{process_group_id}/{process_model_id}" load_test_spec( @@ -62,7 +62,7 @@ class TestScriptUnitTestRunner(BaseTest): app.config["THREAD_LOCAL_DATA"].process_instance_id = None process_group_id = "test_logging_spiff_logger" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id) process_model_id = "simple_script" process_model_identifier = f"{process_group_id}/{process_model_id}" @@ -99,7 +99,7 @@ class TestScriptUnitTestRunner(BaseTest): app.config["THREAD_LOCAL_DATA"].process_instance_id = None process_group_id = "script_with_unit_tests" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id) process_model_id = "script_with_unit_tests" process_model_identifier = f"{process_group_id}/{process_model_id}" @@ -132,7 +132,7 @@ class TestScriptUnitTestRunner(BaseTest): app.config["THREAD_LOCAL_DATA"].process_instance_id = None process_group_id = "script_with_unit_tests" - self.create_process_group(client, with_super_admin_user, process_group_id, process_group_id) + self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id) process_model_id = "script_with_unit_tests" process_model_identifier = f"{process_group_id}/{process_model_id}" diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 335e6a89..870dc440 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -140,6 +140,7 @@ export default function ProcessInstanceListTable({ const [endFromTimeInvalid, setEndFromTimeInvalid] = useState(false); const [endToTimeInvalid, setEndToTimeInvalid] = useState(false); const [requiresRefilter, setRequiresRefilter] = useState(false); + const [lastColumnFilter, setLastColumnFilter] = useState(''); const processInstanceListPathPrefix = variant === 'all' @@ -1105,10 +1106,18 @@ export default function ProcessInstanceListTable({ return null; } - // get the columns anytime we display the filter options if they are empty - if (availableReportColumns.length < 1) { + let queryParamString = ''; + if (processModelSelection) { + queryParamString += `?process_model_identifier=${processModelSelection.id}`; + } + // get the columns anytime we display the filter options if they are empty. + // and if the columns are not empty, check if the columns are stale + // because we selected a different process model in the filter options. + const columnFilterIsStale = lastColumnFilter !== queryParamString; + if (availableReportColumns.length < 1 || columnFilterIsStale) { + setLastColumnFilter(queryParamString); HttpService.makeCallToBackend({ - path: `/process-instances/reports/columns`, + path: `/process-instances/reports/columns${queryParamString}`, successCallback: setAvailableReportColumns, }); } diff --git a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx index b7debc6b..21847bbf 100644 --- a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx +++ b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx @@ -36,10 +36,17 @@ export default function ProcessModelSearch({ const shouldFilterProcessModel = (options: any) => { const processModel: ProcessModel = options.item; - const { inputValue } = options; - return getFullProcessModelLabel(processModel) - .toLowerCase() - .includes((inputValue || '').toLowerCase()); + let { inputValue } = options; + if (!inputValue) { + inputValue = ''; + } + const inputValueArray = inputValue.split(' '); + const processModelLowerCase = + getFullProcessModelLabel(processModel).toLowerCase(); + + return inputValueArray.every((i: any) => { + return processModelLowerCase.includes((i || '').toLowerCase()); + }); }; return (