diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index ebf27ad82..b71bed93d 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 c2a832866..67a2a6e46 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 f01a574f6..b608c4d9f 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-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 2681b6785..a13552f37 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 = ( @@ -1143,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); diff --git a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx index d8c855340..c33946985 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]);