Merge remote-tracking branch 'origin/main' into feature/call-activity-references
This commit is contained in:
commit
1a11f5edba
|
@ -273,10 +273,10 @@ jobs:
|
||||||
nox --force-color --session=coverage -- xml
|
nox --force-color --session=coverage -- xml
|
||||||
|
|
||||||
- name: Upload coverage report
|
- name: Upload coverage report
|
||||||
uses: codecov/codecov-action@v3.1.0
|
uses: codecov/codecov-action@v3.1.3
|
||||||
|
|
||||||
- name: SonarCloud Scan
|
- name: SonarCloud Scan
|
||||||
uses: sonarsource/sonarcloud-github-action@master
|
uses: sonarsource/sonarcloud-github-action@v1.8
|
||||||
# thought about just skipping dependabot
|
# thought about just skipping dependabot
|
||||||
# if: ${{ github.actor != 'dependabot[bot]' }}
|
# if: ${{ github.actor != 'dependabot[bot]' }}
|
||||||
# but figured all pull requests seems better, since none of them will have access to sonarcloud.
|
# but figured all pull requests seems better, since none of them will have access to sonarcloud.
|
||||||
|
|
|
@ -65,7 +65,7 @@ jobs:
|
||||||
|
|
||||||
- name: Write app version info
|
- name: Write app version info
|
||||||
working-directory: spiffworkflow-frontend
|
working-directory: spiffworkflow-frontend
|
||||||
run: echo $DOCKER_METADATA_OUTPUT_JSON | jq '.labels' > version_info.json
|
run: echo "$DOCKER_METADATA_OUTPUT_JSON" | jq '.labels' > version_info.json
|
||||||
- name: Build and push Frontend Docker image
|
- name: Build and push Frontend Docker image
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.0.0
|
||||||
with:
|
with:
|
||||||
|
@ -109,7 +109,7 @@ jobs:
|
||||||
|
|
||||||
- name: Write app version info
|
- name: Write app version info
|
||||||
working-directory: spiffworkflow-backend
|
working-directory: spiffworkflow-backend
|
||||||
run: echo $DOCKER_METADATA_OUTPUT_JSON | jq '.labels' > version_info.json
|
run: echo "$DOCKER_METADATA_OUTPUT_JSON" | jq '.labels' > version_info.json
|
||||||
- name: Build and push Backend Docker image
|
- name: Build and push Backend Docker image
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.0.0
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -49,6 +49,7 @@ class HumanTaskModel(SpiffworkflowBaseDBModel):
|
||||||
|
|
||||||
# task_id came first which is why it's a string and task_model_id is the int and foreignkey
|
# task_id came first which is why it's a string and task_model_id is the int and foreignkey
|
||||||
task_model_id: int = db.Column(ForeignKey(TaskModel.id), nullable=True, index=True) # type: ignore
|
task_model_id: int = db.Column(ForeignKey(TaskModel.id), nullable=True, index=True) # type: ignore
|
||||||
|
task_model = relationship(TaskModel)
|
||||||
task_id: str = db.Column(db.String(50))
|
task_id: str = db.Column(db.String(50))
|
||||||
task_name: str = db.Column(db.String(255))
|
task_name: str = db.Column(db.String(255))
|
||||||
task_title: str = db.Column(db.String(50))
|
task_title: str = db.Column(db.String(50))
|
||||||
|
|
|
@ -3,6 +3,7 @@ import enum
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import marshmallow
|
import marshmallow
|
||||||
|
@ -18,6 +19,11 @@ from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||||
from spiffworkflow_backend.models.json_data import JsonDataModel
|
from spiffworkflow_backend.models.json_data import JsonDataModel
|
||||||
from spiffworkflow_backend.models.task_definition import TaskDefinitionModel
|
from spiffworkflow_backend.models.task_definition import TaskDefinitionModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from spiffworkflow_backend.models.human_task_user import ( # noqa: F401
|
||||||
|
HumanTaskModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TaskNotFoundError(Exception):
|
class TaskNotFoundError(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -52,6 +58,7 @@ class TaskModel(SpiffworkflowBaseDBModel):
|
||||||
guid: str = db.Column(db.String(36), nullable=False, unique=True)
|
guid: str = db.Column(db.String(36), nullable=False, unique=True)
|
||||||
bpmn_process_id: int = db.Column(ForeignKey(BpmnProcessModel.id), nullable=False, index=True) # type: ignore
|
bpmn_process_id: int = db.Column(ForeignKey(BpmnProcessModel.id), nullable=False, index=True) # type: ignore
|
||||||
bpmn_process = relationship(BpmnProcessModel, back_populates="tasks")
|
bpmn_process = relationship(BpmnProcessModel, back_populates="tasks")
|
||||||
|
human_tasks = relationship("HumanTaskModel", back_populates="task_model", cascade="delete")
|
||||||
process_instance_id: int = db.Column(ForeignKey("process_instance.id"), nullable=False, index=True)
|
process_instance_id: int = db.Column(ForeignKey("process_instance.id"), nullable=False, index=True)
|
||||||
|
|
||||||
# find this by looking up the "workflow_name" and "task_spec" from the properties_json
|
# find this by looking up the "workflow_name" and "task_spec" from the properties_json
|
||||||
|
@ -110,6 +117,7 @@ class Task:
|
||||||
event_definition: Union[dict[str, Any], None] = None,
|
event_definition: Union[dict[str, Any], None] = None,
|
||||||
call_activity_process_identifier: Optional[str] = None,
|
call_activity_process_identifier: Optional[str] = None,
|
||||||
calling_subprocess_task_id: Optional[str] = None,
|
calling_subprocess_task_id: Optional[str] = None,
|
||||||
|
error_message: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""__init__."""
|
"""__init__."""
|
||||||
self.id = id
|
self.id = id
|
||||||
|
@ -147,6 +155,7 @@ class Task:
|
||||||
self.properties = properties # Arbitrary extension properties from BPMN editor.
|
self.properties = properties # Arbitrary extension properties from BPMN editor.
|
||||||
if self.properties is None:
|
if self.properties is None:
|
||||||
self.properties = {}
|
self.properties = {}
|
||||||
|
self.error_message = error_message
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serialized(self) -> dict[str, Any]:
|
def serialized(self) -> dict[str, Any]:
|
||||||
|
@ -183,6 +192,7 @@ class Task:
|
||||||
"event_definition": self.event_definition,
|
"event_definition": self.event_definition,
|
||||||
"call_activity_process_identifier": self.call_activity_process_identifier,
|
"call_activity_process_identifier": self.call_activity_process_identifier,
|
||||||
"calling_subprocess_task_id": self.calling_subprocess_task_id,
|
"calling_subprocess_task_id": self.calling_subprocess_task_id,
|
||||||
|
"error_message": self.error_message,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -405,14 +405,29 @@ def _interstitial_stream(process_instance_id: int) -> Generator[str, Optional[st
|
||||||
reported_ids.append(spiff_task.id)
|
reported_ids.append(spiff_task.id)
|
||||||
yield f"data: {current_app.json.dumps(task)} \n\n"
|
yield f"data: {current_app.json.dumps(task)} \n\n"
|
||||||
last_task = spiff_task
|
last_task = spiff_task
|
||||||
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
|
try:
|
||||||
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
|
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
|
||||||
spiff_task = processor.next_task()
|
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
|
||||||
|
processor.save() # Fixme - maybe find a way not to do this on every loop?
|
||||||
|
except WorkflowTaskException as wfe:
|
||||||
|
api_error = ApiError.from_workflow_exception(
|
||||||
|
"engine_steps_error", "Failed complete an automated task.", exp=wfe
|
||||||
|
)
|
||||||
|
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
||||||
|
except Exception as e:
|
||||||
|
api_error = ApiError(
|
||||||
|
error_code="engine_steps_error",
|
||||||
|
message=f"Failed complete an automated task. Error was: {str(e)}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
||||||
|
|
||||||
# Note, this has to be done in case someone leaves the page,
|
# Note, this has to be done in case someone leaves the page,
|
||||||
# which can otherwise cancel this function and leave completed tasks un-registered.
|
# which can otherwise cancel this function and leave completed tasks un-registered.
|
||||||
processor.save() # Fixme - maybe find a way not to do this on every loop?
|
spiff_task = processor.next_task()
|
||||||
if len(reported_ids) == 0:
|
|
||||||
# Always provide some response, in the event no instructions were provided.
|
# Always provide some response, in the event no instructions were provided.
|
||||||
|
if len(reported_ids) == 0:
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
||||||
yield f"data: {current_app.json.dumps(task)} \n\n"
|
yield f"data: {current_app.json.dumps(task)} \n\n"
|
||||||
|
|
||||||
|
|
|
@ -1741,8 +1741,8 @@ class ProcessInstanceProcessor:
|
||||||
def next_task(self) -> SpiffTask:
|
def next_task(self) -> SpiffTask:
|
||||||
"""Returns the next task that should be completed even if there are parallel tasks and multiple options are available.
|
"""Returns the next task that should be completed even if there are parallel tasks and multiple options are available.
|
||||||
|
|
||||||
If the process_instance is complete
|
If the process_instance is complete it will return the final end task.
|
||||||
it will return the final end task.
|
If the process_instance is in an error state it will return the task that is erroring.
|
||||||
"""
|
"""
|
||||||
# If the whole blessed mess is done, return the end_event task in the tree
|
# If the whole blessed mess is done, return the end_event task in the tree
|
||||||
# This was failing in the case of a call activity where we have an intermediate EndEvent
|
# This was failing in the case of a call activity where we have an intermediate EndEvent
|
||||||
|
@ -1769,8 +1769,12 @@ class ProcessInstanceProcessor:
|
||||||
waiting_tasks = self.bpmn_process_instance.get_tasks(TaskState.WAITING)
|
waiting_tasks = self.bpmn_process_instance.get_tasks(TaskState.WAITING)
|
||||||
if len(waiting_tasks) > 0:
|
if len(waiting_tasks) > 0:
|
||||||
return waiting_tasks[0]
|
return waiting_tasks[0]
|
||||||
else:
|
|
||||||
return # We have not tasks to return.
|
# If there are no ready tasks, and not waiting tasks, return the latest error.
|
||||||
|
error_task = None
|
||||||
|
for task in SpiffTask.Iterator(self.bpmn_process_instance.task_tree, TaskState.ERROR):
|
||||||
|
error_task = task
|
||||||
|
return error_task
|
||||||
|
|
||||||
# Get a list of all completed user tasks (Non engine tasks)
|
# Get a list of all completed user tasks (Non engine tasks)
|
||||||
completed_user_tasks = self.completed_user_tasks()
|
completed_user_tasks = self.completed_user_tasks()
|
||||||
|
|
|
@ -462,6 +462,12 @@ class ProcessInstanceService:
|
||||||
|
|
||||||
serialized_task_spec = processor.serialize_task_spec(spiff_task.task_spec)
|
serialized_task_spec = processor.serialize_task_spec(spiff_task.task_spec)
|
||||||
|
|
||||||
|
# Grab the last error message.
|
||||||
|
error_message = None
|
||||||
|
for event in processor.process_instance_model.process_instance_events:
|
||||||
|
for detail in event.error_details:
|
||||||
|
error_message = detail.message
|
||||||
|
|
||||||
task = Task(
|
task = Task(
|
||||||
spiff_task.id,
|
spiff_task.id,
|
||||||
spiff_task.task_spec.name,
|
spiff_task.task_spec.name,
|
||||||
|
@ -479,6 +485,7 @@ class ProcessInstanceService:
|
||||||
event_definition=serialized_task_spec.get("event_definition"),
|
event_definition=serialized_task_spec.get("event_definition"),
|
||||||
call_activity_process_identifier=call_activity_process_identifier,
|
call_activity_process_identifier=call_activity_process_identifier,
|
||||||
calling_subprocess_task_id=calling_subprocess_task_id,
|
calling_subprocess_task_id=calling_subprocess_task_id,
|
||||||
|
error_message=error_message,
|
||||||
)
|
)
|
||||||
|
|
||||||
return task
|
return task
|
||||||
|
|
|
@ -634,7 +634,7 @@ class TaskService:
|
||||||
isinstance(exception, ApiError) and exception.error_code == "task_error"
|
isinstance(exception, ApiError) and exception.error_code == "task_error"
|
||||||
):
|
):
|
||||||
task_line_number = exception.line_number
|
task_line_number = exception.line_number
|
||||||
task_line_contents = exception.error_line
|
task_line_contents = exception.error_line[0:255]
|
||||||
task_trace = exception.task_trace
|
task_trace = exception.task_trace
|
||||||
task_offset = exception.offset
|
task_offset = exception.offset
|
||||||
|
|
||||||
|
|
|
@ -311,6 +311,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
||||||
assert process_instance.status == "complete"
|
assert process_instance.status == "complete"
|
||||||
|
|
||||||
|
# this test has been failing intermittently for some time on windows, perhaps ever since it was first added
|
||||||
def test_properly_resets_process_to_given_task_with_call_activity(
|
def test_properly_resets_process_to_given_task_with_call_activity(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
@ -350,12 +351,14 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
||||||
|
|
||||||
processor.suspend()
|
processor.suspend()
|
||||||
task_model_to_reset_to = (
|
all_task_models_matching_top_level_subprocess_script = (
|
||||||
TaskModel.query.join(TaskDefinitionModel)
|
TaskModel.query.join(TaskDefinitionModel)
|
||||||
.filter(TaskDefinitionModel.bpmn_identifier == "top_level_subprocess_script")
|
.filter(TaskDefinitionModel.bpmn_identifier == "top_level_subprocess_script")
|
||||||
.order_by(TaskModel.id.desc()) # type: ignore
|
.order_by(TaskModel.id.desc()) # type: ignore
|
||||||
.first()
|
.all()
|
||||||
)
|
)
|
||||||
|
assert len(all_task_models_matching_top_level_subprocess_script) == 1
|
||||||
|
task_model_to_reset_to = all_task_models_matching_top_level_subprocess_script[0]
|
||||||
assert task_model_to_reset_to is not None
|
assert task_model_to_reset_to is not None
|
||||||
ProcessInstanceProcessor.reset_process(process_instance, task_model_to_reset_to.guid)
|
ProcessInstanceProcessor.reset_process(process_instance, task_model_to_reset_to.guid)
|
||||||
|
|
||||||
|
@ -364,7 +367,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
|
||||||
# make sure we reset to the task we expected
|
# make sure we did actually reset to the task we expected
|
||||||
ready_or_waiting_tasks = processor.get_all_ready_or_waiting_tasks()
|
ready_or_waiting_tasks = processor.get_all_ready_or_waiting_tasks()
|
||||||
top_level_subprocess_script_spiff_task = next(
|
top_level_subprocess_script_spiff_task = next(
|
||||||
task for task in ready_or_waiting_tasks if task.task_spec.name == "top_level_subprocess_script"
|
task for task in ready_or_waiting_tasks if task.task_spec.name == "top_level_subprocess_script"
|
||||||
|
@ -373,7 +376,11 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
processor.resume()
|
processor.resume()
|
||||||
processor.do_engine_steps(save=True, execution_strategy_name="greedy")
|
processor.do_engine_steps(save=True, execution_strategy_name="greedy")
|
||||||
|
|
||||||
|
# reload again, just in case, since the assertion where it says there should be 1 active_human_task
|
||||||
|
# is failing intermittently on windows, so just debugging.
|
||||||
|
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
assert len(process_instance.active_human_tasks) == 1
|
assert len(process_instance.active_human_tasks) == 1
|
||||||
|
|
||||||
human_task_one = process_instance.active_human_tasks[0]
|
human_task_one = process_instance.active_human_tasks[0]
|
||||||
spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
|
spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
|
||||||
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -109,6 +109,7 @@ export default function ErrorDisplay() {
|
||||||
|
|
||||||
if (errorObject) {
|
if (errorObject) {
|
||||||
const title = 'Error:';
|
const title = 'Error:';
|
||||||
|
window.scrollTo(0, 0); // Scroll back to the top of the page
|
||||||
|
|
||||||
errorTag = (
|
errorTag = (
|
||||||
<Notification title={title} onClose={() => removeError()} type="error">
|
<Notification title={title} onClose={() => removeError()} type="error">
|
||||||
|
|
|
@ -1190,7 +1190,7 @@ export default function ProcessInstanceListTable({
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
placeholder="Start typing username"
|
placeholder="Start typing username"
|
||||||
titleText="Process Initiator"
|
titleText="Started By"
|
||||||
selectedItem={processInitiatorSelection}
|
selectedItem={processInitiatorSelection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1199,7 +1199,7 @@ export default function ProcessInstanceListTable({
|
||||||
<TextInput
|
<TextInput
|
||||||
id="process-instance-initiator-search"
|
id="process-instance-initiator-search"
|
||||||
placeholder="Enter username"
|
placeholder="Enter username"
|
||||||
labelText="Process Initiator"
|
labelText="Started By"
|
||||||
invalid={processInitiatorNotFoundErrorText !== ''}
|
invalid={processInitiatorNotFoundErrorText !== ''}
|
||||||
invalidText={processInitiatorNotFoundErrorText}
|
invalidText={processInitiatorNotFoundErrorText}
|
||||||
onChange={(event: any) => {
|
onChange={(event: any) => {
|
||||||
|
@ -1365,68 +1365,30 @@ export default function ProcessInstanceListTable({
|
||||||
const formatter =
|
const formatter =
|
||||||
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
||||||
const value = row[column.accessor];
|
const value = row[column.accessor];
|
||||||
const modifiedModelId = modifyProcessIdentifierForPathParam(
|
|
||||||
row.process_model_identifier
|
|
||||||
);
|
|
||||||
const navigateToProcessInstance = () => {
|
|
||||||
navigate(`${processInstanceShowPathPrefix}/${modifiedModelId}/${row.id}`);
|
|
||||||
};
|
|
||||||
const navigateToProcessModel = () => {
|
|
||||||
navigate(`/admin/process-models/${modifiedModelId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (column.accessor === 'status') {
|
if (column.accessor === 'status') {
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
<td data-qa={`process-instance-status-${value}`}>
|
||||||
<td
|
|
||||||
onClick={navigateToProcessInstance}
|
|
||||||
onKeyDown={navigateToProcessInstance}
|
|
||||||
data-qa={`process-instance-status-${value}`}
|
|
||||||
>
|
|
||||||
{formatter(row, value)}
|
{formatter(row, value)}
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (column.accessor === 'process_model_display_name') {
|
if (column.accessor === 'process_model_display_name') {
|
||||||
const pmStyle = { background: 'rgba(0, 0, 0, .02)' };
|
return <td> {formatter(row, value)} </td>;
|
||||||
return (
|
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
||||||
<td
|
|
||||||
style={pmStyle}
|
|
||||||
onClick={navigateToProcessModel}
|
|
||||||
onKeyDown={navigateToProcessModel}
|
|
||||||
>
|
|
||||||
{formatter(row, value)}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (column.accessor === 'waiting_for') {
|
if (column.accessor === 'waiting_for') {
|
||||||
return (
|
return <td> {getWaitingForTableCellComponent(row)} </td>;
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
||||||
<td
|
|
||||||
onClick={navigateToProcessInstance}
|
|
||||||
onKeyDown={navigateToProcessInstance}
|
|
||||||
>
|
|
||||||
{getWaitingForTableCellComponent(row)}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (column.accessor === 'updated_at_in_seconds') {
|
if (column.accessor === 'updated_at_in_seconds') {
|
||||||
return (
|
return (
|
||||||
<TableCellWithTimeAgoInWords
|
<TableCellWithTimeAgoInWords
|
||||||
timeInSeconds={row.updated_at_in_seconds}
|
timeInSeconds={row.updated_at_in_seconds}
|
||||||
onClick={navigateToProcessInstance}
|
|
||||||
onKeyDown={navigateToProcessInstance}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||||
<td
|
<td data-qa={`process-instance-show-link-${column.accessor}`}>
|
||||||
data-qa={`process-instance-show-link-${column.accessor}`}
|
|
||||||
onKeyDown={navigateToProcessInstance}
|
|
||||||
onClick={navigateToProcessInstance}
|
|
||||||
>
|
|
||||||
{formatter(row, value)}
|
{formatter(row, value)}
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
@ -1484,9 +1446,22 @@ export default function ProcessInstanceListTable({
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowStyle = { cursor: 'pointer' };
|
const rowStyle = { cursor: 'pointer' };
|
||||||
|
const modifiedModelId = modifyProcessIdentifierForPathParam(
|
||||||
|
row.process_model_identifier
|
||||||
|
);
|
||||||
|
const navigateToProcessInstance = () => {
|
||||||
|
navigate(
|
||||||
|
`${processInstanceShowPathPrefix}/${modifiedModelId}/${row.id}`
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<tr style={rowStyle} key={row.id}>
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||||
|
<tr
|
||||||
|
style={rowStyle}
|
||||||
|
key={row.id}
|
||||||
|
onClick={navigateToProcessInstance}
|
||||||
|
onKeyDown={navigateToProcessInstance}
|
||||||
|
>
|
||||||
{currentRow}
|
{currentRow}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default function ProcessModelSearch({
|
||||||
processModels,
|
processModels,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
onChange,
|
onChange,
|
||||||
titleText = 'Process model',
|
titleText = 'Process',
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const getParentGroupsDisplayName = (processModel: ProcessModel) => {
|
const getParentGroupsDisplayName = (processModel: ProcessModel) => {
|
||||||
if (processModel.parent_groups) {
|
if (processModel.parent_groups) {
|
||||||
|
|
|
@ -81,6 +81,7 @@ export interface ProcessInstanceTask {
|
||||||
|
|
||||||
potential_owner_usernames?: string;
|
potential_owner_usernames?: string;
|
||||||
assigned_user_group_identifier?: string;
|
assigned_user_group_identifier?: string;
|
||||||
|
error_message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessReference {
|
export interface ProcessReference {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { getBasicHeaders } from '../services/HttpService';
|
||||||
import InstructionsForEndUser from '../components/InstructionsForEndUser';
|
import InstructionsForEndUser from '../components/InstructionsForEndUser';
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
import { ProcessInstanceTask } from '../interfaces';
|
import { ProcessInstanceTask } from '../interfaces';
|
||||||
|
import useAPIError from '../hooks/UseApiError';
|
||||||
|
|
||||||
export default function ProcessInterstitial() {
|
export default function ProcessInterstitial() {
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
|
@ -20,6 +21,7 @@ export default function ProcessInterstitial() {
|
||||||
const userTasks = useMemo(() => {
|
const userTasks = useMemo(() => {
|
||||||
return ['User Task', 'Manual Task'];
|
return ['User Task', 'Manual Task'];
|
||||||
}, []);
|
}, []);
|
||||||
|
const { addError } = useAPIError();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchEventSource(
|
fetchEventSource(
|
||||||
|
@ -27,9 +29,13 @@ export default function ProcessInterstitial() {
|
||||||
{
|
{
|
||||||
headers: getBasicHeaders(),
|
headers: getBasicHeaders(),
|
||||||
onmessage(ev) {
|
onmessage(ev) {
|
||||||
const task = JSON.parse(ev.data);
|
const retValue = JSON.parse(ev.data);
|
||||||
setData((prevData) => [...prevData, task]);
|
if ('error_code' in retValue) {
|
||||||
setLastTask(task);
|
addError(retValue);
|
||||||
|
} else {
|
||||||
|
setData((prevData) => [...prevData, retValue]);
|
||||||
|
setLastTask(retValue);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onclose() {
|
onclose() {
|
||||||
setState('CLOSED');
|
setState('CLOSED');
|
||||||
|
@ -85,6 +91,8 @@ export default function ProcessInterstitial() {
|
||||||
return <img src="/interstitial/waiting.png" alt="Waiting ...." />;
|
return <img src="/interstitial/waiting.png" alt="Waiting ...." />;
|
||||||
case 'COMPLETED':
|
case 'COMPLETED':
|
||||||
return <img src="/interstitial/completed.png" alt="Completed" />;
|
return <img src="/interstitial/completed.png" alt="Completed" />;
|
||||||
|
case 'ERROR':
|
||||||
|
return <img src="/interstitial/errored.png" alt="Errored" />;
|
||||||
default:
|
default:
|
||||||
return getStatus();
|
return getStatus();
|
||||||
}
|
}
|
||||||
|
@ -104,6 +112,10 @@ export default function ProcessInterstitial() {
|
||||||
if (shouldRedirect(myTask)) {
|
if (shouldRedirect(myTask)) {
|
||||||
return <div>Redirecting you to the next task now ...</div>;
|
return <div>Redirecting you to the next task now ...</div>;
|
||||||
}
|
}
|
||||||
|
if (myTask.error_message) {
|
||||||
|
return <div>{myTask.error_message}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<InstructionsForEndUser task={myTask} />
|
<InstructionsForEndUser task={myTask} />
|
||||||
|
@ -147,7 +159,7 @@ export default function ProcessInterstitial() {
|
||||||
<Column md={2} lg={4} sm={2}>
|
<Column md={2} lg={4} sm={2}>
|
||||||
Task: <em>{d.title}</em>
|
Task: <em>{d.title}</em>
|
||||||
</Column>
|
</Column>
|
||||||
<Column md={6} lg={8} sm={4}>
|
<Column md={6} lg={6} sm={4}>
|
||||||
{userMessage(d)}
|
{userMessage(d)}
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -117,10 +117,10 @@ export default function TaskShow() {
|
||||||
const processResult = (result: ProcessInstanceTask) => {
|
const processResult = (result: ProcessInstanceTask) => {
|
||||||
setTask(result);
|
setTask(result);
|
||||||
setDisabled(false);
|
setDisabled(false);
|
||||||
|
|
||||||
if (!result.can_complete) {
|
if (!result.can_complete) {
|
||||||
navigateToInterstitial(result);
|
navigateToInterstitial(result);
|
||||||
}
|
}
|
||||||
|
window.scrollTo(0, 0); // Scroll back to the top of the page
|
||||||
|
|
||||||
/* Disable call to load previous tasks -- do not display menu.
|
/* Disable call to load previous tasks -- do not display menu.
|
||||||
const url = `/v1.0/process-instances/for-me/${modifyProcessIdentifierForPathParam(
|
const url = `/v1.0/process-instances/for-me/${modifyProcessIdentifierForPathParam(
|
||||||
|
|
Loading…
Reference in New Issue