diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index ebf27ad8..b71bed93 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -1538,6 +1538,25 @@ paths: items: $ref: "#/components/schemas/User" + /users/exists/by-username: + post: + tags: + - Users + operationId: spiffworkflow_backend.routes.users_controller.user_exists_by_username + summary: Returns a true if user exists by username. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + responses: + "200": + description: true if user exists + content: + application/json: + schema: + $ref: "#/components/schemas/OkTrue" + /user-groups/for-current-user: get: tags: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/users_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/users_controller.py index c2a83286..67a2a6e4 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/users_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/users_controller.py @@ -1,13 +1,29 @@ """Users_controller.""" +from typing import Any +from typing import Dict + import flask from flask import current_app from flask import g from flask import jsonify from flask import make_response +from spiffworkflow_backend.exceptions.api_error import ApiError from spiffworkflow_backend.models.user import UserModel +def user_exists_by_username(body: Dict[str, Any]) -> flask.wrappers.Response: + if "username" not in body: + raise ApiError( + error_code="username_not_given", + message="Username could not be found in post body.", + status_code=400, + ) + username = body["username"] + found_user = UserModel.query.filter_by(username=username).first() + return make_response(jsonify({"user_found": found_user is not None}), 200) + + def user_search(username_prefix: str) -> flask.wrappers.Response: """User_search.""" found_users = UserModel.query.filter(UserModel.username.like(f"{username_prefix}%")).all() # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index f01a574f..b608c4d9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -565,6 +565,7 @@ class AuthorizationService: permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/service-tasks")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/user-groups/for-current-user")) + permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username")) permissions_to_assign.append( PermissionToAssign(permission="read", target_uri="/process-instances/find-by-id/*") ) 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 d7ea5613..45e83d7c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -25,7 +25,6 @@ from spiffworkflow_backend.models.process_instance_file_data import ( from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.task import Task from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.services.assertion_service import safe_assertion from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.git_service import GitCommandError from spiffworkflow_backend.services.git_service import GitService @@ -96,13 +95,6 @@ class ProcessInstanceService: ) process_instance_lock_prefix = "Background" for process_instance in records: - with safe_assertion(process_instance.status == status_value) as false_assumption: - if false_assumption: - raise AssertionError( - f"Queue assumed process instance {process_instance.id} has status of {status_value} " - f"when it really is {process_instance.status}" - ) - locked = False processor = None try: @@ -110,10 +102,12 @@ class ProcessInstanceService: processor = ProcessInstanceProcessor(process_instance) processor.lock_process_instance(process_instance_lock_prefix) locked = True - execution_strategy_name = current_app.config[ - "SPIFFWORKFLOW_BACKEND_ENGINE_STEP_DEFAULT_STRATEGY_BACKGROUND" - ] - processor.do_engine_steps(save=True, execution_strategy_name=execution_strategy_name) + db.session.refresh(process_instance) + if process_instance.status == status_value: + execution_strategy_name = current_app.config[ + "SPIFFWORKFLOW_BACKEND_ENGINE_STEP_DEFAULT_STRATEGY_BACKGROUND" + ] + processor.do_engine_steps(save=True, execution_strategy_name=execution_strategy_name) except ProcessInstanceIsAlreadyLockedError: continue except Exception as e: diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index e13ad390..8dacce35 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -288,6 +288,7 @@ class TestAuthorizationService(BaseTest): ("/tasks/*", "read"), ("/tasks/*", "update"), ("/user-groups/for-current-user", "read"), + ("/users/exists/by-username", "create"), ] permissions_to_assign = AuthorizationService.explode_permissions("all", "BASIC") permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index c4ccc313..a13552f3 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -182,6 +182,10 @@ export default function ProcessInstanceListTable({ const [processInitiatorText, setProcessInitiatorText] = useState< string | null >(null); + const [ + processInitiatorNotFoundErrorText, + setProcessInitiatorNotFoundErrorText, + ] = useState(''); const lastRequestedInitatorSearchTerm = useRef(); @@ -560,8 +564,17 @@ export default function ProcessInstanceListTable({ return (reportMetadata as any).filter_by; }; + const navigateToNewReport = (queryParamString: string) => { + removeError(); + setProcessInstanceReportJustSaved(null); + setProcessInstanceFilters({}); + navigate(`${processInstanceListPathPrefix}?${queryParamString}`); + }; + const applyFilter = (event: any) => { event.preventDefault(); + setProcessInitiatorNotFoundErrorText(''); + const { page, perPage } = getPageInfoFromSearchParams( searchParams, undefined, @@ -605,21 +618,33 @@ export default function ProcessInstanceListTable({ queryParamString += `&report_id=${processInstanceReportSelection.id}`; } - if (processInitiatorSelection) { - queryParamString += `&process_initiator_username=${processInitiatorSelection.username}`; - } else if (processInitiatorText) { - queryParamString += `&process_initiator_username=${processInitiatorText}`; - } - const reportColumnsBase64 = encodeBase64(JSON.stringify(reportColumns())); queryParamString += `&report_columns=${reportColumnsBase64}`; const reportFilterByBase64 = encodeBase64(JSON.stringify(reportFilterBy())); queryParamString += `&report_filter_by=${reportFilterByBase64}`; - removeError(); - setProcessInstanceReportJustSaved(null); - setProcessInstanceFilters({}); - navigate(`${processInstanceListPathPrefix}?${queryParamString}`); + if (processInitiatorSelection) { + queryParamString += `&process_initiator_username=${processInitiatorSelection.username}`; + navigateToNewReport(queryParamString); + } else if (processInitiatorText) { + HttpService.makeCallToBackend({ + path: targetUris.userExists, + httpMethod: 'POST', + postBody: { username: processInitiatorText }, + successCallback: (result: any) => { + if (result.user_found) { + queryParamString += `&process_initiator_username=${processInitiatorText}`; + navigateToNewReport(queryParamString); + } else { + setProcessInitiatorNotFoundErrorText( + `The provided username is invalid. Please type the exact username.` + ); + } + }, + }); + } else { + navigateToNewReport(queryParamString); + } }; const dateComponent = ( @@ -683,6 +708,7 @@ export default function ProcessInstanceListTable({ items={processStatusAllOptions} onChange={(selection: any) => { setProcessStatusSelection(selection.selectedItems); + setRequiresRefilter(true); }} itemToString={(item: any) => { return item || ''; @@ -1142,6 +1168,8 @@ export default function ProcessInstanceListTable({ id="process-instance-initiator-search" placeholder="Enter username" labelText="Process Initiator" + invalid={processInitiatorNotFoundErrorText !== ''} + invalidText={processInitiatorNotFoundErrorText} onChange={(event: any) => { setProcessInitiatorText(event.target.value); setRequiresRefilter(true); @@ -1450,14 +1478,18 @@ export default function ProcessInstanceListTable({ // eslint-disable-next-line prefer-destructuring perPage = perPageOptions[1]; } - let resultsTable = ( -

- Please press the filter button when you have completed updating the - filters. -

- ); - if (!requiresRefilter) { - resultsTable = ( + let refilterTextComponent = null; + if (requiresRefilter) { + refilterTextComponent = ( +

+ Please press the filter button when you have completed updating the + filters. +

+ ); + } + const resultsTable = ( + <> + {refilterTextComponent} - ); - } + + ); return ( <> {reportColumnForm()} diff --git a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx index d8c85534..c3394698 100644 --- a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx +++ b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx @@ -30,6 +30,7 @@ export const useUriListForPermissions = () => { processModelShowPath: `/v1.0/process-models/${params.process_model_id}`, secretListPath: `/v1.0/secrets`, userSearch: `/v1.0/users/search`, + userExists: `/v1.0/users/exists/by-username`, }; }, [params]);