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 dc0f2d76..65821bfb 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py @@ -68,9 +68,9 @@ from spiffworkflow_backend.services.process_instance_queue_service import ( from spiffworkflow_backend.services.process_instance_queue_service import ( ProcessInstanceQueueService, ) -from spiffworkflow_backend.services.process_instance_report_service import ( - ProcessInstanceReportFilter, -) +# from spiffworkflow_backend.services.process_instance_report_service import ( +# ProcessInstanceReportFilter, +# ) from spiffworkflow_backend.services.process_instance_report_service import ( ProcessInstanceReportService, ) 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 a4cadb96..92040b2b 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 @@ -37,71 +37,71 @@ class ProcessInstanceReportNotFoundError(Exception): """ProcessInstanceReportNotFoundError.""" -@dataclass -class ProcessInstanceReportFilter: - """ProcessInstanceReportFilter.""" - - process_model_identifier: Optional[str] = None - user_group_identifier: Optional[str] = None - start_from: Optional[int] = None - start_to: Optional[int] = None - end_from: Optional[int] = None - end_to: Optional[int] = None - process_status: Optional[list[str]] = None - initiated_by_me: Optional[bool] = None - has_terminal_status: Optional[bool] = None - has_active_status: Optional[bool] = None - with_tasks_completed_by_me: Optional[bool] = None - with_tasks_i_can_complete: Optional[bool] = None - with_tasks_assigned_to_my_group: Optional[bool] = None - with_relation_to_me: Optional[bool] = None - process_initiator_username: Optional[str] = None - report_column_list: Optional[list] = None - report_filter_by_list: Optional[list] = None - oldest_open_human_task_fields: Optional[list] = None - - def to_dict(self) -> dict[str, str]: - """To_dict.""" - d = {} - - if self.process_model_identifier is not None: - d["process_model_identifier"] = self.process_model_identifier - if self.user_group_identifier is not None: - d["user_group_identifier"] = self.user_group_identifier - if self.start_from is not None: - d["start_from"] = str(self.start_from) - if self.start_to is not None: - d["start_to"] = str(self.start_to) - if self.end_from is not None: - d["end_from"] = str(self.end_from) - if self.end_to is not None: - d["end_to"] = str(self.end_to) - if self.process_status is not None: - d["process_status"] = ",".join(self.process_status) - if self.initiated_by_me is not None: - d["initiated_by_me"] = str(self.initiated_by_me).lower() - if self.has_terminal_status is not None: - d["has_terminal_status"] = str(self.has_terminal_status).lower() - if self.has_active_status is not None: - d["has_active_status"] = str(self.has_active_status).lower() - if self.with_tasks_completed_by_me is not None: - d["with_tasks_completed_by_me"] = str(self.with_tasks_completed_by_me).lower() - if self.with_tasks_i_can_complete is not None: - d["with_tasks_i_can_complete"] = str(self.with_tasks_i_can_complete).lower() - if self.with_tasks_assigned_to_my_group is not None: - d["with_tasks_assigned_to_my_group"] = str(self.with_tasks_assigned_to_my_group).lower() - if self.with_relation_to_me is not None: - d["with_relation_to_me"] = str(self.with_relation_to_me).lower() - if self.process_initiator_username is not None: - d["process_initiator_username"] = str(self.process_initiator_username) - if self.report_column_list is not None: - d["report_column_list"] = str(self.report_column_list) - if self.report_filter_by_list is not None: - d["report_filter_by_list"] = str(self.report_filter_by_list) - if self.oldest_open_human_task_fields is not None: - d["oldest_open_human_task_fields"] = str(self.oldest_open_human_task_fields) - - return d +# @dataclass +# class ProcessInstanceReportFilter: +# """ProcessInstanceReportFilter.""" +# +# process_model_identifier: Optional[str] = None +# user_group_identifier: Optional[str] = None +# start_from: Optional[int] = None +# start_to: Optional[int] = None +# end_from: Optional[int] = None +# end_to: Optional[int] = None +# process_status: Optional[list[str]] = None +# initiated_by_me: Optional[bool] = None +# has_terminal_status: Optional[bool] = None +# has_active_status: Optional[bool] = None +# with_tasks_completed_by_me: Optional[bool] = None +# with_tasks_i_can_complete: Optional[bool] = None +# with_tasks_assigned_to_my_group: Optional[bool] = None +# with_relation_to_me: Optional[bool] = None +# process_initiator_username: Optional[str] = None +# report_column_list: Optional[list] = None +# report_filter_by_list: Optional[list] = None +# oldest_open_human_task_fields: Optional[list] = None +# +# def to_dict(self) -> dict[str, str]: +# """To_dict.""" +# d = {} +# +# if self.process_model_identifier is not None: +# d["process_model_identifier"] = self.process_model_identifier +# if self.user_group_identifier is not None: +# d["user_group_identifier"] = self.user_group_identifier +# if self.start_from is not None: +# d["start_from"] = str(self.start_from) +# if self.start_to is not None: +# d["start_to"] = str(self.start_to) +# if self.end_from is not None: +# d["end_from"] = str(self.end_from) +# if self.end_to is not None: +# d["end_to"] = str(self.end_to) +# if self.process_status is not None: +# d["process_status"] = ",".join(self.process_status) +# if self.initiated_by_me is not None: +# d["initiated_by_me"] = str(self.initiated_by_me).lower() +# if self.has_terminal_status is not None: +# d["has_terminal_status"] = str(self.has_terminal_status).lower() +# if self.has_active_status is not None: +# d["has_active_status"] = str(self.has_active_status).lower() +# if self.with_tasks_completed_by_me is not None: +# d["with_tasks_completed_by_me"] = str(self.with_tasks_completed_by_me).lower() +# if self.with_tasks_i_can_complete is not None: +# d["with_tasks_i_can_complete"] = str(self.with_tasks_i_can_complete).lower() +# if self.with_tasks_assigned_to_my_group is not None: +# d["with_tasks_assigned_to_my_group"] = str(self.with_tasks_assigned_to_my_group).lower() +# if self.with_relation_to_me is not None: +# d["with_relation_to_me"] = str(self.with_relation_to_me).lower() +# if self.process_initiator_username is not None: +# d["process_initiator_username"] = str(self.process_initiator_username) +# if self.report_column_list is not None: +# d["report_column_list"] = str(self.report_column_list) +# if self.report_filter_by_list is not None: +# d["report_filter_by_list"] = str(self.report_filter_by_list) +# if self.oldest_open_human_task_fields is not None: +# d["oldest_open_human_task_fields"] = str(self.oldest_open_human_task_fields) +# +# return d class ProcessInstanceReportService: @@ -268,141 +268,141 @@ class ProcessInstanceReportService: return process_instance_report # type: ignore - @classmethod - def filter_by_to_dict(cls, process_instance_report: ProcessInstanceReportModel) -> dict[str, str]: - """Filter_by_to_dict.""" - metadata = process_instance_report.report_metadata - filter_by = metadata.get("filter_by", []) - filters = {d["field_name"]: d["field_value"] for d in filter_by if "field_name" in d and "field_value" in d} - return filters - - @classmethod - def filter_from_metadata(cls, process_instance_report: ProcessInstanceReportModel) -> ProcessInstanceReportFilter: - """Filter_from_metadata.""" - filters = cls.filter_by_to_dict(process_instance_report) - - def bool_value(key: str) -> Optional[bool]: - """Bool_value.""" - if key not in filters: - return None - # bool returns True if not an empty string so check explicitly for false - if filters[key] in ["false", "False"]: - return False - return bool(filters[key]) - - def int_value(key: str) -> Optional[int]: - """Int_value.""" - return int(filters[key]) if key in filters else None - - def list_value(key: str) -> Optional[list[str]]: - return filters[key].split(",") if key in filters else None - - process_model_identifier = filters.get("process_model_identifier") - user_group_identifier = filters.get("user_group_identifier") - start_from = int_value("start_from") - start_to = int_value("start_to") - end_from = int_value("end_from") - end_to = int_value("end_to") - process_status = list_value("process_status") - initiated_by_me = bool_value("initiated_by_me") - has_terminal_status = bool_value("has_terminal_status") - has_active_status = bool_value("has_active_status") - with_tasks_completed_by_me = bool_value("with_tasks_completed_by_me") - with_tasks_i_can_complete = bool_value("with_tasks_i_can_complete") - with_tasks_assigned_to_my_group = bool_value("with_tasks_assigned_to_my_group") - with_relation_to_me = bool_value("with_relation_to_me") - process_initiator_username = filters.get("process_initiator_username") - report_column_list = list_value("report_column_list") - report_filter_by_list = list_value("report_filter_by_list") - oldest_open_human_task_fields = list_value("oldest_open_human_task_fields") - - report_filter = ProcessInstanceReportFilter( - process_model_identifier=process_model_identifier, - user_group_identifier=user_group_identifier, - start_from=start_from, - start_to=start_to, - end_from=end_from, - end_to=end_to, - process_status=process_status, - initiated_by_me=initiated_by_me, - has_terminal_status=has_terminal_status, - has_active_status=has_active_status, - with_tasks_completed_by_me=with_tasks_completed_by_me, - with_tasks_i_can_complete=with_tasks_i_can_complete, - with_tasks_assigned_to_my_group=with_tasks_assigned_to_my_group, - with_relation_to_me=with_relation_to_me, - process_initiator_username=process_initiator_username, - report_column_list=report_column_list, - report_filter_by_list=report_filter_by_list, - oldest_open_human_task_fields=oldest_open_human_task_fields, - ) - - return report_filter - - @classmethod - def filter_from_metadata_with_overrides( - cls, - process_instance_report: ProcessInstanceReportModel, - process_model_identifier: Optional[str] = None, - user_group_identifier: Optional[str] = None, - start_from: Optional[int] = None, - start_to: Optional[int] = None, - end_from: Optional[int] = None, - end_to: Optional[int] = None, - process_status: Optional[str] = None, - initiated_by_me: Optional[bool] = None, - has_terminal_status: Optional[bool] = None, - has_active_status: Optional[bool] = None, - with_tasks_completed_by_me: Optional[bool] = None, - with_tasks_i_can_complete: Optional[bool] = None, - with_tasks_assigned_to_my_group: Optional[bool] = None, - with_relation_to_me: Optional[bool] = None, - process_initiator_username: Optional[str] = None, - report_column_list: Optional[list] = None, - report_filter_by_list: Optional[list] = None, - oldest_open_human_task_fields: Optional[list] = None, - ) -> ProcessInstanceReportFilter: - """Filter_from_metadata_with_overrides.""" - report_filter = cls.filter_from_metadata(process_instance_report) - - if process_model_identifier is not None: - report_filter.process_model_identifier = process_model_identifier - if user_group_identifier is not None: - report_filter.user_group_identifier = user_group_identifier - if start_from is not None: - report_filter.start_from = start_from - if start_to is not None: - report_filter.start_to = start_to - if end_from is not None: - report_filter.end_from = end_from - if end_to is not None: - report_filter.end_to = end_to - if process_status is not None: - report_filter.process_status = process_status.split(",") - if initiated_by_me is not None: - report_filter.initiated_by_me = initiated_by_me - if has_terminal_status is not None: - report_filter.has_terminal_status = has_terminal_status - if has_active_status is not None: - report_filter.has_active_status = has_active_status - if with_tasks_completed_by_me is not None: - report_filter.with_tasks_completed_by_me = with_tasks_completed_by_me - if with_tasks_i_can_complete is not None: - report_filter.with_tasks_i_can_complete = with_tasks_i_can_complete - if process_initiator_username is not None: - report_filter.process_initiator_username = process_initiator_username - if report_column_list is not None: - report_filter.report_column_list = report_column_list - if report_filter_by_list is not None: - report_filter.report_filter_by_list = report_filter_by_list - if oldest_open_human_task_fields is not None: - report_filter.oldest_open_human_task_fields = oldest_open_human_task_fields - if with_tasks_assigned_to_my_group is not None: - report_filter.with_tasks_assigned_to_my_group = with_tasks_assigned_to_my_group - if with_relation_to_me is not None: - report_filter.with_relation_to_me = with_relation_to_me - - return report_filter + # @classmethod + # def filter_by_to_dict(cls, process_instance_report: ProcessInstanceReportModel) -> dict[str, str]: + # """Filter_by_to_dict.""" + # metadata = process_instance_report.report_metadata + # filter_by = metadata.get("filter_by", []) + # filters = {d["field_name"]: d["field_value"] for d in filter_by if "field_name" in d and "field_value" in d} + # return filters + # + # @classmethod + # def filter_from_metadata(cls, process_instance_report: ProcessInstanceReportModel) -> ProcessInstanceReportFilter: + # """Filter_from_metadata.""" + # filters = cls.filter_by_to_dict(process_instance_report) + # + # def bool_value(key: str) -> Optional[bool]: + # """Bool_value.""" + # if key not in filters: + # return None + # # bool returns True if not an empty string so check explicitly for false + # if filters[key] in ["false", "False"]: + # return False + # return bool(filters[key]) + # + # def int_value(key: str) -> Optional[int]: + # """Int_value.""" + # return int(filters[key]) if key in filters else None + # + # def list_value(key: str) -> Optional[list[str]]: + # return filters[key].split(",") if key in filters else None + # + # process_model_identifier = filters.get("process_model_identifier") + # user_group_identifier = filters.get("user_group_identifier") + # start_from = int_value("start_from") + # start_to = int_value("start_to") + # end_from = int_value("end_from") + # end_to = int_value("end_to") + # process_status = list_value("process_status") + # initiated_by_me = bool_value("initiated_by_me") + # has_terminal_status = bool_value("has_terminal_status") + # has_active_status = bool_value("has_active_status") + # with_tasks_completed_by_me = bool_value("with_tasks_completed_by_me") + # with_tasks_i_can_complete = bool_value("with_tasks_i_can_complete") + # with_tasks_assigned_to_my_group = bool_value("with_tasks_assigned_to_my_group") + # with_relation_to_me = bool_value("with_relation_to_me") + # process_initiator_username = filters.get("process_initiator_username") + # report_column_list = list_value("report_column_list") + # report_filter_by_list = list_value("report_filter_by_list") + # oldest_open_human_task_fields = list_value("oldest_open_human_task_fields") + # + # report_filter = ProcessInstanceReportFilter( + # process_model_identifier=process_model_identifier, + # user_group_identifier=user_group_identifier, + # start_from=start_from, + # start_to=start_to, + # end_from=end_from, + # end_to=end_to, + # process_status=process_status, + # initiated_by_me=initiated_by_me, + # has_terminal_status=has_terminal_status, + # has_active_status=has_active_status, + # with_tasks_completed_by_me=with_tasks_completed_by_me, + # with_tasks_i_can_complete=with_tasks_i_can_complete, + # with_tasks_assigned_to_my_group=with_tasks_assigned_to_my_group, + # with_relation_to_me=with_relation_to_me, + # process_initiator_username=process_initiator_username, + # report_column_list=report_column_list, + # report_filter_by_list=report_filter_by_list, + # oldest_open_human_task_fields=oldest_open_human_task_fields, + # ) + # + # return report_filter + # + # @classmethod + # def filter_from_metadata_with_overrides( + # cls, + # process_instance_report: ProcessInstanceReportModel, + # process_model_identifier: Optional[str] = None, + # user_group_identifier: Optional[str] = None, + # start_from: Optional[int] = None, + # start_to: Optional[int] = None, + # end_from: Optional[int] = None, + # end_to: Optional[int] = None, + # process_status: Optional[str] = None, + # initiated_by_me: Optional[bool] = None, + # has_terminal_status: Optional[bool] = None, + # has_active_status: Optional[bool] = None, + # with_tasks_completed_by_me: Optional[bool] = None, + # with_tasks_i_can_complete: Optional[bool] = None, + # with_tasks_assigned_to_my_group: Optional[bool] = None, + # with_relation_to_me: Optional[bool] = None, + # process_initiator_username: Optional[str] = None, + # report_column_list: Optional[list] = None, + # report_filter_by_list: Optional[list] = None, + # oldest_open_human_task_fields: Optional[list] = None, + # ) -> ProcessInstanceReportFilter: + # """Filter_from_metadata_with_overrides.""" + # report_filter = cls.filter_from_metadata(process_instance_report) + # + # if process_model_identifier is not None: + # report_filter.process_model_identifier = process_model_identifier + # if user_group_identifier is not None: + # report_filter.user_group_identifier = user_group_identifier + # if start_from is not None: + # report_filter.start_from = start_from + # if start_to is not None: + # report_filter.start_to = start_to + # if end_from is not None: + # report_filter.end_from = end_from + # if end_to is not None: + # report_filter.end_to = end_to + # if process_status is not None: + # report_filter.process_status = process_status.split(",") + # if initiated_by_me is not None: + # report_filter.initiated_by_me = initiated_by_me + # if has_terminal_status is not None: + # report_filter.has_terminal_status = has_terminal_status + # if has_active_status is not None: + # report_filter.has_active_status = has_active_status + # if with_tasks_completed_by_me is not None: + # report_filter.with_tasks_completed_by_me = with_tasks_completed_by_me + # if with_tasks_i_can_complete is not None: + # report_filter.with_tasks_i_can_complete = with_tasks_i_can_complete + # if process_initiator_username is not None: + # report_filter.process_initiator_username = process_initiator_username + # if report_column_list is not None: + # report_filter.report_column_list = report_column_list + # if report_filter_by_list is not None: + # report_filter.report_filter_by_list = report_filter_by_list + # if oldest_open_human_task_fields is not None: + # report_filter.oldest_open_human_task_fields = oldest_open_human_task_fields + # if with_tasks_assigned_to_my_group is not None: + # report_filter.with_tasks_assigned_to_my_group = with_tasks_assigned_to_my_group + # if with_relation_to_me is not None: + # report_filter.with_relation_to_me = with_relation_to_me + # + # return report_filter @classmethod def add_metadata_columns_to_process_instance( @@ -498,10 +498,16 @@ class ProcessInstanceReportService: ] @classmethod - def blah(cls, filters: list[FilterValue], filter_key: str) -> Generator: + def get_filter_value(cls, filters: list[FilterValue], filter_key: str) -> Any: for filter in filters: if filter['field_name'] == filter_key and filter['field_value'] is not None: - yield filter['field_value'] + return filter['field_value'] + + @classmethod + def check_filter_value(cls, filters: list[FilterValue], filter_key: str) -> Generator: + value = cls.get_filter_value(filters, filter_key) + if value is not None: + yield value @classmethod def run_process_instance_report( @@ -518,7 +524,7 @@ class ProcessInstanceReportService: process_instance_query = process_instance_query.options(selectinload(ProcessInstanceModel.process_initiator)) filters = report_metadata['filter_by'] - for value in cls.blah(filters, 'process_model_identifier'): + for value in cls.check_filter_value(filters, 'process_model_identifier'): process_model = ProcessModelService.get_process_model( f"{value}", ) @@ -534,183 +540,194 @@ class ProcessInstanceReportService: ) ) - # if report_filter.start_from is not None: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.start_in_seconds >= report_filter.start_from - # ) - # if report_filter.start_to is not None: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.start_in_seconds <= report_filter.start_to - # ) - # if report_filter.end_from is not None: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.end_in_seconds >= report_filter.end_from - # ) - # if report_filter.end_to is not None: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.end_in_seconds <= report_filter.end_to - # ) - # if report_filter.process_status is not None: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.status.in_(report_filter.process_status) # type: ignore - # ) - # - # if report_filter.initiated_by_me is True: - # process_instance_query = process_instance_query.filter_by(process_initiator=user) - # - # if report_filter.has_terminal_status is True: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore - # ) - # elif report_filter.has_terminal_status is False: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.status.not_in(ProcessInstanceModel.terminal_statuses()) # type: ignore - # ) - # if report_filter.has_active_status is True: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.status.in_(ProcessInstanceModel.active_statuses()) # type: ignore - # ) - # - # if report_filter.process_initiator_username is not None: - # initiator = UserModel.query.filter_by(username=report_filter.process_initiator_username).first() - # process_initiator_id = -1 - # if initiator: - # process_initiator_id = initiator.id - # process_instance_query = process_instance_query.filter_by(process_initiator_id=process_initiator_id) - # - # if ( - # not report_filter.with_tasks_completed_by_me - # and not report_filter.with_tasks_assigned_to_my_group - # and not report_filter.with_tasks_i_can_complete - # and report_filter.with_relation_to_me is True - # ): - # process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin( - # HumanTaskUserModel, - # and_( - # HumanTaskModel.id == HumanTaskUserModel.human_task_id, - # HumanTaskUserModel.user_id == user.id, - # ), - # ) - # process_instance_query = process_instance_query.filter( - # or_( - # HumanTaskUserModel.id.is_not(None), - # ProcessInstanceModel.process_initiator_id == user.id, - # ) - # ) - # - # if report_filter.with_tasks_completed_by_me is True: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.process_initiator_id != user.id - # ) - # process_instance_query = process_instance_query.join( - # HumanTaskModel, - # and_( - # HumanTaskModel.process_instance_id == ProcessInstanceModel.id, - # HumanTaskModel.completed_by_user_id == user.id, - # ), - # ) - # - # if report_filter.with_tasks_i_can_complete is True: - # process_instance_query = process_instance_query.filter( - # ProcessInstanceModel.process_initiator_id != user.id - # ) - # process_instance_query = process_instance_query.join( - # HumanTaskModel, - # and_( - # HumanTaskModel.process_instance_id == ProcessInstanceModel.id, - # HumanTaskModel.lane_assignment_id.is_(None), # type: ignore - # ), - # ).join( - # HumanTaskUserModel, - # and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id), - # ) - # if report_filter.has_active_status: - # process_instance_query = process_instance_query.filter( - # HumanTaskModel.completed.is_(False) # type: ignore - # ) - # - # if report_filter.with_tasks_assigned_to_my_group is True: - # group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id] - # if report_filter.user_group_identifier: - # group_model_join_conditions.append(GroupModel.identifier == report_filter.user_group_identifier) - # - # process_instance_query = process_instance_query.join(HumanTaskModel) - # if report_filter.has_active_status: - # process_instance_query = process_instance_query.filter( - # HumanTaskModel.completed.is_(False) # type: ignore - # ) - # - # process_instance_query = process_instance_query.join(GroupModel, and_(*group_model_join_conditions)) - # process_instance_query = process_instance_query.join( - # UserGroupAssignmentModel, - # UserGroupAssignmentModel.group_id == GroupModel.id, - # ) - # process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id) - # - # instance_metadata_aliases = {} - # stock_columns = cls.get_column_names_for_model(ProcessInstanceModel) - # if isinstance(report_filter.report_column_list, list): - # process_instance_report.report_metadata["columns"] = report_filter.report_column_list - # if isinstance(report_filter.report_filter_by_list, list): - # process_instance_report.report_metadata["filter_by"] = report_filter.report_filter_by_list - # - # for column in process_instance_report.report_metadata["columns"]: - # if column["accessor"] in stock_columns: - # continue - # instance_metadata_alias = aliased(ProcessInstanceMetadataModel) - # instance_metadata_aliases[column["accessor"]] = instance_metadata_alias - # - # filter_for_column = None - # if "filter_by" in process_instance_report.report_metadata: - # filter_for_column = next( - # ( - # f - # for f in process_instance_report.report_metadata["filter_by"] - # if f["field_name"] == column["accessor"] - # ), - # None, - # ) - # isouter = True - # conditions = [ - # ProcessInstanceModel.id == instance_metadata_alias.process_instance_id, - # instance_metadata_alias.key == column["accessor"], - # ] - # if filter_for_column: - # isouter = False - # conditions.append(instance_metadata_alias.value == filter_for_column["field_value"]) - # process_instance_query = process_instance_query.join( - # instance_metadata_alias, and_(*conditions), isouter=isouter - # ).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"])) - # - # order_by_query_array = [] - # order_by_array = process_instance_report.report_metadata["order_by"] - # if len(order_by_array) < 1: - # order_by_array = ProcessInstanceReportModel.default_order_by() - # for order_by_option in order_by_array: - # attribute = re.sub("^-", "", order_by_option) - # if attribute in stock_columns: - # if order_by_option.startswith("-"): - # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc()) - # else: - # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc()) - # elif attribute in instance_metadata_aliases: - # if order_by_option.startswith("-"): - # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc()) - # else: - # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc()) + for value in cls.check_filter_value(filters, 'start_from'): + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.start_in_seconds >= value + ) + for value in cls.check_filter_value(filters, 'start_to'): + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.start_in_seconds <= value + ) + for value in cls.check_filter_value(filters, 'end_from'): + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.end_in_seconds >= value + ) + for value in cls.check_filter_value(filters, 'end_to'): + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.end_in_seconds <= value + ) + for value in cls.check_filter_value(filters, 'process_status'): + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.status.in_(value.split(',')) # type: ignore + ) + + for value in cls.check_filter_value(filters, 'initiated_by_me'): + if value is True: + process_instance_query = process_instance_query.filter_by(process_initiator=user) + + for value in cls.check_filter_value(filters, 'has_terminal_status'): + if value is True: + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore + ) + elif value is False: + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.status.not_in(ProcessInstanceModel.terminal_statuses()) # type: ignore + ) + for value in cls.check_filter_value(filters, 'has_active_status'): + if value is True: + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.status.in_(ProcessInstanceModel.active_statuses()) # type: ignore + ) + + for value in cls.check_filter_value(filters, 'process_initiator_username'): + initiator = UserModel.query.filter_by(username=value).first() + process_initiator_id = -1 + if initiator: + process_initiator_id = initiator.id + process_instance_query = process_instance_query.filter_by(process_initiator_id=process_initiator_id) + + with_tasks_completed_by_me = cls.get_filter_value(filters, 'with_tasks_completed_by_me') + with_tasks_assigned_to_my_group = cls.get_filter_value(filters, 'with_tasks_assigned_to_my_group') + with_tasks_i_can_complete = cls.get_filter_value(filters, 'with_tasks_i_can_complete') + with_relation_to_me = cls.get_filter_value(filters, 'with_relation_to_me') + has_active_status = cls.get_filter_value(filters, 'has_active_status') + user_group_identifier = cls.get_filter_value(filters, 'user_group_identifier') + if ( + not with_tasks_completed_by_me + and not with_tasks_assigned_to_my_group + and not with_tasks_i_can_complete + and with_relation_to_me is True + ): + process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin( + HumanTaskUserModel, + and_( + HumanTaskModel.id == HumanTaskUserModel.human_task_id, + HumanTaskUserModel.user_id == user.id, + ), + ) + process_instance_query = process_instance_query.filter( + or_( + HumanTaskUserModel.id.is_not(None), + ProcessInstanceModel.process_initiator_id == user.id, + ) + ) + + if with_tasks_completed_by_me is True: + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.process_initiator_id != user.id + ) + process_instance_query = process_instance_query.join( + HumanTaskModel, + and_( + HumanTaskModel.process_instance_id == ProcessInstanceModel.id, + HumanTaskModel.completed_by_user_id == user.id, + ), + ) + + if with_tasks_i_can_complete is True: + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.process_initiator_id != user.id + ) + process_instance_query = process_instance_query.join( + HumanTaskModel, + and_( + HumanTaskModel.process_instance_id == ProcessInstanceModel.id, + HumanTaskModel.lane_assignment_id.is_(None), # type: ignore + ), + ).join( + HumanTaskUserModel, + and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id), + ) + if has_active_status: + process_instance_query = process_instance_query.filter( + HumanTaskModel.completed.is_(False) # type: ignore + ) + + if with_tasks_assigned_to_my_group is True: + group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id] + if user_group_identifier: + group_model_join_conditions.append(GroupModel.identifier == user_group_identifier) + + process_instance_query = process_instance_query.join(HumanTaskModel) + if has_active_status: + process_instance_query = process_instance_query.filter( + HumanTaskModel.completed.is_(False) # type: ignore + ) + + process_instance_query = process_instance_query.join(GroupModel, and_(*group_model_join_conditions)) + process_instance_query = process_instance_query.join( + UserGroupAssignmentModel, + UserGroupAssignmentModel.group_id == GroupModel.id, + ) + process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id) + + instance_metadata_aliases = {} + report_column_list = cls.get_filter_value(filters, 'report_column_list') + report_filter_by_list = cls.get_filter_value(filters, 'report_filter_by_list') + stock_columns = cls.get_column_names_for_model(ProcessInstanceModel) + if isinstance(report_column_list, list): + process_instance_report.report_metadata["columns"] = report_column_list + if isinstance(report_filter_by_list, list): + process_instance_report.report_metadata["filter_by"] = report_filter_by_list + + for column in process_instance_report.report_metadata["columns"]: + if column["accessor"] in stock_columns: + continue + instance_metadata_alias = aliased(ProcessInstanceMetadataModel) + instance_metadata_aliases[column["accessor"]] = instance_metadata_alias + + filter_for_column = None + if "filter_by" in process_instance_report.report_metadata: + filter_for_column = next( + ( + f + for f in process_instance_report.report_metadata["filter_by"] + if f["field_name"] == column["accessor"] + ), + None, + ) + isouter = True + conditions = [ + ProcessInstanceModel.id == instance_metadata_alias.process_instance_id, + instance_metadata_alias.key == column["accessor"], + ] + if filter_for_column: + isouter = False + conditions.append(instance_metadata_alias.value == filter_for_column["field_value"]) + process_instance_query = process_instance_query.join( + instance_metadata_alias, and_(*conditions), isouter=isouter + ).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"])) + + order_by_query_array = [] + order_by_array = process_instance_report.report_metadata["order_by"] + if len(order_by_array) < 1: + order_by_array = ProcessInstanceReportModel.default_order_by() + for order_by_option in order_by_array: + attribute = re.sub("^-", "", order_by_option) + if attribute in stock_columns: + if order_by_option.startswith("-"): + order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc()) + else: + order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc()) + elif attribute in instance_metadata_aliases: + if order_by_option.startswith("-"): + order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc()) + else: + order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc()) process_instances = ( process_instance_query.group_by(ProcessInstanceModel.id) .add_columns(ProcessInstanceModel.id) - # .order_by(*order_by_query_array) + .order_by(*order_by_query_array) .paginate(page=page, per_page=per_page, error_out=False) ) results = cls.add_metadata_columns_to_process_instance( process_instances.items, process_instance_report.report_metadata["columns"] ) - # if report_filter.oldest_open_human_task_fields: - # results = cls.add_human_task_fields(results, report_filter.oldest_open_human_task_fields) + for value in cls.check_filter_value(filters, 'oldest_open_human_task_fields'): + results = cls.add_human_task_fields(results, value) response_json = { "report": process_instance_report, "results": results, diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 112957f6..083ce875 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -37,6 +37,7 @@ import { convertSecondsToFormattedDateString, convertSecondsToFormattedDateTime, convertSecondsToFormattedTimeHoursMinutes, + decodeBase64, encodeBase64, getPageInfoFromSearchParams, getProcessModelFullIdentifierFromSearchParams, @@ -287,10 +288,16 @@ export default function ProcessInstanceListTable({ // searchParams.set('key', result.hash); // setSearchParams(searchParams); } - const stopRefreshing = () => { + + // Useful to stop refreshing if an api call gets an error + // since those errors can make the page unusable in any way + const stopRefreshing = (error: any) => { if (clearRefreshRef.current) { clearRefreshRef.current(); } + if (error) { + console.error(error); + } }; function getProcessInstances() { // eslint-disable-next-line prefer-const @@ -306,10 +313,31 @@ export default function ProcessInstanceListTable({ } let queryParamString = `per_page=${perPage}&page=${page}`; - const userAppliedFilter = searchParams.get('user_filter'); - if (userAppliedFilter) { - queryParamString += `&user_filter=${userAppliedFilter}`; - } + // "columns": [ + // {"Header": "id", "accessor": "id"}, + // { + // "Header": "process_model_display_name", + // "accessor": "process_model_display_name", + // }, + // {"Header": "start_in_seconds", "accessor": "start_in_seconds"}, + // {"Header": "end_in_seconds", "accessor": "end_in_seconds"}, + // {"Header": "status", "accessor": "status"}, + // ], + // "filter_by": [ + // {"field_name": "initiated_by_me", "field_value": "true"}, + // {"field_name": "has_terminal_status", "field_value": "true"}, + // ], + // "order_by": ["-start_in_seconds", "-id"], + const postBody: ReportMetadata = { + columns: [], + filter_by: [], + order_by: [], + }; + + // const userAppliedFilter = searchParams.get('user_filter'); + // if (userAppliedFilter) { + // queryParamString += `&user_filter=${userAppliedFilter}`; + // } if (searchParams.get('report_id')) { queryParamString += `&report_id=${searchParams.get('report_id')}`; @@ -318,14 +346,29 @@ export default function ProcessInstanceListTable({ } if (searchParams.get('report_columns')) { - queryParamString += `&report_columns=${searchParams.get( - 'report_columns' - )}`; + // queryParamString += `&report_columns=${searchParams.get( + // 'report_columns' + // )}`; + // const reportColumnsBase64 = encodeBase64(JSON.stringify(reportColumns())); + const reportColumnsBase64 = searchParams.get('report_columns'); + if (reportColumnsBase64) { + const reportColumnsList = JSON.parse( + decodeBase64(reportColumnsBase64) + ); + postBody.columns = reportColumnsList; + } } if (searchParams.get('report_filter_by')) { - queryParamString += `&report_filter_by=${searchParams.get( - 'report_filter_by' - )}`; + // queryParamString += `&report_filter_by=${searchParams.get( + // 'report_filter_by' + // )}`; + const reportFilterByBase64 = searchParams.get('report_filter_by'); + if (reportFilterByBase64) { + const reportFilterByList = JSON.parse( + decodeBase64(reportFilterByBase64) + ); + postBody.filter_by = reportFilterByList; + } } Object.keys(dateParametersToAlwaysFilterBy).forEach( @@ -336,7 +379,11 @@ export default function ProcessInstanceListTable({ dateParametersToAlwaysFilterBy[paramName][1]; const searchParamValue = searchParams.get(paramName); if (searchParamValue) { - queryParamString += `&${paramName}=${searchParamValue}`; + // queryParamString += `&${paramName}=${searchParamValue}`; + postBody.filter_by.push({ + field_name: paramName, + field_value: searchParamValue, + }); const dateString = convertSecondsToFormattedDateString( searchParamValue as any ); @@ -356,11 +403,19 @@ export default function ProcessInstanceListTable({ paramName === 'process_model_identifier' && processModelFullIdentifier ) { - queryParamString += `&process_model_identifier=${processModelFullIdentifier}`; + // queryParamString += `&process_model_identifier=${processModelFullIdentifier}`; + postBody.filter_by.push({ + field_name: 'process_model_identifier', + field_value: processModelFullIdentifier, + }); } else if (searchParams.get(paramName)) { // @ts-expect-error TS(7053) FIXME: const functionToCall = parametersToGetFromSearchParams[paramName]; - queryParamString += `&${paramName}=${searchParams.get(paramName)}`; + // queryParamString += `&${paramName}=${searchParams.get(paramName)}`; + postBody.filter_by.push({ + field_name: paramName, + field_value: searchParams.get(paramName), + }); if (functionToCall !== null) { functionToCall(searchParams.get(paramName) || ''); } @@ -377,8 +432,19 @@ export default function ProcessInstanceListTable({ path: `${processInstanceApiSearchPath}?${queryParamString}`, successCallback: setProcessInstancesFromResult, httpMethod: 'POST', + failureCallback: stopRefreshing, onUnauthorized: stopRefreshing, - postBody: {'report_metadata': {'filter_by': [{'field_name': 'process_model_identifier', 'field_value': 'example/with-milestones' }]}}, + postBody: { + report_metadata: postBody, + // report_metadata: { + // filter_by: [ + // { + // field_name: 'process_model_identifier', + // field_value: 'example/with-milestones', + // }, + // ], + // }, + }, }); } function processResultForProcessModels(result: any) { @@ -618,7 +684,8 @@ export default function ProcessInstanceListTable({ undefined, paginationQueryParamPrefix ); - let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`; + // let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`; + let queryParamString = `per_page=${perPage}&page=${page}`; const { valid, startFromSeconds, @@ -631,6 +698,24 @@ export default function ProcessInstanceListTable({ return; } + // "columns": [ + // {"Header": "id", "accessor": "id"}, + // { + // "Header": "process_model_display_name", + // "accessor": "process_model_display_name", + // }, + // {"Header": "start_in_seconds", "accessor": "start_in_seconds"}, + // {"Header": "end_in_seconds", "accessor": "end_in_seconds"}, + // {"Header": "status", "accessor": "status"}, + // ], + // "filter_by": [ + // {"field_name": "initiated_by_me", "field_value": "true"}, + // {"field_name": "has_terminal_status", "field_value": "true"}, + // ], + // "order_by": ["-start_in_seconds", "-id"], + + // const postBody = {}; + if (startFromSeconds) { queryParamString += `&start_from=${startFromSeconds}`; } @@ -947,7 +1032,8 @@ export default function ProcessInstanceListTable({ reportColumnForEditing.accessor ); if (reportFilter) { - reportColumnForEditing.filter_field_value = reportFilter.field_value; + reportColumnForEditing.filter_field_value = + reportFilter.field_value || ''; reportColumnForEditing.filter_operator = reportFilter.operator || 'equals'; } diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index aa0579e9..b3e6bcb2 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -163,7 +163,7 @@ export interface MessageInstance { export interface ReportFilter { field_name: string; - field_value: string; + field_value: string | null; operator?: string; }