From 1a6a2c0ae9e1ea4d4aafcd6b8ee35ace1976869d Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Fri, 7 Jul 2023 10:11:12 -0400 Subject: [PATCH 1/5] Feature/fix diagram zoom (#378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "set undefined values to null rjsf form data w/ burnettk (#336)" This reverts commit cca9b147f6648b2aa0717c7859ce88056640f4a8. * Quick test to check if calls are happening correctly to resize the diagram. * Quick test to check if calls are happening correctly to resize the diagram. * remove all the logic around fitviewport limitations, which doesn't seem to be requured. For some reason (modeler as any).constructor.name return '_9' on the dev server, rather than returning 'Modeler', 'Viewer' etal ... * drop console log * Clean up comments. --------- Co-authored-by: Jakub SokoĊ‚owski Co-authored-by: jasquat Co-authored-by: burnettk Co-authored-by: Madhurya Liyanage --- .../src/components/ReactDiagramEditor.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/spiffworkflow-frontend/src/components/ReactDiagramEditor.tsx b/spiffworkflow-frontend/src/components/ReactDiagramEditor.tsx index 8d4eb8afa..194c7968b 100644 --- a/spiffworkflow-frontend/src/components/ReactDiagramEditor.tsx +++ b/spiffworkflow-frontend/src/components/ReactDiagramEditor.tsx @@ -425,17 +425,7 @@ export default function ReactDiagramEditor({ } const canvas = (modeler as any).get('canvas'); - - // only get the canvas if the dmn active viewer is actually - // a Modeler and not an Editor which is what it will be when we are - // actively editing a decision table - if ((modeler as any).constructor.name === 'Modeler') { - canvas.zoom(FitViewport, 'auto'); - } - - if ((modeler as any).constructor.name === 'Viewer') { - canvas.zoom(FitViewport, 'auto'); - } + canvas.zoom(FitViewport, 'auto'); // Concerned this might bug out somehow. // highlighting a field // Option 3 at: From e6094a83a8f0d0513e6afab2cf6bacb0729fbe83 Mon Sep 17 00:00:00 2001 From: Kevin Burnett <18027+burnettk@users.noreply.github.com> Date: Fri, 7 Jul 2023 07:51:22 -0700 Subject: [PATCH 2/5] do not convert null to a string when clearing out a typeahead field w/ burnettk (#373) Co-authored-by: jasquat --- .../custom_widgets/TypeaheadWidget/TypeaheadWidget.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spiffworkflow-frontend/src/rjsf/custom_widgets/TypeaheadWidget/TypeaheadWidget.tsx b/spiffworkflow-frontend/src/rjsf/custom_widgets/TypeaheadWidget/TypeaheadWidget.tsx index 7ee9e073b..9931ddf1d 100644 --- a/spiffworkflow-frontend/src/rjsf/custom_widgets/TypeaheadWidget/TypeaheadWidget.tsx +++ b/spiffworkflow-frontend/src/rjsf/custom_widgets/TypeaheadWidget/TypeaheadWidget.tsx @@ -114,7 +114,15 @@ export default function TypeaheadWidget({ onInputChange={typeaheadSearch} onChange={(event: any) => { setSelectedItem(event.selectedItem); - onChange(JSON.stringify(event.selectedItem)); + let valueToUse = event.selectedItem; + + // if the value is not truthy then do not stringify it + // otherwise things like null becomes "null" + if (valueToUse) { + valueToUse = JSON.stringify(valueToUse); + } + + onChange(valueToUse); }} id={id} items={items} From 7c3c3af057df5006e8a722f7ddbbd0b4dddf78a5 Mon Sep 17 00:00:00 2001 From: jasquat <2487833+jasquat@users.noreply.github.com> Date: Fri, 7 Jul 2023 10:52:57 -0400 Subject: [PATCH 3/5] =?UTF-8?q?also=20check=20human=20task=20table=20for?= =?UTF-8?q?=20completed=20by=20user=20when=20determining=20if=E2=80=A6=20(?= =?UTF-8?q?#370)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * also check human task table for completed by user when determining if an instance is associated with a user * update pygments * added some comments for clarity w/ burnettk --------- Co-authored-by: jasquat Co-authored-by: burnettk --- spiffworkflow-backend/poetry.lock | 11 ++++++----- .../routes/process_instances_controller.py | 4 ++++ .../services/process_instance_processor.py | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock index 34e178c20..72accf583 100644 --- a/spiffworkflow-backend/poetry.lock +++ b/spiffworkflow-backend/poetry.lock @@ -1700,14 +1700,14 @@ files = [ [[package]] name = "pygments" -version = "2.14.0" +version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [package.extras] @@ -2122,7 +2122,8 @@ files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, 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 4869266f1..3636f2f8c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py @@ -713,7 +713,11 @@ def _find_process_instance_for_me_or_raise( ) .filter( or_( + # you were allowed to complete it HumanTaskUserModel.id.is_not(None), + # or you completed it (which admins can do even if it wasn't assigned via HumanTaskUserModel) + HumanTaskModel.completed_by_user_id == g.user.id, + # or you started it ProcessInstanceModel.process_initiator_id == g.user.id, ) ) 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 ae09994f5..896ac2b06 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -1095,6 +1095,7 @@ class ProcessInstanceProcessor: event_type = ProcessInstanceEventType.task_skipped.value start_time = time.time() + # manual actually means any human task if spiff_task.task_spec.manual: # Executing or not executing a human task results in the same state. current_app.logger.info( From 5ffb3e5ae56751f238d107c5e2ac1d4c9b2eda18 Mon Sep 17 00:00:00 2001 From: jasquat <2487833+jasquat@users.noreply.github.com> Date: Fri, 7 Jul 2023 10:54:10 -0400 Subject: [PATCH 4/5] restrict to specific user if querying for instances with tasks waiting for me w/ burnettk (#372) Co-authored-by: jasquat --- .../services/process_instance_report_service.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py index 56b6fdb4b..b89fb60e0 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py @@ -244,7 +244,9 @@ class ProcessInstanceReportService: return results @classmethod - def add_human_task_fields(cls, process_instance_dicts: list[dict]) -> list[dict]: + def add_human_task_fields( + cls, process_instance_dicts: list[dict], restrict_human_tasks_to_user: UserModel | None = None + ) -> list[dict]: fields_to_return = [ "task_id", "task_title", @@ -264,6 +266,10 @@ class ProcessInstanceReportService: .outerjoin(assigned_user, assigned_user.id == HumanTaskUserModel.user_id) .outerjoin(GroupModel, GroupModel.id == HumanTaskModel.lane_assignment_id) ) + if restrict_human_tasks_to_user is not None: + human_task_query = human_task_query.filter( + HumanTaskUserModel.user_id == restrict_human_tasks_to_user.id + ) potential_owner_usernames_from_group_concat_or_similar = cls._get_potential_owner_usernames(assigned_user) human_task = ( human_task_query.add_columns( @@ -371,6 +377,7 @@ class ProcessInstanceReportService: # Always join that hot user table for good performance at serialization time. process_instance_query = process_instance_query.options(selectinload(ProcessInstanceModel.process_initiator)) filters = report_metadata["filter_by"] + restrict_human_tasks_to_user = None for value in cls.check_filter_value(filters, "process_model_identifier"): process_model = ProcessModelService.get_process_model( @@ -483,6 +490,7 @@ class ProcessInstanceReportService: and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id), ) human_task_already_joined = True + restrict_human_tasks_to_user = user if user_group_identifier is not None: group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id] @@ -573,7 +581,7 @@ class ProcessInstanceReportService: for value in cls.check_filter_value(filters, "with_oldest_open_task"): if value is True: - results = cls.add_human_task_fields(results) + results = cls.add_human_task_fields(results, restrict_human_tasks_to_user=restrict_human_tasks_to_user) report_metadata["filter_by"] = filters response_json = { From 90bfc53380e676d56d257f493e71ea26a100da3c Mon Sep 17 00:00:00 2001 From: jasquat <2487833+jasquat@users.noreply.github.com> Date: Fri, 7 Jul 2023 10:54:57 -0400 Subject: [PATCH 5/5] use a sha256 value as the id of the page when checking active-users w/ burnettk (#356) Co-authored-by: jasquat --- .../src/components/ActiveUsers.tsx | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/spiffworkflow-frontend/src/components/ActiveUsers.tsx b/spiffworkflow-frontend/src/components/ActiveUsers.tsx index 87b2e885d..96e493730 100644 --- a/spiffworkflow-frontend/src/components/ActiveUsers.tsx +++ b/spiffworkflow-frontend/src/components/ActiveUsers.tsx @@ -1,36 +1,51 @@ import { useEffect, useState } from 'react'; import HttpService from '../services/HttpService'; -import { - encodeBase64, - refreshAtInterval, - REFRESH_TIMEOUT_SECONDS, -} from '../helpers'; import { User } from '../interfaces'; +import { refreshAtInterval, REFRESH_TIMEOUT_SECONDS } from '../helpers'; + +async function sha256(message: string) { + // encode as UTF-8 + const msgBuffer = new TextEncoder().encode(message); + + // hash the message + const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); + + // convert ArrayBuffer to Array + const hashArray = Array.from(new Uint8Array(hashBuffer)); + + // convert bytes to hex string + return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); +} export default function ActiveUsers() { // Handles getting and displaying active users. const [activeUsers, setActiveUsers] = useState([]); - const lastVisitedIdentifier = encodeBase64(window.location.pathname); useEffect(() => { const updateActiveUsers = () => { - HttpService.makeCallToBackend({ - path: `/active-users/updates/${lastVisitedIdentifier}`, - successCallback: setActiveUsers, - httpMethod: 'POST', - }); - }; - - const unregisterUser = () => { - HttpService.makeCallToBackend({ - path: `/active-users/unregister/${lastVisitedIdentifier}`, - successCallback: setActiveUsers, - httpMethod: 'POST', - }); + const makeCall = (lastVisitedIdentifier: any) => { + HttpService.makeCallToBackend({ + path: `/active-users/updates/${lastVisitedIdentifier}`, + successCallback: setActiveUsers, + httpMethod: 'POST', + }); + }; + sha256(window.location.pathname).then(makeCall); }; updateActiveUsers(); + const unregisterUser = () => { + const makeCall = (lastVisitedIdentifier: any) => { + HttpService.makeCallToBackend({ + path: `/active-users/unregister/${lastVisitedIdentifier}`, + successCallback: setActiveUsers, + httpMethod: 'POST', + }); + }; + sha256(window.location.pathname).then(makeCall); + }; + return refreshAtInterval( 15, REFRESH_TIMEOUT_SECONDS,