diff --git a/CHANGELOG.md b/CHANGELOG.md index ce42ae1ce9..31c181fc8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Mark items as `Added`, `Changed`, `Fixed`, `Modified`, `Removed`, `Untested Features`, `Upcoming Features`, `Known Issues` +## 6.1.0 + +`Added` + + **forms-flow-bpm** +* Added support to fetch secrets from Vault. +* Added environment variables + `VAULT_ENABLED`, `VAULT_URL`, `VAULT_TOKEN`, `VAULT_PATH`, `VAULT_SECRET` to support Vault. + ## 6.0.2 - 2024-06-05 `Added` diff --git a/deployment/docker/sample.env b/deployment/docker/sample.env index 4ca3eb893b..e4890908dc 100644 --- a/deployment/docker/sample.env +++ b/deployment/docker/sample.env @@ -255,6 +255,6 @@ CUSTOM_SUBMISSION_URL=http://{your-ip-address}:{port} # Vault configuration # VAULT_ENABLED=false # VAULT_URL=http://{your-ip-address}:8200 -# VAULT_TOKEN= +# VAULT_TOKEN= # VAULT_PATH= # VAULT_SECRET= \ No newline at end of file diff --git a/forms-flow-api-utils/src/formsflow_api_utils/services/__init__.py b/forms-flow-api-utils/src/formsflow_api_utils/services/__init__.py index 2802cf5096..178c287cb5 100644 --- a/forms-flow-api-utils/src/formsflow_api_utils/services/__init__.py +++ b/forms-flow-api-utils/src/formsflow_api_utils/services/__init__.py @@ -1,7 +1,8 @@ """This exports all of the services used by the application.""" from formsflow_api_utils.services.external.formio import FormioService - +from formsflow_api_utils.services.external.custom_submission import CustomSubmissionService __all__ = [ "FormioService", + "CustomSubmissionService" ] diff --git a/forms-flow-api-utils/src/formsflow_api_utils/services/external/__init__.py b/forms-flow-api-utils/src/formsflow_api_utils/services/external/__init__.py index 0a568cdf99..b0725ca811 100644 --- a/forms-flow-api-utils/src/formsflow_api_utils/services/external/__init__.py +++ b/forms-flow-api-utils/src/formsflow_api_utils/services/external/__init__.py @@ -1,3 +1,4 @@ """This exports all of the external communication services used by the application.""" from .formio import FormioService +from .custom_submission import CustomSubmissionService diff --git a/forms-flow-api-utils/src/formsflow_api_utils/services/external/custom_submission.py b/forms-flow-api-utils/src/formsflow_api_utils/services/external/custom_submission.py new file mode 100644 index 0000000000..41f7cb0d12 --- /dev/null +++ b/forms-flow-api-utils/src/formsflow_api_utils/services/external/custom_submission.py @@ -0,0 +1,33 @@ +import requests +from flask import current_app + +from formsflow_api_utils.exceptions import BusinessException, ExternalError +from formsflow_api_utils.utils import HTTP_TIMEOUT +from typing import Any + +class CustomSubmissionService: + + def __init__(self) -> None: + self.base_url = current_app.config.get("CUSTOM_SUBMISSION_URL") + + def __get_headers(self, token: str) -> dict: + """Returns the headers for the request.""" + return {"Authorization": token, "Content-Type": "application/json"} + + def fetch_submission_data(self, token: str, form_id: str, submission_id: str) -> Any: + """Returns the submission data from the form adapter.""" + if not self.base_url: + raise BusinessException("Base URL for custom submission is not configured.") + + submission_url = f"{self.base_url}/form/{form_id}/submission/{submission_id}" + current_app.logger.debug(f"Fetching custom submission data: {submission_url}") + headers = self.__get_headers(token) + + try: + response = requests.get(submission_url, headers=headers, timeout=HTTP_TIMEOUT) + current_app.logger.debug(f"Custom submission response code: {response.status_code}") + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + current_app.logger.error(f"Error fetching custom submission data: {str(e)}") + raise ExternalError("Failed to fetch custom submission data.") from e diff --git a/forms-flow-api-utils/src/formsflow_api_utils/utils/caching.py b/forms-flow-api-utils/src/formsflow_api_utils/utils/caching.py index 898face689..7bbab23887 100644 --- a/forms-flow-api-utils/src/formsflow_api_utils/utils/caching.py +++ b/forms-flow-api-utils/src/formsflow_api_utils/utils/caching.py @@ -4,7 +4,8 @@ from flask import current_app from redis.client import Redis import json - +import os +from redis import RedisCluster as rc class RedisManager: """ @@ -28,11 +29,15 @@ def get_client(cls, app=None) -> Redis: Raises: Exception: If the Redis client has not been initialized and no app context is provided. """ + redis_cluster = os.getenv('REDIS_CLUSTER', 'false').lower() == 'true' if cls._redis_client is None: if app is None: app = current_app redis_url = app.config.get("REDIS_URL") - cls._redis_client = redis.StrictRedis.from_url(redis_url) + if redis_cluster: + cls._redis_client = rc.from_url(redis_url) + else: + cls._redis_client = redis.StrictRedis.from_url(redis_url) app.logger.info("Redis client initiated successfully") return cls._redis_client @@ -75,4 +80,3 @@ def get(cls, key): return json.loads(val_json) # Deserialize the JSON string back into Python object else: return None - diff --git a/forms-flow-api/requirements.txt b/forms-flow-api/requirements.txt index c57e6d2804..a8c3777f94 100644 --- a/forms-flow-api/requirements.txt +++ b/forms-flow-api/requirements.txt @@ -51,7 +51,7 @@ pyparsing==3.1.2 python-dotenv==1.0.1 python-jose==3.3.0 pytz==2024.1 -redis==5.0.3 +redis==5.0.5 referencing==0.34.0 requests==2.31.0 rpds-py==0.18.0 diff --git a/forms-flow-api/sample.env b/forms-flow-api/sample.env index 2b24a5a510..e27cdb90b0 100644 --- a/forms-flow-api/sample.env +++ b/forms-flow-api/sample.env @@ -69,4 +69,5 @@ FORMIO_ROOT_PASSWORD=changeme #CONFIGURE_LOGS=true #Redis configuration -REDIS_URL=redis://{your-ip-address}:6379/0 \ No newline at end of file +REDIS_URL=redis://{your-ip-address}:6379/0 +REDIS_CLUSTER=false \ No newline at end of file diff --git a/forms-flow-api/src/formsflow_api/models/application_history.py b/forms-flow-api/src/formsflow_api/models/application_history.py index b2a7166055..7f2ed9c5cb 100644 --- a/forms-flow-api/src/formsflow_api/models/application_history.py +++ b/forms-flow-api/src/formsflow_api/models/application_history.py @@ -55,3 +55,8 @@ def get_application_history(cls, application_id: int): cls.submitted_by, ) ) + + @classmethod + def get_application_history_by_id(cls, application_id: int): + """Find application history by id.""" + return cls.query.filter(cls.application_id == application_id).first() diff --git a/forms-flow-api/src/formsflow_api/resources/application_history.py b/forms-flow-api/src/formsflow_api/resources/application_history.py index adfcb9224a..6a46f8f55e 100644 --- a/forms-flow-api/src/formsflow_api/resources/application_history.py +++ b/forms-flow-api/src/formsflow_api/resources/application_history.py @@ -94,12 +94,11 @@ def post(application_id): """Post a new history entry using the request body.""" application_history_json = request.get_json() - # try: application_history_schema = ApplicationHistorySchema() dict_data = application_history_schema.load(application_history_json) dict_data["application_id"] = application_id application_history = ApplicationHistoryService.create_application_history( - data=dict_data + data=dict_data, application_id=application_id ) response, status = ( diff --git a/forms-flow-api/src/formsflow_api/services/application_history.py b/forms-flow-api/src/formsflow_api/services/application_history.py index afeb95f1be..55c39bc6f2 100644 --- a/forms-flow-api/src/formsflow_api/services/application_history.py +++ b/forms-flow-api/src/formsflow_api/services/application_history.py @@ -3,7 +3,7 @@ from flask import current_app from formsflow_api_utils.utils import get_form_and_submission_id_from_form_url -from formsflow_api.models import ApplicationHistory +from formsflow_api.models import Application, ApplicationHistory from formsflow_api.schemas import ApplicationHistorySchema @@ -11,8 +11,18 @@ class ApplicationHistoryService: """This class manages application service.""" @staticmethod - def create_application_history(data): + def create_application_history(data, application_id): """Create new application history.""" + # Replace service-account with application creator in initial history. + if data.get("submitted_by") and data.get("submitted_by").startswith( + "service-account" + ): + application_history = ApplicationHistory.get_application_history_by_id( + application_id + ) + if not application_history: + application = Application.find_by_id(application_id) + data["submitted_by"] = application.created_by (form_id, submission_id) = get_form_and_submission_id_from_form_url( data["form_url"] ) diff --git a/forms-flow-api/tests/unit/api/test_application_history.py b/forms-flow-api/tests/unit/api/test_application_history.py index 1f584e76af..7fc5b3e295 100644 --- a/forms-flow-api/tests/unit/api/test_application_history.py +++ b/forms-flow-api/tests/unit/api/test_application_history.py @@ -5,6 +5,7 @@ from formsflow_api_utils.utils import VIEW_SUBMISSIONS from tests.utilities.base_test import get_token +from formsflow_api.models import Application def get_history_create_payload(): @@ -71,3 +72,28 @@ def test_application_history_get_un_authorized(app, client, session, jwt): # sending get request withouttoken rv = client.get("/application/1/history") assert rv.status_code == 401 + + +def create_application_history_service_account(app, client, session, jwt): + """Tests if the initial application history created with a service account replaced by application creator.""" + application = Application() + application.created_by = "client" + application.application_status = "New" + application.form_process_mapper_id = 1 + application.submission_id = "2345" + application.latest_form_id = "1234" + application.save() + + payload = { + "applicationId": 1, + "applicationStatus": "New", + "formUrl": "http://testsample.com/form/23/submission/3423", + "submittedBy": "service-account-bpmn", + } + token = get_token(jwt) + headers = {"Authorization": f"Bearer {token}", "content-type": "application/json"} + new_entry = client.post( + "/application/1/history", headers=headers, json=payload + ) + assert new_entry.status_code == 201 + assert new_entry.submitted_by == "client" diff --git a/forms-flow-api/tests/unit/services/test_application_history.py b/forms-flow-api/tests/unit/services/test_application_history.py index 28b61a7e03..580e7096b6 100644 --- a/forms-flow-api/tests/unit/services/test_application_history.py +++ b/forms-flow-api/tests/unit/services/test_application_history.py @@ -14,7 +14,7 @@ def test_create_application_history(app, client, session): } payload["application_id"] = 1222 # sample value application_history = application_history_service.create_application_history( - data=payload + data=payload, application_id=1222 ) assert application_history.application_id == 1222 assert application_history.application_status == "Pending" diff --git a/forms-flow-documents/docker-compose.yml b/forms-flow-documents/docker-compose.yml index 5a0739f3b1..89e232f283 100644 --- a/forms-flow-documents/docker-compose.yml +++ b/forms-flow-documents/docker-compose.yml @@ -40,7 +40,6 @@ services: tty: true # -t networks: - forms-flow-webapi-network - - redis redis: image: "redis:alpine" diff --git a/forms-flow-idm/keycloak/docker-compose.yml b/forms-flow-idm/keycloak/docker-compose.yml index df21043fab..c50b1b7772 100644 --- a/forms-flow-idm/keycloak/docker-compose.yml +++ b/forms-flow-idm/keycloak/docker-compose.yml @@ -21,7 +21,7 @@ services: POSTGRES_USER: ${KEYCLOAK_JDBC_USER:-admin} POSTGRES_PASSWORD: ${KEYCLOAK_JDBC_PASSWORD:-changeme} ports: - - 5431:5431 + - 5431:5432 networks: - keycloak-server-network @@ -33,12 +33,12 @@ services: - keycloak_custom_data:/keycloak_custom_data entrypoint: ["/bin/bash", "-c", "/keycloak_custom_data/start-keycloak.sh"] environment: - - DB_VENDOR=POSTGRES - - DB_ADDR=keycloak-db - - DB_PORT=5432 - - DB_DATABASE=${KEYCLOAK_JDBC_DB:-keycloak} - - DB_USER=${KEYCLOAK_JDBC_USER:-admin} - - DB_PASSWORD=${KEYCLOAK_JDBC_PASSWORD:-changeme} + - KC_DB=postgres + - KC_DB_URL_HOST=keycloak-db + - KC_DB_URL_PORT=5432 + - KC_DB_URL_DATABASE=${KEYCLOAK_JDBC_DB:-keycloak} + - KC_DB_USERNAME=${KEYCLOAK_JDBC_USER:-admin} + - KC_DB_PASSWORD=${KEYCLOAK_JDBC_PASSWORD:-changeme} - KEYCLOAK_ADMIN=${KEYCLOAK_ADMIN_USER:-admin} - KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD:-changeme} - KEYCLOAK_START_MODE=${KEYCLOAK_START_MODE:-start-dev} diff --git a/forms-flow-idm/keycloak/themes/formsflow/login/resources/css/login.css b/forms-flow-idm/keycloak/themes/formsflow/login/resources/css/login.css index c6c6fe0fbc..026584c6d4 100644 --- a/forms-flow-idm/keycloak/themes/formsflow/login/resources/css/login.css +++ b/forms-flow-idm/keycloak/themes/formsflow/login/resources/css/login.css @@ -383,14 +383,12 @@ @media (max-width: 767px) { .login-pf body { - background: url("../img/img2.png") no-repeat; + background: none; /* background-size: cover; */ background-size: 50rem 40rem; } #kc-header { - padding-left: 15px; - padding-right: 15px; float: none; text-align: left; } @@ -398,9 +396,9 @@ #kc-header-wrapper { font-size: 16px; font-weight: bold; - padding: 20px 60px 0 0; color: #72767b; letter-spacing: 0; + padding: 0; } div.kc-logo-text { @@ -527,10 +525,9 @@ .login-pf-page .card-pf { /* max-width: none; */ /* margin-left: 0; */ - margin-right: 5rem; - padding-top: 0; border-top: 0; /* box-shadow: 0 0; */ + margin: 0 10px ; } .kc-social-grid { @@ -541,8 +538,15 @@ .kc-social-grid .kc-social-icon-text { left: -15px; } + .card-pf { + padding: 1.5rem ; + } + .login-pf-page { + padding-top: 0 ; + } } - + + .login-pf-page .login-pf-signup { font-size: 15px; color: #72767b; diff --git a/forms-flow-web/src/components/AccessDenied/index.js b/forms-flow-web/src/components/AccessDenied/index.js index a32750ef00..7e9a150e58 100644 --- a/forms-flow-web/src/components/AccessDenied/index.js +++ b/forms-flow-web/src/components/AccessDenied/index.js @@ -3,13 +3,17 @@ import { kcServiceInstance } from "../PrivateRoute"; // Import the kcServiceInst import { ReactComponent as AccessDeniedIcon } from "./AccessDenied.svg"; import './accessDenied.scss'; import { useTranslation } from "react-i18next"; -import { BASE_ROUTE } from "../../constants/constants"; +import { useSelector } from "react-redux"; import { useHistory } from "react-router-dom"; +import { MULTITENANCY_ENABLED } from "../../constants/constants"; const AccessDenied = ({ userRoles }) => { const { t } = useTranslation(); const history = useHistory(); + const tenantKey = useSelector((state) => state.tenants?.tenantId); + const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; + const handleLogout = () => { const kcInstance = kcServiceInstance(); @@ -17,7 +21,7 @@ const AccessDenied = ({ userRoles }) => { }; const handleReturn = () => { - history.push(BASE_ROUTE); + history.push(redirectUrl); }; const showReturnToLogin = userRoles?.length === 0; diff --git a/forms-flow-web/src/components/Dashboard/StatusChart.js b/forms-flow-web/src/components/Dashboard/StatusChart.js index c3e13504da..58009c6e97 100644 --- a/forms-flow-web/src/components/Dashboard/StatusChart.js +++ b/forms-flow-web/src/components/Dashboard/StatusChart.js @@ -43,8 +43,8 @@ const ChartForm = React.memo((props) => {
- {t("Form Name")} : -

1 ? "500px" : "700px", marginBottom: "30px"}}>{formName}

+ {t("Form Name")} : +

1 ? "500px" : "700px"}}>{formName}

{t("Latest Version")} :{" "} diff --git a/forms-flow-web/src/components/Form/FileUpload/fileUploadModal.js b/forms-flow-web/src/components/Form/FileUpload/fileUploadModal.js index dd9bfff691..eba2c96d67 100644 --- a/forms-flow-web/src/components/Form/FileUpload/fileUploadModal.js +++ b/forms-flow-web/src/components/Form/FileUpload/fileUploadModal.js @@ -7,8 +7,8 @@ import { DesignerAccessDenied } from "../../../actions/checkListActions"; import { Translation, useTranslation } from "react-i18next"; // eslint-disable-next-line no-unused-vars -const FileModal = React.memo(({ modalOpen = false, onClose, forms, - pathErrorMessage, isloading }) => { +const FileModal = React.memo(({ modalOpen = false, onClose, + validationErrors, isloading }) => { const dispatch = useDispatch(); const formUploadList = useSelector( (state) => state.formCheckList.formUploadFormList @@ -77,7 +77,8 @@ const FileModal = React.memo(({ modalOpen = false, onClose, forms, {(t) => t("No forms found")}

)} -
{pathErrorMessage}
+
{validationErrors?.path}
+
{validationErrors?.name}
{noAccess && {t("Access restricted by its designer..!")}} diff --git a/forms-flow-web/src/components/Form/Item/View.js b/forms-flow-web/src/components/Form/Item/View.js index a186a1d8f0..cbb08d741d 100644 --- a/forms-flow-web/src/components/Form/Item/View.js +++ b/forms-flow-web/src/components/Form/Item/View.js @@ -398,7 +398,7 @@ const View = React.memo((props) => { className="col-12" >
- {isPublic || formStatus === "active" ? ( + {(isPublic || (formStatus === "active") ) ? (
{ const { t } = useTranslation(); const [showFormUploadModal, setShowFormUploadModal] = useState(false); const [isloading, setIsLoading] = useState(true); - const [pathErrorMessage, setPathErrorMessage] = useState(''); + const [validationErrors, setValidationErrors] = useState({ + path: '', + name: '', + }); const dispatch = useDispatch(); const uploadFormNode = useRef(); const { @@ -109,7 +112,7 @@ const List = React.memo((props) => { useEffect(() => { if (!showFormUploadModal) { - setPathErrorMessage(""); + setValidationErrors((prevErrors) => ({ ...prevErrors, path: '', name: '' })); setIsLoading(true); } }, [showFormUploadModal]); @@ -283,14 +286,17 @@ const List = React.memo((props) => { .catch((err) => { const errorResponse = err.response.data; const pathErrorMessage = errorResponse.errors.path ? errorResponse.errors.path.message : ''; + const nameErrorMessage = errorResponse.errors.name ? errorResponse.errors.name.message : ''; newFormData.componentChanged = false; - if (pathErrorMessage !== '') { + if (pathErrorMessage !== '' || nameErrorMessage !== '') { dispatch(formUploadFailureCount()); setIsLoading(false); - setPathErrorMessage(pathErrorMessage); + setValidationErrors({ + path: pathErrorMessage, + name: nameErrorMessage, + }); } else { - dispatch( fetchFormByAlias(newFormData.path, async (err, formObj) => { if (!err) { @@ -498,10 +504,10 @@ const List = React.memo((props) => { isloading={isloading} modalOpen={showFormUploadModal} onClose={() => setShowFormUploadModal(false)} - pathErrorMessage={pathErrorMessage} + validationErrors={validationErrors} /> {(forms.isActive || designerFormLoading || isBPMFormListLoading) && - !searchFormLoading ? ( + !searchFormLoading ? (
diff --git a/forms-flow-web/src/components/Form/constants/ClientTable.js b/forms-flow-web/src/components/Form/constants/ClientTable.js index aeb62e3073..7499b191cd 100644 --- a/forms-flow-web/src/components/Form/constants/ClientTable.js +++ b/forms-flow-web/src/components/Form/constants/ClientTable.js @@ -179,7 +179,7 @@ function ClientTable() { onKeyDown={(e) => e.keyCode === 13 ? handleSearch() : "" } - className="bg-white" + className="bg-white out-line" data-testid="form-search-input-box" placeholder={t("Search by form title")} title={t("Search by form title")} diff --git a/forms-flow-web/src/components/Form/constants/FormTable.js b/forms-flow-web/src/components/Form/constants/FormTable.js index 7df70bae52..27584a6a2b 100644 --- a/forms-flow-web/src/components/Form/constants/FormTable.js +++ b/forms-flow-web/src/components/Form/constants/FormTable.js @@ -222,7 +222,7 @@ function FormTable() { { setSearch(e.target.value); diff --git a/forms-flow-web/src/components/Modeler/constants/bpmnTable.js b/forms-flow-web/src/components/Modeler/constants/bpmnTable.js index 04454163a1..08f3a888f8 100644 --- a/forms-flow-web/src/components/Modeler/constants/bpmnTable.js +++ b/forms-flow-web/src/components/Modeler/constants/bpmnTable.js @@ -117,7 +117,7 @@ function BpmnTable() { e.keyCode == 13 ? handleSearchButtonClick() : "" } placeholder={t("Search by workflow name")} - className="bg-white" + className="bg-white out-line" title={t("Search by workflow name")} data-testid="processes-search-workflow-input-box" aria-label={t("Search by workflow name")} diff --git a/forms-flow-web/src/components/MultiTenant/LandingPage.js b/forms-flow-web/src/components/MultiTenant/LandingPage.js index 7ecf45e2a9..3cd18c8873 100644 --- a/forms-flow-web/src/components/MultiTenant/LandingPage.js +++ b/forms-flow-web/src/components/MultiTenant/LandingPage.js @@ -14,7 +14,7 @@ const LandingPage = () => { event.preventDefault(); validateTenant(username) .then((res) => { - if (res.data.status === "INVALID") { + if (!res.data.tenantKeyExist) { setError(t("Tenant not found")); } else { setError(null); // Clear the error if validation is successful diff --git a/forms-flow-web/src/components/PrivateRoute.jsx b/forms-flow-web/src/components/PrivateRoute.jsx index b707ae4848..c26b3dda3b 100644 --- a/forms-flow-web/src/components/PrivateRoute.jsx +++ b/forms-flow-web/src/components/PrivateRoute.jsx @@ -51,6 +51,7 @@ import { import { AppConfig } from "../config"; import { getFormioRoleIds } from "../apiManager/services/userservices"; import AccessDenied from "./AccessDenied"; +import { LANGUAGE } from "../constants/constants"; export const kcServiceInstance = (tenantId = null) => { return KeycloakService.getInstance( @@ -138,7 +139,7 @@ const PrivateRoute = React.memo((props) => { if (tenantId && MULTITENANCY_ENABLED) { validateTenant(tenantId) .then((res) => { - if (res.data.status === "INVALID") { + if (!res.data.tenantKeyExist) { setTenantValid(false); } else { setTenantValid(true); @@ -165,12 +166,13 @@ const PrivateRoute = React.memo((props) => { }, [tenantId, props.store, dispatch]); useEffect(() => { - if (kcInstance) { + if (kcInstance ) { const lang = kcInstance?.userData?.locale || tenant?.tenantData?.details?.locale || - selectedLanguage; - dispatch(setLanguage(lang)); + selectedLanguage || + LANGUAGE; + dispatch(setLanguage(lang)); } }, [kcInstance, tenant?.tenantData]); diff --git a/forms-flow-web/src/components/ServiceFlow/ServiceFlow.scss b/forms-flow-web/src/components/ServiceFlow/ServiceFlow.scss index 5549afa178..fd21ce82a9 100644 --- a/forms-flow-web/src/components/ServiceFlow/ServiceFlow.scss +++ b/forms-flow-web/src/components/ServiceFlow/ServiceFlow.scss @@ -67,10 +67,7 @@ .mr-6{ margin-right: 6px; } -.assigned-user{ - font-size: 18px; - margin-bottom: 3px; -} + .z-index{ z-index: 9999; } diff --git a/forms-flow-web/src/components/ServiceFlow/list/search/TaskSearchBarView.js b/forms-flow-web/src/components/ServiceFlow/list/search/TaskSearchBarView.js index 1edf825260..94b76ab312 100644 --- a/forms-flow-web/src/components/ServiceFlow/list/search/TaskSearchBarView.js +++ b/forms-flow-web/src/components/ServiceFlow/list/search/TaskSearchBarView.js @@ -18,6 +18,9 @@ const TaskSearchBarListView = React.memo(({ toggleAllTaskVariables }) => { const taskList = useSelector((state) => state.bpmTasks.tasksList); const allTaskVariablesExpanded = useSelector((state) => state.bpmTasks.allTaskVariablesExpand); const selectedFilter = useSelector((state) => state.bpmTasks.selectedFilter); + const filterList = useSelector((state) => state.bpmTasks.filtersAndCount); + const taskFilter = filterList?.find((task)=>task.id === selectedFilter.id ); + const taskFiltercount = taskFilter ? taskFilter.count : 0; //Current Task filter count const dispatch = useDispatch(); const { t } = useTranslation(); useEffect(() => { @@ -38,9 +41,11 @@ const TaskSearchBarListView = React.memo(({ toggleAllTaskVariables }) => { return ( <> - {tasksCount > 0 ? ( -
-
+ {taskFiltercount > 0 &&
+ { + tasksCount > 0 ? ( + <> +
)} -
-
+
+
-
- +
+ + ) : null + }
)}
-
- ) : null} +
} ); }); diff --git a/forms-flow-web/src/components/ServiceFlow/list/sort/CreateNewFilter.js b/forms-flow-web/src/components/ServiceFlow/list/sort/CreateNewFilter.js index 41a54d8f08..6b1a1e4fad 100644 --- a/forms-flow-web/src/components/ServiceFlow/list/sort/CreateNewFilter.js +++ b/forms-flow-web/src/components/ServiceFlow/list/sort/CreateNewFilter.js @@ -73,6 +73,7 @@ export default function CreateNewFilterDrawer({ }) { const dispatch = useDispatch(); const [filterName, setFilterName] = useState(""); + const [createdBy, setCreatedBy] = useState(""); const [showUndefinedVariable, setShowUndefinedVariable] = useState(false); const [definitionKeyId, setDefinitionKeyId] = useState(""); const [candidateGroup, setCandidateGroup] = useState([]); @@ -98,8 +99,8 @@ export default function CreateNewFilterDrawer({ const userGroups = useSelector( (state) => state.userAuthorization?.userGroups ); - const userName = useSelector( - (state) => state.user?.userDetail?.preferred_username + const userDetails = useSelector( + (state) => state.user?.userDetail ); const sortParams = useSelector( (state) => state.bpmTasks.filterListSortParams @@ -108,7 +109,6 @@ export default function CreateNewFilterDrawer({ (state) => state.bpmTasks.filterListSearchParams ); const selectedFilter = useSelector((state) => state.bpmTasks.selectedFilter); - const filterList = useSelector((state) => state.bpmTasks.filterList); const [variables, setVariables] = useState([]); const [forms, setForms] = useState({ data: [], isLoading: true }); @@ -184,6 +184,7 @@ export default function CreateNewFilterDrawer({ useEffect(() => { if (selectedFilterData) { setFilterName(selectedFilterData.name); + setCreatedBy(selectedFilterData.createdBy); setFIlterDisplayOrder(selectedFilterData.order); let processDefinitionName = selectedFilterData?.criteria?.processDefinitionKey; @@ -323,14 +324,9 @@ export default function CreateNewFilterDrawer({ }) .finally(() => { if (selectedFilterData) { - const filterSelected = filterList?.find( - (filter) => filter.id === selectedFilterData?.id - ); if (selectedFilterData?.id === selectedFilter?.id) { dispatch(setSelectedBPMFilter(resData)); fetchTasks(resData); - } else { - dispatch(setSelectedBPMFilter(filterSelected)); } toast.success(t("Changes Applied Successfully")); } else { @@ -373,7 +369,7 @@ export default function CreateNewFilterDrawer({ roles = []; } if (permissions === PRIVATE_ONLY_YOU) { - users.push(userName); + users.push(userDetails.preferred_username); } if ( selectUserGroupIcon === "user" && @@ -397,9 +393,9 @@ export default function CreateNewFilterDrawer({ const data = { name: filterName, - order: filterDisplayOrder, + order: filterDisplayOrder, criteria: { - processDefinitionKey : definitionKeyId , + processDefinitionKey: definitionKeyId, candidateGroup: MULTITENANCY_ENABLED && candidateGroup ? tenantKey + "-" + candidateGroup @@ -465,8 +461,9 @@ export default function CreateNewFilterDrawer({ fetchBPMTaskCount(data.filters) .then((res) => { dispatch(setBPMFiltersAndCount(res.data)); - dispatch(fetchServiceTaskList(data.defaultFilter || data.filters[0] - , null, firstResult)); + const filter = data.filters.find((i) => data.defaultFilter == i.id) || + data.filters[0]; + dispatch(fetchServiceTaskList(filter, null, firstResult)); }) .catch((err) => { if (err) { @@ -609,11 +606,13 @@ export default function CreateNewFilterDrawer({ } }; - const handleOrderChange = (e)=>{ + const handleOrderChange = (e) => { const value = e.target.value; const pattern = /^[1-9]\d*$/; const validInput = pattern.test(e.target.value); - setFIlterDisplayOrder(()=> value ? Number(validInput ? value : filterDisplayOrder) : null); + setFIlterDisplayOrder(() => + value ? Number(validInput ? value : filterDisplayOrder) : null + ); }; const list = () => ( @@ -670,7 +669,9 @@ export default function CreateNewFilterDrawer({
- +
setIsMyTasksEnabled(e.target.checked)} title={t("Show only current user assigned task")} disabled={viewMode} /> -
+
{(t) => t("Show only current user assigned task")} @@ -715,7 +716,7 @@ export default function CreateNewFilterDrawer({ <>
@@ -724,7 +725,7 @@ export default function CreateNewFilterDrawer({ title={t("Display authorized tasks based on user roles")} disabled={viewMode} /> -
+
{(t) => t("Display authorized tasks based on user roles")} @@ -743,7 +744,7 @@ export default function CreateNewFilterDrawer({
@@ -853,7 +854,7 @@ export default function CreateNewFilterDrawer({
-
+
{(t) => t("Permission")}{" "} setPermissions(e.target.value)} disabled={viewMode} /> - {" "}
{permissions === SPECIFIC_USER_OR_GROUP && - specificUserGroup === SPECIFIC_USER_OR_GROUP ? ( + specificUserGroup === SPECIFIC_USER_OR_GROUP ? (
@@ -973,7 +974,13 @@ export default function CreateNewFilterDrawer({
) : null}
- + {createdBy && <> +
+ {(t) => t("Filter Created by")} +
+ + {createdBy} + } diff --git a/forms-flow-web/src/containers/TaskHead.js b/forms-flow-web/src/containers/TaskHead.js index 7efa7e11c8..4f8fbfe140 100644 --- a/forms-flow-web/src/containers/TaskHead.js +++ b/forms-flow-web/src/containers/TaskHead.js @@ -11,11 +11,12 @@ import { fetchBPMTaskCount } from "../apiManager/services/bpmTaskServices"; import { toast } from "react-toastify"; import { updateDefaultFilter } from "../apiManager/services/userservices"; import _ from "lodash"; + function TaskHead() { const dispatch = useDispatch(); const { t } = useTranslation(); const tenantKey = useSelector((state) => state.tenants?.tenantId); - const itemCount = useSelector((state) => state.bpmTasks.tasksCount); + const filtersAndCount = useSelector((state) => state.bpmTasks.filtersAndCount); const [filterSelectedForEdit, setFilterSelectedForEdit] = useState(null); const [openFilterDrawer, setOpenFilterDrawer] = useState(false); const [viewMode, setViewMode] = useState(false); @@ -48,10 +49,15 @@ function TaskHead() { }; const filterListLoading = () => { - return <>{isFilterLoading && <> {t("Loading...")}}; + return <>{isFilterLoading && <>{t("Loading...")}}; + }; + + const getItemCount = (filterName) => { + const filter = filtersAndCount?.find(f => f.name === filterName); + return filter ? filter.count : 0; }; - const count = isTaskListLoading ? "" : `(${itemCount})`; + const count = isTaskListLoading ? "" : `(${getItemCount(selectedFilter?.name)})`; const textTruncate = (wordLength, targetLength, text) => { return text?.length > wordLength @@ -158,3 +164,4 @@ function TaskHead() { } export default TaskHead; + diff --git a/forms-flow-web/src/helper/helper.js b/forms-flow-web/src/helper/helper.js index 9f16a9bf07..1f37bea0b6 100644 --- a/forms-flow-web/src/helper/helper.js +++ b/forms-flow-web/src/helper/helper.js @@ -35,7 +35,7 @@ const textTruncate = (wordLength, targetLength, text) => { }; const renderPage = (formStatus, processLoadError) => { - if (!processLoadError && ((formStatus === "inactive") || !formStatus)) { + if (!processLoadError && (formStatus === "inactive")) { return (