Merge remote-tracking branch 'origin/main' into bug/boudary_event_suspend_manual_complete

This commit is contained in:
Dan 2023-05-03 17:08:44 -04:00
commit 5458d59f30
10 changed files with 201 additions and 74 deletions

View File

@ -0,0 +1,10 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-PYTHON-FLASK-5490129:
- '*':
reason: Filed ticket to upgrade flask
expires: 2024-06-02T14:48:14.372Z
created: 2023-05-03T14:48:14.379Z
patch: {}

View File

@ -0,0 +1,41 @@
#!/usr/bin/env bash
function error_handler() {
>&2 echo "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}."
exit "$2"
}
trap 'error_handler ${LINENO} $?' ERR
set -o errtrace -o errexit -o nounset -o pipefail
script_dir="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
if [[ -z "${KEYCLOAK_BASE_URL:-}" ]]; then
export KEYCLOAK_BASE_URL=http://localhost:7002
fi
if [[ -z "${BACKEND_BASE_URL:-}" ]]; then
export BACKEND_BASE_URL=http://localhost:7000
fi
message_identifier="${1:-start_test}"
username="${2:-admin}"
password="${3:-admin}"
realm_name="${4:-spiffworkflow}"
if [[ -z "${message_identifier}" ]]; then
>&2 echo "usage: $(basename "$0") [message_identifier] [username: OPTONAL] [password: OPTONAL] [realm_name: OPTONAL]"
exit 1
fi
function check_result_for_error() {
local result="$1"
error_code=$(jq '.error_code' <<<"$result")
if [[ -n "$error_code" && "$error_code" != "null" ]]; then
>&2 echo "ERROR: Failed to run process instance. Received error: $result"
exit 1
fi
}
access_token=$("${script_dir}/get_token" "$username" "$password" "$realm_name")
curl --silent -X POST "${BACKEND_BASE_URL}/v1.0/login_with_access_token?access_token=${access_token}" -H "Authorization: Bearer $access_token" >/dev/null
result=$(curl --silent -X POST "${BACKEND_BASE_URL}/v1.0/messages/${message_identifier}" -H "Authorization: Bearer $access_token" -d '{"payload": {"email": "HEY@example.com"}}' -H 'Content-type: application/json')
check_result_for_error "$result"
echo "$result"

View File

@ -18,13 +18,13 @@ def setup_database_uri(app: Flask) -> None:
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None: if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None:
database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}" database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}"
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite": if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite":
app.config[ app.config["SQLALCHEMY_DATABASE_URI"] = (
"SQLALCHEMY_DATABASE_URI" f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3"
] = f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" )
elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres": elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres":
app.config[ app.config["SQLALCHEMY_DATABASE_URI"] = (
"SQLALCHEMY_DATABASE_URI" f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}"
] = f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" )
else: else:
# use pswd to trick flake8 with hardcoded passwords # use pswd to trick flake8 with hardcoded passwords
db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD") db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD")

View File

@ -129,9 +129,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
def serialized_with_metadata(self) -> dict[str, Any]: def serialized_with_metadata(self) -> dict[str, Any]:
process_instance_attributes = self.serialized process_instance_attributes = self.serialized
process_instance_attributes["process_metadata"] = self.process_metadata process_instance_attributes["process_metadata"] = self.process_metadata
process_instance_attributes[ process_instance_attributes["process_model_with_diagram_identifier"] = (
"process_model_with_diagram_identifier" self.process_model_with_diagram_identifier
] = self.process_model_with_diagram_identifier )
return process_instance_attributes return process_instance_attributes
@property @property

View File

@ -69,7 +69,6 @@ def message_send(
message_name: str, message_name: str,
body: Dict[str, Any], body: Dict[str, Any],
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Message_start."""
if "payload" not in body: if "payload" not in body:
raise ( raise (
ApiError( ApiError(

View File

@ -423,9 +423,9 @@ class ProcessInstanceProcessor:
tld.process_instance_id = process_instance_model.id tld.process_instance_id = process_instance_model.id
# we want this to be the fully qualified path to the process model including all group subcomponents # we want this to be the fully qualified path to the process model including all group subcomponents
current_app.config[ current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = (
"THREAD_LOCAL_DATA" f"{process_instance_model.process_model_identifier}"
].process_model_identifier = f"{process_instance_model.process_model_identifier}" )
self.process_instance_model = process_instance_model self.process_instance_model = process_instance_model
self.process_model_service = ProcessModelService() self.process_model_service = ProcessModelService()
@ -585,9 +585,9 @@ class ProcessInstanceProcessor:
bpmn_subprocess_definition.bpmn_identifier bpmn_subprocess_definition.bpmn_identifier
] = bpmn_process_definition_dict ] = bpmn_process_definition_dict
spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {} spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {}
bpmn_subprocess_definition_bpmn_identifiers[ bpmn_subprocess_definition_bpmn_identifiers[bpmn_subprocess_definition.id] = (
bpmn_subprocess_definition.id bpmn_subprocess_definition.bpmn_identifier
] = bpmn_subprocess_definition.bpmn_identifier )
task_definitions = TaskDefinitionModel.query.filter( task_definitions = TaskDefinitionModel.query.filter(
TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore

View File

@ -34,7 +34,11 @@ from spiffworkflow_backend.services.process_model_service import ProcessModelSer
class ProcessInstanceReportNotFoundError(Exception): class ProcessInstanceReportNotFoundError(Exception):
"""ProcessInstanceReportNotFoundError.""" pass
class ProcessInstanceReportMetadataInvalidError(Exception):
pass
class ProcessInstanceReportService: class ProcessInstanceReportService:
@ -53,15 +57,15 @@ class ProcessInstanceReportService:
} }
system_report_completed_instances_initiated_by_me: ReportMetadata = { system_report_completed_instances_initiated_by_me: ReportMetadata = {
"columns": [ "columns": [
{"Header": "id", "accessor": "id", "filterable": False}, {"Header": "Id", "accessor": "id", "filterable": False},
{ {
"Header": "process_model_display_name", "Header": "Process",
"accessor": "process_model_display_name", "accessor": "process_model_display_name",
"filterable": False, "filterable": False,
}, },
{"Header": "start_in_seconds", "accessor": "start_in_seconds", "filterable": False}, {"Header": "Start Time", "accessor": "start_in_seconds", "filterable": False},
{"Header": "end_in_seconds", "accessor": "end_in_seconds", "filterable": False}, {"Header": "End Time", "accessor": "end_in_seconds", "filterable": False},
{"Header": "status", "accessor": "status", "filterable": False}, {"Header": "Status", "accessor": "status", "filterable": False},
], ],
"filter_by": [ "filter_by": [
{"field_name": "initiated_by_me", "field_value": True, "operator": "equals"}, {"field_name": "initiated_by_me", "field_value": True, "operator": "equals"},
@ -72,7 +76,7 @@ class ProcessInstanceReportService:
system_report_completed_instances_with_tasks_completed_by_me: ReportMetadata = { system_report_completed_instances_with_tasks_completed_by_me: ReportMetadata = {
"columns": cls.builtin_column_options(), "columns": cls.builtin_column_options(),
"filter_by": [ "filter_by": [
{"field_name": "with_tasks_completed_by_me", "field_value": True, "operator": "equals"}, {"field_name": "instances_with_tasks_completed_by_me", "field_value": True, "operator": "equals"},
{"field_name": "process_status", "field_value": terminal_status_values, "operator": "equals"}, {"field_name": "process_status", "field_value": terminal_status_values, "operator": "equals"},
], ],
"order_by": ["-start_in_seconds", "-id"], "order_by": ["-start_in_seconds", "-id"],
@ -86,9 +90,9 @@ class ProcessInstanceReportService:
} }
system_report_in_progress_instances_initiated_by_me: ReportMetadata = { system_report_in_progress_instances_initiated_by_me: ReportMetadata = {
"columns": [ "columns": [
{"Header": "id", "accessor": "id", "filterable": False}, {"Header": "Id", "accessor": "id", "filterable": False},
{ {
"Header": "process_model_display_name", "Header": "Process",
"accessor": "process_model_display_name", "accessor": "process_model_display_name",
"filterable": False, "filterable": False,
}, },
@ -96,7 +100,7 @@ class ProcessInstanceReportService:
{"Header": "Waiting For", "accessor": "waiting_for", "filterable": False}, {"Header": "Waiting For", "accessor": "waiting_for", "filterable": False},
{"Header": "Started", "accessor": "start_in_seconds", "filterable": False}, {"Header": "Started", "accessor": "start_in_seconds", "filterable": False},
{"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False}, {"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
{"Header": "status", "accessor": "status", "filterable": False}, {"Header": "Status", "accessor": "status", "filterable": False},
], ],
"filter_by": [ "filter_by": [
{"field_name": "initiated_by_me", "field_value": True, "operator": "equals"}, {"field_name": "initiated_by_me", "field_value": True, "operator": "equals"},
@ -111,9 +115,9 @@ class ProcessInstanceReportService:
} }
system_report_in_progress_instances_with_tasks_for_me: ReportMetadata = { system_report_in_progress_instances_with_tasks_for_me: ReportMetadata = {
"columns": [ "columns": [
{"Header": "id", "accessor": "id", "filterable": False}, {"Header": "Id", "accessor": "id", "filterable": False},
{ {
"Header": "process_model_display_name", "Header": "Process",
"accessor": "process_model_display_name", "accessor": "process_model_display_name",
"filterable": False, "filterable": False,
}, },
@ -123,7 +127,7 @@ class ProcessInstanceReportService:
{"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False}, {"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
], ],
"filter_by": [ "filter_by": [
{"field_name": "with_tasks_i_can_complete", "field_value": True, "operator": "equals"}, {"field_name": "instances_with_tasks_waiting_for_me", "field_value": True, "operator": "equals"},
{"field_name": "process_status", "field_value": active_status_values, "operator": "equals"}, {"field_name": "process_status", "field_value": active_status_values, "operator": "equals"},
{ {
"field_name": "with_oldest_open_task", "field_name": "with_oldest_open_task",
@ -135,9 +139,9 @@ class ProcessInstanceReportService:
} }
system_report_in_progress_instances_with_tasks: ReportMetadata = { system_report_in_progress_instances_with_tasks: ReportMetadata = {
"columns": [ "columns": [
{"Header": "id", "accessor": "id", "filterable": False}, {"Header": "Id", "accessor": "id", "filterable": False},
{ {
"Header": "process_model_display_name", "Header": "Process",
"accessor": "process_model_display_name", "accessor": "process_model_display_name",
"filterable": False, "filterable": False,
}, },
@ -414,17 +418,17 @@ class ProcessInstanceReportService:
process_initiator_id = initiator.id process_initiator_id = initiator.id
process_instance_query = process_instance_query.filter_by(process_initiator_id=process_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") instances_with_tasks_completed_by_me = cls.get_filter_value(filters, "instances_with_tasks_completed_by_me")
with_tasks_i_can_complete = cls.get_filter_value(filters, "with_tasks_i_can_complete") instances_with_tasks_waiting_for_me = cls.get_filter_value(filters, "instances_with_tasks_waiting_for_me")
user_group_identifier = cls.get_filter_value(filters, "user_group_identifier") user_group_identifier = cls.get_filter_value(filters, "user_group_identifier")
# builtin only - for the for-me paths # builtin only - for the for-me paths
with_relation_to_me = cls.get_filter_value(filters, "with_relation_to_me") with_relation_to_me = cls.get_filter_value(filters, "with_relation_to_me")
if ( if (
not with_tasks_completed_by_me not instances_with_tasks_completed_by_me
and not user_group_identifier and not user_group_identifier
and not with_tasks_i_can_complete and not instances_with_tasks_waiting_for_me
and with_relation_to_me is True and with_relation_to_me is True
): ):
process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin( process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
@ -441,7 +445,16 @@ class ProcessInstanceReportService:
) )
) )
if with_tasks_completed_by_me is True: if instances_with_tasks_completed_by_me is True and instances_with_tasks_waiting_for_me is True:
raise ProcessInstanceReportMetadataInvalidError(
"Cannot set both 'instances_with_tasks_completed_by_me' and 'instances_with_tasks_waiting_for_me' to"
" true. You must choose one."
)
# ensure we only join with HumanTaskModel once
human_task_already_joined = False
if instances_with_tasks_completed_by_me is True:
process_instance_query = process_instance_query.filter( process_instance_query = process_instance_query.filter(
ProcessInstanceModel.process_initiator_id != user.id ProcessInstanceModel.process_initiator_id != user.id
) )
@ -452,10 +465,11 @@ class ProcessInstanceReportService:
HumanTaskModel.completed_by_user_id == user.id, HumanTaskModel.completed_by_user_id == user.id,
), ),
) )
human_task_already_joined = True
# this excludes some tasks you can complete, because that's the way the requirements were described. # this excludes some tasks you can complete, because that's the way the requirements were described.
# if it's assigned to one of your groups, it does not get returned by this query. # if it's assigned to one of your groups, it does not get returned by this query.
if with_tasks_i_can_complete is True: if instances_with_tasks_waiting_for_me is True:
process_instance_query = process_instance_query.filter( process_instance_query = process_instance_query.filter(
ProcessInstanceModel.process_initiator_id != user.id ProcessInstanceModel.process_initiator_id != user.id
) )
@ -470,13 +484,15 @@ class ProcessInstanceReportService:
HumanTaskUserModel, HumanTaskUserModel,
and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id), and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id),
) )
human_task_already_joined = True
if user_group_identifier is not None: if user_group_identifier is not None:
group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id] group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
if user_group_identifier: if user_group_identifier:
group_model_join_conditions.append(GroupModel.identifier == user_group_identifier) group_model_join_conditions.append(GroupModel.identifier == user_group_identifier)
process_instance_query = process_instance_query.join(HumanTaskModel) if human_task_already_joined is False:
process_instance_query = process_instance_query.join(HumanTaskModel)
if process_status is not None: if process_status is not None:
non_active_statuses = [ non_active_statuses = [
s for s in process_status.split(",") if s not in ProcessInstanceModel.active_statuses() s for s in process_status.split(",") if s not in ProcessInstanceModel.active_statuses()

View File

@ -1,4 +1,5 @@
"""Test_process_instance_report_service.""" """Test_process_instance_report_service."""
import pytest
from flask import Flask from flask import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
@ -7,9 +8,9 @@ from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.human_task import HumanTaskModel from spiffworkflow_backend.models.human_task import HumanTaskModel
from spiffworkflow_backend.services.process_instance_report_service import ( from spiffworkflow_backend.models.process_instance_report import ReportMetadata
ProcessInstanceReportService, from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportMetadataInvalidError
) from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportService
from spiffworkflow_backend.services.user_service import UserService from spiffworkflow_backend.services.user_service import UserService
@ -52,6 +53,62 @@ class TestProcessInstanceReportService(BaseTest):
assert response_json["results"][0]["status"] == "complete" assert response_json["results"][0]["status"] == "complete"
assert response_json["results"][1]["status"] == "complete" assert response_json["results"][1]["status"] == "complete"
def test_raises_if_filtering_with_both_task_i_can_complete_and_tasks_completed_by_me(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
) -> None:
user_one = self.find_or_create_user(username="user_one")
report_metadata: ReportMetadata = {
"columns": [],
"filter_by": [
{"field_name": "instances_with_tasks_waiting_for_me", "field_value": True, "operator": "equals"},
{"field_name": "instances_with_tasks_completed_by_me", "field_value": True, "operator": "equals"},
],
"order_by": [],
}
with pytest.raises(ProcessInstanceReportMetadataInvalidError):
ProcessInstanceReportService.run_process_instance_report(
report_metadata=report_metadata,
user=user_one,
)
def test_with_group_identifier_does_not_conflict_with_system_filters(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
) -> None:
user_one = self.find_or_create_user(username="user_one")
report_metadata: ReportMetadata = {
"columns": [],
"filter_by": [
{"field_name": "instances_with_tasks_waiting_for_me", "field_value": True, "operator": "equals"},
{"field_name": "user_group_identifier", "field_value": "group_one", "operator": "equals"},
],
"order_by": [],
}
result = ProcessInstanceReportService.run_process_instance_report(
report_metadata=report_metadata,
user=user_one,
)
assert result is not None
report_metadata = {
"columns": [],
"filter_by": [
{"field_name": "instances_with_tasks_completed_by_me", "field_value": True, "operator": "equals"},
{"field_name": "user_group_identifier", "field_value": "group_one", "operator": "equals"},
],
"order_by": [],
}
result = ProcessInstanceReportService.run_process_instance_report(
report_metadata=report_metadata,
user=user_one,
)
assert result is not None
def test_can_filter_by_completed_instances_with_tasks_completed_by_me( def test_can_filter_by_completed_instances_with_tasks_completed_by_me(
self, self,
app: Flask, app: Flask,

View File

@ -138,7 +138,9 @@ export default function ProcessInstanceListTable({
); );
const canSearchUsers: boolean = ability.can('GET', targetUris.userSearch); const canSearchUsers: boolean = ability.can('GET', targetUris.userSearch);
const [processInstances, setProcessInstances] = useState([]); const [processInstances, setProcessInstances] = useState<ProcessInstance[]>(
[]
);
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>(); const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
const [pagination, setPagination] = useState<PaginationObject | null>(null); const [pagination, setPagination] = useState<PaginationObject | null>(null);
@ -215,7 +217,10 @@ export default function ProcessInstanceListTable({
); );
const [userGroups, setUserGroups] = useState<string[]>([]); const [userGroups, setUserGroups] = useState<string[]>([]);
const systemReportOptions: string[] = useMemo(() => { const systemReportOptions: string[] = useMemo(() => {
return ['with_tasks_i_can_complete', 'with_tasks_completed_by_me']; return [
'instances_with_tasks_waiting_for_me',
'instances_with_tasks_completed_by_me',
];
}, []); }, []);
const [reportHash, setReportHash] = useState<string | null>(null); const [reportHash, setReportHash] = useState<string | null>(null);
@ -846,6 +851,9 @@ export default function ProcessInstanceListTable({
setEndToDate(''); setEndToDate('');
setEndToTime(''); setEndToTime('');
setProcessInitiatorSelection(null); setProcessInitiatorSelection(null);
setWithOldestOpenTask(false);
setSystemReport(null);
setSelectedUserGroup(null);
setRequiresRefilter(true); setRequiresRefilter(true);
if (reportMetadata) { if (reportMetadata) {
reportMetadata.filter_by = []; reportMetadata.filter_by = [];
@ -1213,9 +1221,9 @@ export default function ProcessInstanceListTable({
<Column md={4} lg={8} sm={2}> <Column md={4} lg={8} sm={2}>
<Dropdown <Dropdown
id="system-report-dropdown" id="system-report-dropdown"
titleText="System Report" titleText="System report"
items={['', ...systemReportOptions]} items={['', ...systemReportOptions]}
itemToString={(item: any) => item} itemToString={(item: any) => titleizeString(item)}
selectedItem={systemReport} selectedItem={systemReport}
onChange={(value: any) => { onChange={(value: any) => {
setSystemReport(value.selectedItem); setSystemReport(value.selectedItem);
@ -1226,7 +1234,7 @@ export default function ProcessInstanceListTable({
<Column md={4} lg={8} sm={2}> <Column md={4} lg={8} sm={2}>
<Dropdown <Dropdown
id="user-group-dropdown" id="user-group-dropdown"
titleText="User Group" titleText="Assigned user group"
items={['', ...userGroups]} items={['', ...userGroups]}
itemToString={(item: any) => item} itemToString={(item: any) => item}
selectedItem={selectedUserGroup} selectedItem={selectedUserGroup}
@ -1262,6 +1270,7 @@ export default function ProcessInstanceListTable({
onRequestSubmit={handleAdvancedOptionsClose} onRequestSubmit={handleAdvancedOptionsClose}
onRequestClose={handleAdvancedOptionsClose} onRequestClose={handleAdvancedOptionsClose}
hasScrollingContent hasScrollingContent
size="lg"
> >
{formElements} {formElements}
</Modal> </Modal>
@ -1336,7 +1345,7 @@ export default function ProcessInstanceListTable({
return null; return null;
}} }}
placeholder="Start typing username" placeholder="Start typing username"
titleText="Started By" titleText="Started by"
selectedItem={processInitiatorSelection} selectedItem={processInitiatorSelection}
/> />
); );
@ -1345,7 +1354,7 @@ export default function ProcessInstanceListTable({
<TextInput <TextInput
id="process-instance-initiator-search" id="process-instance-initiator-search"
placeholder="Enter username" placeholder="Enter username"
labelText="Started By" labelText="Started by"
invalid={processInitiatorNotFoundErrorText !== ''} invalid={processInitiatorNotFoundErrorText !== ''}
invalidText={processInitiatorNotFoundErrorText} invalidText={processInitiatorNotFoundErrorText}
onChange={(event: any) => { onChange={(event: any) => {
@ -1509,7 +1518,7 @@ export default function ProcessInstanceListTable({
return value; return value;
}; };
const formattedColumn = (row: any, column: any) => { const formattedColumn = (row: ProcessInstance, column: ReportColumn) => {
const reportColumnFormatters: Record<string, any> = { const reportColumnFormatters: Record<string, any> = {
id: formatProcessInstanceId, id: formatProcessInstanceId,
process_model_identifier: formatProcessModelIdentifier, process_model_identifier: formatProcessModelIdentifier,
@ -1520,40 +1529,41 @@ export default function ProcessInstanceListTable({
updated_at_in_seconds: formatSecondsForDisplay, updated_at_in_seconds: formatSecondsForDisplay,
task_updated_at_in_seconds: formatSecondsForDisplay, task_updated_at_in_seconds: formatSecondsForDisplay,
}; };
const columnAccessor = column.accessor as keyof ProcessInstance;
const formatter = const formatter =
reportColumnFormatters[column.accessor] ?? defaultFormatter; reportColumnFormatters[columnAccessor] ?? defaultFormatter;
const value = row[column.accessor]; const value = row[columnAccessor];
if (column.accessor === 'status') { if (columnAccessor === 'status') {
return ( return (
<td data-qa={`process-instance-status-${value}`}> <td data-qa={`process-instance-status-${value}`}>
{formatter(row, value)} {formatter(row, value)}
</td> </td>
); );
} }
if (column.accessor === 'process_model_display_name') { if (columnAccessor === 'process_model_display_name') {
return <td> {formatter(row, value)} </td>; return <td> {formatter(row, value)} </td>;
} }
if (column.accessor === 'waiting_for') { if (columnAccessor === 'waiting_for') {
return <td> {getWaitingForTableCellComponent(row)} </td>; return <td> {getWaitingForTableCellComponent(row)} </td>;
} }
if (column.accessor === 'updated_at_in_seconds') { if (columnAccessor === 'updated_at_in_seconds') {
return ( return (
<TableCellWithTimeAgoInWords <TableCellWithTimeAgoInWords
timeInSeconds={row.updated_at_in_seconds} timeInSeconds={row.updated_at_in_seconds}
/> />
); );
} }
if (column.accessor === 'task_updated_at_in_seconds') { if (columnAccessor === 'task_updated_at_in_seconds') {
return ( return (
<TableCellWithTimeAgoInWords <TableCellWithTimeAgoInWords
timeInSeconds={row.task_updated_at_in_seconds} timeInSeconds={row.task_updated_at_in_seconds || 0}
/> />
); );
} }
return ( return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<td data-qa={`process-instance-show-link-${column.accessor}`}> <td data-qa={`process-instance-show-link-${columnAccessor}`}>
{formatter(row, value)} {formatter(row, value)}
</td> </td>
); );
@ -1561,27 +1571,15 @@ export default function ProcessInstanceListTable({
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
const buildTable = () => { const buildTable = () => {
const headerLabels: Record<string, string> = { const headers = reportColumns().map((column: ReportColumn) => {
id: 'Id', return column.Header;
process_model_identifier: 'Process',
process_model_display_name: 'Process',
start_in_seconds: 'Start Time',
end_in_seconds: 'End Time',
status: 'Status',
process_initiator_username: 'Started By',
};
const getHeaderLabel = (header: string) => {
return headerLabels[header] ?? header;
};
const headers = reportColumns().map((column: any) => {
return getHeaderLabel((column as any).Header);
}); });
if (showActionsColumn) { if (showActionsColumn) {
headers.push('Action'); headers.push('Action');
} }
const rows = processInstances.map((row: any) => { const rows = processInstances.map((row: ProcessInstance) => {
const currentRow = reportColumns().map((column: any) => { const currentRow = reportColumns().map((column: ReportColumn) => {
return formattedColumn(row, column); return formattedColumn(row, column);
}); });
if (showActionsColumn) { if (showActionsColumn) {

View File

@ -143,6 +143,12 @@ export interface ProcessInstance {
bpmn_version_control_type: string; bpmn_version_control_type: string;
process_metadata?: ProcessInstanceMetadata[]; process_metadata?: ProcessInstanceMetadata[];
process_model_with_diagram_identifier?: string; process_model_with_diagram_identifier?: string;
// from tasks
potential_owner_usernames?: string;
task_id?: string;
task_updated_at_in_seconds?: number;
waiting_for?: string;
} }
export interface MessageCorrelationProperties { export interface MessageCorrelationProperties {