Feature/task assignment (#352)
* added an api to assign a list of users to a task w/ burnettk * use the modal submit and close buttons when saving task data on the instance show page w/ burnettk * switch save and cancel buttons on secrets new page w/ burnettk * add some icons, tho still missing event stuff * finished adding imporoved icons and fixing up task modal w/ burnettk * added some user search options to assig tasks to w/ burnettk * cleaned up task details modal and added call to backend to add potential users w/ burnettk * fixed broken tests w/ burnettk * removed some merge comments w/ burnettk * process instance id is an int not a str w/ burnettk --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com> Co-authored-by: burnettk <burnettk@users.noreply.github.com>
This commit is contained in:
parent
93b8c09e90
commit
841f3ccc8c
|
@ -1618,6 +1618,44 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Workflow"
|
$ref: "#/components/schemas/Workflow"
|
||||||
|
|
||||||
|
/task-assign/{modified_process_model_identifier}/{process_instance_id}/{task_guid}:
|
||||||
|
parameters:
|
||||||
|
- name: modified_process_model_identifier
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The modified id of an existing process model
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: process_instance_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The unique id of an existing process instance.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: task_guid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The unique id of the task.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
post:
|
||||||
|
operationId: spiffworkflow_backend.routes.tasks_controller.task_assign
|
||||||
|
summary: Assign a given task to a list of additional users
|
||||||
|
tags:
|
||||||
|
- Process Instances
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/User"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "ok: true"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/OkTrue"
|
||||||
|
|
||||||
/process-data/{modified_process_model_identifier}/{process_instance_id}/{process_data_identifier}:
|
/process-data/{modified_process_model_identifier}/{process_instance_id}/{process_data_identifier}:
|
||||||
parameters:
|
parameters:
|
||||||
- name: modified_process_model_identifier
|
- name: modified_process_model_identifier
|
||||||
|
@ -1724,7 +1762,7 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
description: The unique id of the process instance
|
description: The unique id of the process instance
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: integer
|
||||||
- name: task_guid
|
- name: task_guid
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
|
|
|
@ -47,6 +47,23 @@ class SpiffworkflowBaseDBModel(db.Model): # type: ignore
|
||||||
|
|
||||||
return m_type.value
|
return m_type.value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def commit_with_rollback_on_exception(cls) -> None:
|
||||||
|
"""Attempts to commit the session and rolls back if it fails.
|
||||||
|
|
||||||
|
We may need to add other error handling here as we go. But sqlalchemy insists that we
|
||||||
|
"frame" our commits.
|
||||||
|
|
||||||
|
https://docs.sqlalchemy.org/en/20/errors.html#error-7s2a
|
||||||
|
https://docs.sqlalchemy.org/en/20/faq/sessions.html#faq-session-rollback
|
||||||
|
https://docs.sqlalchemy.org/en/20/orm/session_basics.html#session-faq-whentocreate
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
db.session.commit()
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def update_created_modified_on_create_listener(
|
def update_created_modified_on_create_listener(
|
||||||
mapper: Mapper, _connection: Connection, target: SpiffworkflowBaseDBModel
|
mapper: Mapper, _connection: Connection, target: SpiffworkflowBaseDBModel
|
||||||
|
|
|
@ -29,6 +29,7 @@ from sqlalchemy.orm import aliased
|
||||||
from sqlalchemy.orm.util import AliasedClass
|
from sqlalchemy.orm.util import AliasedClass
|
||||||
|
|
||||||
from spiffworkflow_backend.exceptions.api_error import ApiError
|
from spiffworkflow_backend.exceptions.api_error import ApiError
|
||||||
|
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||||
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
|
||||||
|
@ -173,13 +174,12 @@ def task_data_show(
|
||||||
|
|
||||||
|
|
||||||
def task_data_update(
|
def task_data_update(
|
||||||
process_instance_id: str,
|
process_instance_id: int,
|
||||||
modified_process_model_identifier: str,
|
modified_process_model_identifier: str,
|
||||||
task_guid: str,
|
task_guid: str,
|
||||||
body: dict,
|
body: dict,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""Update task data."""
|
process_instance = ProcessInstanceModel.query.filter(ProcessInstanceModel.id == process_instance_id).first()
|
||||||
process_instance = ProcessInstanceModel.query.filter(ProcessInstanceModel.id == int(process_instance_id)).first()
|
|
||||||
if process_instance:
|
if process_instance:
|
||||||
if process_instance.status != "suspended":
|
if process_instance.status != "suspended":
|
||||||
raise ProcessInstanceTaskDataCannotBeUpdatedError(
|
raise ProcessInstanceTaskDataCannotBeUpdatedError(
|
||||||
|
@ -227,13 +227,13 @@ def task_data_update(
|
||||||
|
|
||||||
def manual_complete_task(
|
def manual_complete_task(
|
||||||
modified_process_model_identifier: str,
|
modified_process_model_identifier: str,
|
||||||
process_instance_id: str,
|
process_instance_id: int,
|
||||||
task_guid: str,
|
task_guid: str,
|
||||||
body: dict,
|
body: dict,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""Mark a task complete without executing it."""
|
"""Mark a task complete without executing it."""
|
||||||
execute = body.get("execute", True)
|
execute = body.get("execute", True)
|
||||||
process_instance = ProcessInstanceModel.query.filter(ProcessInstanceModel.id == int(process_instance_id)).first()
|
process_instance = ProcessInstanceModel.query.filter(ProcessInstanceModel.id == process_instance_id).first()
|
||||||
if process_instance:
|
if process_instance:
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
processor.manual_complete_task(task_guid, execute, g.user)
|
processor.manual_complete_task(task_guid, execute, g.user)
|
||||||
|
@ -249,6 +249,57 @@ def manual_complete_task(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def task_assign(
|
||||||
|
modified_process_model_identifier: str,
|
||||||
|
process_instance_id: int,
|
||||||
|
task_guid: str,
|
||||||
|
body: dict,
|
||||||
|
) -> Response:
|
||||||
|
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
||||||
|
|
||||||
|
if process_instance.status != ProcessInstanceStatus.suspended.value:
|
||||||
|
raise ApiError(
|
||||||
|
error_code="error_not_suspended",
|
||||||
|
message="The process instance must be suspended to perform this operation",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
if "user_ids" not in body:
|
||||||
|
raise ApiError(
|
||||||
|
error_code="malformed_request",
|
||||||
|
message="user_ids as an array must be given in the body of the request.",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
_get_process_model(
|
||||||
|
process_instance.process_model_identifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id)
|
||||||
|
human_tasks = HumanTaskModel.query.filter_by(
|
||||||
|
process_instance_id=process_instance.id, task_id=task_model.guid
|
||||||
|
).all()
|
||||||
|
|
||||||
|
if len(human_tasks) > 1:
|
||||||
|
raise ApiError(
|
||||||
|
error_code="multiple_tasks_found",
|
||||||
|
message="More than one ready tasks were found. This should never happen.",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
human_task = human_tasks[0]
|
||||||
|
|
||||||
|
for user_id in body["user_ids"]:
|
||||||
|
human_task_user = HumanTaskUserModel.query.filter_by(user_id=user_id, human_task=human_task).first()
|
||||||
|
if human_task_user is None:
|
||||||
|
human_task_user = HumanTaskUserModel(user_id=user_id, human_task=human_task)
|
||||||
|
db.session.add(human_task_user)
|
||||||
|
|
||||||
|
SpiffworkflowBaseDBModel.commit_with_rollback_on_exception()
|
||||||
|
|
||||||
|
return make_response(jsonify({"ok": True}), 200)
|
||||||
|
|
||||||
|
|
||||||
def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappers.Response:
|
def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappers.Response:
|
||||||
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
||||||
|
|
||||||
|
@ -353,6 +404,15 @@ def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappe
|
||||||
return make_response(jsonify(task_model), 200)
|
return make_response(jsonify(task_model), 200)
|
||||||
|
|
||||||
|
|
||||||
|
def task_submit(
|
||||||
|
process_instance_id: int,
|
||||||
|
task_guid: str,
|
||||||
|
body: dict[str, Any],
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
with sentry_sdk.start_span(op="controller_action", description="tasks_controller.task_submit"):
|
||||||
|
return _task_submit_shared(process_instance_id, task_guid, body)
|
||||||
|
|
||||||
|
|
||||||
def _render_instructions_for_end_user(task_model: TaskModel, extensions: dict | None = None) -> str:
|
def _render_instructions_for_end_user(task_model: TaskModel, extensions: dict | None = None) -> str:
|
||||||
"""Assure any instructions for end user are processed for jinja syntax."""
|
"""Assure any instructions for end user are processed for jinja syntax."""
|
||||||
if extensions is None:
|
if extensions is None:
|
||||||
|
@ -424,7 +484,7 @@ def _interstitial_stream(
|
||||||
return
|
return
|
||||||
|
|
||||||
# path used by the interstitial page while executing tasks - ie the background processor is not executing them
|
# path used by the interstitial page while executing tasks - ie the background processor is not executing them
|
||||||
ready_engine_task_count = get_ready_engine_step_count(processor.bpmn_process_instance)
|
ready_engine_task_count = _get_ready_engine_step_count(processor.bpmn_process_instance)
|
||||||
if execute_tasks and ready_engine_task_count == 0:
|
if execute_tasks and ready_engine_task_count == 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -467,7 +527,7 @@ def _interstitial_stream(
|
||||||
yield _render_data("task", task)
|
yield _render_data("task", task)
|
||||||
|
|
||||||
|
|
||||||
def get_ready_engine_step_count(bpmn_process_instance: BpmnWorkflow) -> int:
|
def _get_ready_engine_step_count(bpmn_process_instance: BpmnWorkflow) -> int:
|
||||||
return len([t for t in bpmn_process_instance.get_tasks(TaskState.READY) if not t.task_spec.manual])
|
return len([t for t in bpmn_process_instance.get_tasks(TaskState.READY) if not t.task_spec.manual])
|
||||||
|
|
||||||
|
|
||||||
|
@ -656,15 +716,6 @@ def _task_submit_shared(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def task_submit(
|
|
||||||
process_instance_id: int,
|
|
||||||
task_guid: str,
|
|
||||||
body: dict[str, Any],
|
|
||||||
) -> flask.wrappers.Response:
|
|
||||||
with sentry_sdk.start_span(op="controller_action", description="tasks_controller.task_submit"):
|
|
||||||
return _task_submit_shared(process_instance_id, task_guid, body)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_tasks(
|
def _get_tasks(
|
||||||
processes_started_by_user: bool = True,
|
processes_started_by_user: bool = True,
|
||||||
has_lane_assignment_id: bool = True,
|
has_lane_assignment_id: bool = True,
|
||||||
|
|
|
@ -86,6 +86,7 @@ PATH_SEGMENTS_FOR_PERMISSION_ALL = [
|
||||||
{"path": "/process-model-natural-language", "relevant_permissions": ["create"]},
|
{"path": "/process-model-natural-language", "relevant_permissions": ["create"]},
|
||||||
{"path": "/process-model-publish", "relevant_permissions": ["create"]},
|
{"path": "/process-model-publish", "relevant_permissions": ["create"]},
|
||||||
{"path": "/process-model-tests", "relevant_permissions": ["create"]},
|
{"path": "/process-model-tests", "relevant_permissions": ["create"]},
|
||||||
|
{"path": "/task-assign", "relevant_permissions": ["create"]},
|
||||||
{"path": "/task-data", "relevant_permissions": ["read", "update"]},
|
{"path": "/task-data", "relevant_permissions": ["read", "update"]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -546,6 +547,7 @@ class AuthorizationService:
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/task-complete/*"))
|
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/task-complete/*"))
|
||||||
|
|
||||||
# read comes from PG and PM ALL permissions as well
|
# read comes from PG and PM ALL permissions as well
|
||||||
|
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/task-assign/*"))
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="update", target_uri="/task-data/*"))
|
permissions_to_assign.append(PermissionToAssign(permission="update", target_uri="/task-data/*"))
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/event-error-details/*"))
|
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/event-error-details/*"))
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/logs/*"))
|
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/logs/*"))
|
||||||
|
|
|
@ -2493,6 +2493,7 @@ class TestProcessApi(BaseTest):
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
data=json.dumps({"execute": False}),
|
data=json.dumps({"execute": False}),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.json["status"] == "suspended"
|
assert response.json["status"] == "suspended"
|
||||||
task_model = TaskModel.query.filter_by(guid=human_task["guid"]).first()
|
task_model = TaskModel.query.filter_by(guid=human_task["guid"]).first()
|
||||||
assert task_model is not None
|
assert task_model is not None
|
||||||
|
|
|
@ -138,6 +138,7 @@ class TestAuthorizationService(BaseTest):
|
||||||
("/process-models/some-process-group:some-process-model:*", "delete"),
|
("/process-models/some-process-group:some-process-model:*", "delete"),
|
||||||
("/process-models/some-process-group:some-process-model:*", "read"),
|
("/process-models/some-process-group:some-process-model:*", "read"),
|
||||||
("/process-models/some-process-group:some-process-model:*", "update"),
|
("/process-models/some-process-group:some-process-model:*", "update"),
|
||||||
|
("/task-assign/some-process-group:some-process-model:*", "create"),
|
||||||
("/task-data/some-process-group:some-process-model:*", "read"),
|
("/task-data/some-process-group:some-process-model:*", "read"),
|
||||||
("/task-data/some-process-group:some-process-model:*", "update"),
|
("/task-data/some-process-group:some-process-model:*", "update"),
|
||||||
]
|
]
|
||||||
|
@ -223,6 +224,7 @@ class TestAuthorizationService(BaseTest):
|
||||||
("/process-models/some-process-group:some-process-model/*", "delete"),
|
("/process-models/some-process-group:some-process-model/*", "delete"),
|
||||||
("/process-models/some-process-group:some-process-model/*", "read"),
|
("/process-models/some-process-group:some-process-model/*", "read"),
|
||||||
("/process-models/some-process-group:some-process-model/*", "update"),
|
("/process-models/some-process-group:some-process-model/*", "update"),
|
||||||
|
("/task-assign/some-process-group:some-process-model/*", "create"),
|
||||||
("/task-data/some-process-group:some-process-model/*", "read"),
|
("/task-data/some-process-group:some-process-model/*", "read"),
|
||||||
("/task-data/some-process-group:some-process-model/*", "update"),
|
("/task-data/some-process-group:some-process-model/*", "update"),
|
||||||
]
|
]
|
||||||
|
@ -333,6 +335,7 @@ class TestAuthorizationService(BaseTest):
|
||||||
("/secrets/*", "read"),
|
("/secrets/*", "read"),
|
||||||
("/secrets/*", "update"),
|
("/secrets/*", "update"),
|
||||||
("/send-event/*", "create"),
|
("/send-event/*", "create"),
|
||||||
|
("/task-assign/*", "create"),
|
||||||
("/task-complete/*", "create"),
|
("/task-complete/*", "create"),
|
||||||
("/task-data/*", "update"),
|
("/task-data/*", "update"),
|
||||||
("/task-data/*", "read"),
|
("/task-data/*", "read"),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -10,6 +10,7 @@ import { getBasicHeaders } from '../services/HttpService';
|
||||||
import InstructionsForEndUser from './InstructionsForEndUser';
|
import InstructionsForEndUser from './InstructionsForEndUser';
|
||||||
import { ProcessInstance, ProcessInstanceTask } from '../interfaces';
|
import { ProcessInstance, ProcessInstanceTask } from '../interfaces';
|
||||||
import useAPIError from '../hooks/UseApiError';
|
import useAPIError from '../hooks/UseApiError';
|
||||||
|
import { HUMAN_TASK_TYPES } from '../helpers';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
processInstanceId: number;
|
processInstanceId: number;
|
||||||
|
@ -35,9 +36,6 @@ export default function ProcessInterstitial({
|
||||||
useState<ProcessInstance | null>(null);
|
useState<ProcessInstance | null>(null);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const userTasks = useMemo(() => {
|
|
||||||
return ['User Task', 'Manual Task'];
|
|
||||||
}, []);
|
|
||||||
const { addError } = useAPIError();
|
const { addError } = useAPIError();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -77,10 +75,10 @@ export default function ProcessInterstitial({
|
||||||
!processInstance &&
|
!processInstance &&
|
||||||
myTask &&
|
myTask &&
|
||||||
myTask.can_complete &&
|
myTask.can_complete &&
|
||||||
userTasks.includes(myTask.type)
|
HUMAN_TASK_TYPES.includes(myTask.type)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[allowRedirect, processInstance, userTasks]
|
[allowRedirect, processInstance]
|
||||||
);
|
);
|
||||||
|
|
||||||
const shouldRedirectToProcessInstance = useCallback((): boolean => {
|
const shouldRedirectToProcessInstance = useCallback((): boolean => {
|
||||||
|
@ -105,7 +103,6 @@ export default function ProcessInterstitial({
|
||||||
}, [
|
}, [
|
||||||
lastTask,
|
lastTask,
|
||||||
navigate,
|
navigate,
|
||||||
userTasks,
|
|
||||||
shouldRedirectToTask,
|
shouldRedirectToTask,
|
||||||
processInstanceId,
|
processInstanceId,
|
||||||
processInstanceShowPageUrl,
|
processInstanceShowPageUrl,
|
||||||
|
@ -181,7 +178,7 @@ export default function ProcessInterstitial({
|
||||||
return userMessageForProcessInstance(processInstance, myTask);
|
return userMessageForProcessInstance(processInstance, myTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
|
if (!myTask.can_complete && HUMAN_TASK_TYPES.includes(myTask.type)) {
|
||||||
return inlineMessage(
|
return inlineMessage(
|
||||||
'',
|
'',
|
||||||
`This next task is assigned to a different person or team. There is no action for you to take at this time.`
|
`This next task is assigned to a different person or team. There is no action for you to take at this time.`
|
||||||
|
@ -190,7 +187,11 @@ export default function ProcessInterstitial({
|
||||||
if (shouldRedirectToTask(myTask)) {
|
if (shouldRedirectToTask(myTask)) {
|
||||||
return inlineMessage('', `Redirecting ...`);
|
return inlineMessage('', `Redirecting ...`);
|
||||||
}
|
}
|
||||||
if (myTask && myTask.can_complete && userTasks.includes(myTask.type)) {
|
if (
|
||||||
|
myTask &&
|
||||||
|
myTask.can_complete &&
|
||||||
|
HUMAN_TASK_TYPES.includes(myTask.type)
|
||||||
|
) {
|
||||||
return inlineMessage(
|
return inlineMessage(
|
||||||
'',
|
'',
|
||||||
`The task "${myTask.title}" is ready for you to complete.`
|
`The task "${myTask.title}" is ready for you to complete.`
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { ComboBox } from '@carbon/react';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
import { CarbonComboBoxSelection, User } from '../interfaces';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
onSelectedUser: Function;
|
||||||
|
label?: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function UserSearch({
|
||||||
|
onSelectedUser,
|
||||||
|
className,
|
||||||
|
label = 'User',
|
||||||
|
}: OwnProps) {
|
||||||
|
const lastRequestedInitatorSearchTerm = useRef<string>();
|
||||||
|
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||||||
|
const [userList, setUserList] = useState<User[]>([]);
|
||||||
|
|
||||||
|
const handleUserSearchResult = (result: any, inputText: string) => {
|
||||||
|
if (lastRequestedInitatorSearchTerm.current === result.username_prefix) {
|
||||||
|
setUserList(result.users);
|
||||||
|
result.users.forEach((user: User) => {
|
||||||
|
if (user.username === inputText) {
|
||||||
|
setSelectedUser(user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchForUser = (inputText: string) => {
|
||||||
|
if (inputText) {
|
||||||
|
lastRequestedInitatorSearchTerm.current = inputText;
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/users/search?username_prefix=${inputText}`,
|
||||||
|
successCallback: (result: any) =>
|
||||||
|
handleUserSearchResult(result, inputText),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDebouncedSearchUser = useDebouncedCallback(
|
||||||
|
(value: string) => {
|
||||||
|
searchForUser(value);
|
||||||
|
},
|
||||||
|
// delay in ms
|
||||||
|
250
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ComboBox
|
||||||
|
onInputChange={addDebouncedSearchUser}
|
||||||
|
className={className}
|
||||||
|
onChange={(selection: CarbonComboBoxSelection) => {
|
||||||
|
onSelectedUser(selection.selectedItem);
|
||||||
|
}}
|
||||||
|
id="user-search"
|
||||||
|
data-qa="user-search"
|
||||||
|
items={userList}
|
||||||
|
itemToString={(processInstanceInitatorOption: User) => {
|
||||||
|
if (processInstanceInitatorOption) {
|
||||||
|
return processInstanceInitatorOption.username;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
placeholder="Start typing username"
|
||||||
|
titleText={label}
|
||||||
|
selectedItem={selectedUser}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -26,6 +26,13 @@ export const slugifyString = (str: any) => {
|
||||||
.replace(/-+$/g, '');
|
.replace(/-+$/g, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const HUMAN_TASK_TYPES = [
|
||||||
|
'User Task',
|
||||||
|
'Manual Task',
|
||||||
|
'UserTask',
|
||||||
|
'ManualTask',
|
||||||
|
];
|
||||||
|
|
||||||
export const underscorizeString = (inputString: string) => {
|
export const underscorizeString = (inputString: string) => {
|
||||||
return slugifyString(inputString).replace(/-/g, '_');
|
return slugifyString(inputString).replace(/-/g, '_');
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,7 @@ export const useUriListForPermissions = () => {
|
||||||
processInstanceResumePath: `/v1.0/process-instance-resume/${params.process_model_id}/${params.process_instance_id}`,
|
processInstanceResumePath: `/v1.0/process-instance-resume/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
processInstanceSendEventPath: `/v1.0/send-event/${params.process_model_id}/${params.process_instance_id}`,
|
processInstanceSendEventPath: `/v1.0/send-event/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
processInstanceSuspendPath: `/v1.0/process-instance-suspend/${params.process_model_id}/${params.process_instance_id}`,
|
processInstanceSuspendPath: `/v1.0/process-instance-suspend/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
|
processInstanceTaskAssignPath: `/v1.0/task-assign/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
processInstanceTaskDataPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
|
processInstanceTaskDataPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
processInstanceTaskListForMePath: `/v1.0/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}/task-info`,
|
processInstanceTaskListForMePath: `/v1.0/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}/task-info`,
|
||||||
processInstanceTaskListPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}/task-info`,
|
processInstanceTaskListPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}/task-info`,
|
||||||
|
|
|
@ -44,7 +44,7 @@ h1 {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
color: #161616;
|
color: #161616;
|
||||||
margin-bottom: 1em
|
margin-bottom: 1rem
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
@ -54,6 +54,13 @@ h2 {
|
||||||
color: #161616;
|
color: #161616;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #161616;
|
||||||
|
}
|
||||||
|
|
||||||
.span-tag {
|
.span-tag {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
@ -137,12 +144,12 @@ code {
|
||||||
.app-logo {
|
.app-logo {
|
||||||
height: 37px;
|
height: 37px;
|
||||||
width: 152px;
|
width: 152px;
|
||||||
margin-top: 1em;
|
margin-top: 1rem;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spiffworkflow-header-container {
|
.spiffworkflow-header-container {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active-task-highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
|
.active-task-highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
|
||||||
|
@ -168,11 +175,11 @@ code {
|
||||||
}
|
}
|
||||||
|
|
||||||
.cds--breadcrumb {
|
.cds--breadcrumb {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.process-description {
|
.process-description {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1.with-icons {
|
h1.with-icons {
|
||||||
|
@ -211,7 +218,7 @@ dl dd {
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-bottom-margin {
|
.with-bottom-margin {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-profile-toggletip-content {
|
.user-profile-toggletip-content {
|
||||||
|
@ -253,15 +260,15 @@ dl dd {
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-top-margin {
|
.with-top-margin {
|
||||||
margin-top: 1em;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-extra-top-margin {
|
.with-extra-top-margin {
|
||||||
margin-top: 1.3em;
|
margin-top: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-top-margin-for-label-next-to-text-input {
|
.with-top-margin-for-label-next-to-text-input {
|
||||||
margin-top: 2.3em;
|
margin-top: 2.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-tiny-top-margin {
|
.with-tiny-top-margin {
|
||||||
|
@ -273,13 +280,17 @@ dl dd {
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-large-bottom-margin {
|
.with-large-bottom-margin {
|
||||||
margin-bottom: 3em;
|
margin-bottom: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-tiny-bottom-margin {
|
.with-tiny-bottom-margin {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.with-half-rem-bottom-margin {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.diagram-viewer-canvas {
|
.diagram-viewer-canvas {
|
||||||
border:1px solid #000000;
|
border:1px solid #000000;
|
||||||
height:70vh;
|
height:70vh;
|
||||||
|
@ -288,7 +299,7 @@ dl dd {
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
font-size: 1.5em;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item.active {
|
.breadcrumb-item.active {
|
||||||
|
@ -296,7 +307,7 @@ dl dd {
|
||||||
}
|
}
|
||||||
|
|
||||||
.container .nav-tabs {
|
.container .nav-tabs {
|
||||||
margin-top: 1em;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -314,7 +325,7 @@ dl dd {
|
||||||
}
|
}
|
||||||
.markdown table th,
|
.markdown table th,
|
||||||
.markdown table td {
|
.markdown table td {
|
||||||
padding: .5em;
|
padding: .5rem;
|
||||||
border: 1px solid lightgrey;
|
border: 1px solid lightgrey;
|
||||||
}
|
}
|
||||||
/* Zebra Table Style */
|
/* Zebra Table Style */
|
||||||
|
@ -323,7 +334,7 @@ dl dd {
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-instructions {
|
.form-instructions {
|
||||||
margin-bottom: 10em;
|
margin-bottom: 10rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Json Web Form CSS Fix - Bootstrap now requries that each li have a "list-inline-item." Also have a PR
|
/* Json Web Form CSS Fix - Bootstrap now requries that each li have a "list-inline-item." Also have a PR
|
||||||
|
@ -347,13 +358,13 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
||||||
.tile-process-group-content-container {
|
.tile-process-group-content-container {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
height: 264px;
|
height: 264px;
|
||||||
padding: 1em;
|
padding: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-process-group-display-name {
|
.tile-process-group-display-name {
|
||||||
margin-top: 2em;
|
margin-top: 2rem;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1rem;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
color: #161616;
|
color: #161616;
|
||||||
|
@ -361,7 +372,7 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-title-top {
|
.tile-title-top {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2rem;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
color: #161616;
|
color: #161616;
|
||||||
|
@ -386,7 +397,7 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
||||||
|
|
||||||
.tile-pin-bottom {
|
.tile-pin-bottom {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 1em;
|
bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cds--tabs .cds--tabs__nav-link {
|
.cds--tabs .cds--tabs__nav-link {
|
||||||
|
@ -398,7 +409,7 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
||||||
}
|
}
|
||||||
|
|
||||||
td.actions-cell {
|
td.actions-cell {
|
||||||
width: 1em;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.process-instance-list-table {
|
.process-instance-list-table {
|
||||||
|
@ -411,12 +422,12 @@ td.actions-cell {
|
||||||
}
|
}
|
||||||
|
|
||||||
.process-instance-table-header {
|
.process-instance-table-header {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-results-message {
|
.no-results-message {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
margin-left: 2em;
|
margin-left: 2rem;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,12 +436,12 @@ td.actions-cell {
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
letter-spacing: 0.16px;
|
letter-spacing: 0.16px;
|
||||||
color: #525252;
|
color: #525252;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* top and bottom margin since this is sort of the middle of three sections on the process model show page */
|
/* top and bottom margin since this is sort of the middle of three sections on the process model show page */
|
||||||
.process-model-files-section {
|
.process-model-files-section {
|
||||||
margin: 2em 0;
|
margin: 2rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filterIcon {
|
.filterIcon {
|
||||||
|
@ -525,7 +536,7 @@ svg.notification-icon {
|
||||||
}
|
}
|
||||||
|
|
||||||
.please-press-filter-button {
|
.please-press-filter-button {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,28 +548,28 @@ svg.notification-icon {
|
||||||
|
|
||||||
.user_instructions_0 {
|
.user_instructions_0 {
|
||||||
filter: opacity(1);
|
filter: opacity(1);
|
||||||
font-size: 1.2em;
|
font-size: 1.2rem;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user_instructions_1 {
|
.user_instructions_1 {
|
||||||
filter: opacity(60%);
|
filter: opacity(60%);
|
||||||
font-size: 1.1em;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user_instructions_2 {
|
.user_instructions_2 {
|
||||||
filter: opacity(40%);
|
filter: opacity(40%);
|
||||||
font-size: 1em;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user_instructions_3 {
|
.user_instructions_3 {
|
||||||
filter: opacity(20%);
|
filter: opacity(20%);
|
||||||
font-size: 9em;
|
font-size: 9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user_instructions_4 {
|
.user_instructions_4 {
|
||||||
filter: opacity(10%);
|
filter: opacity(10%);
|
||||||
font-size: 8em;
|
font-size: 8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float-right {
|
.float-right {
|
||||||
|
@ -649,7 +660,25 @@ hr {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-dropdown {
|
||||||
|
height: 20rem;
|
||||||
|
width: "auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-data-details-header {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explanatory-message {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indented-content {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
#hidden-form-for-autosave {
|
#hidden-form-for-autosave {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,6 @@ export interface TaskPropertiesJson {
|
||||||
last_state_change: number;
|
last_state_change: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskDefinitionPropertiesJson {
|
|
||||||
spec: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EventDefinition {
|
export interface EventDefinition {
|
||||||
typename: string;
|
typename: string;
|
||||||
payload: any;
|
payload: any;
|
||||||
|
@ -38,6 +34,11 @@ export interface EventDefinition {
|
||||||
message_var?: string;
|
message_var?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TaskDefinitionPropertiesJson {
|
||||||
|
spec: string;
|
||||||
|
event_definition: EventDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SignalButton {
|
export interface SignalButton {
|
||||||
label: string;
|
label: string;
|
||||||
event: EventDefinition;
|
event: EventDefinition;
|
||||||
|
@ -286,7 +287,7 @@ export interface PaginationObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CarbonComboBoxSelection {
|
export interface CarbonComboBoxSelection {
|
||||||
selectedItem: ProcessModel;
|
selectedItem: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CarbonComboBoxProcessSelection {
|
export interface CarbonComboBoxProcessSelection {
|
||||||
|
|
|
@ -7,12 +7,20 @@ import {
|
||||||
useSearchParams,
|
useSearchParams,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
TrashCan,
|
Send,
|
||||||
StopOutline,
|
ButtonSet,
|
||||||
PauseOutline,
|
|
||||||
PlayOutline,
|
|
||||||
InProgress,
|
|
||||||
Checkmark,
|
Checkmark,
|
||||||
|
Edit,
|
||||||
|
InProgress,
|
||||||
|
PauseOutline,
|
||||||
|
UserFollow,
|
||||||
|
Play,
|
||||||
|
PlayOutline,
|
||||||
|
Reset,
|
||||||
|
RuleDraft,
|
||||||
|
SkipForward,
|
||||||
|
StopOutline,
|
||||||
|
TrashCan,
|
||||||
Warning,
|
Warning,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from '@carbon/icons-react';
|
} from '@carbon/icons-react';
|
||||||
|
@ -37,6 +45,7 @@ import HttpService from '../services/HttpService';
|
||||||
import ReactDiagramEditor from '../components/ReactDiagramEditor';
|
import ReactDiagramEditor from '../components/ReactDiagramEditor';
|
||||||
import {
|
import {
|
||||||
convertSecondsToFormattedDateTime,
|
convertSecondsToFormattedDateTime,
|
||||||
|
HUMAN_TASK_TYPES,
|
||||||
modifyProcessIdentifierForPathParam,
|
modifyProcessIdentifierForPathParam,
|
||||||
unModifyProcessIdentifierForPathParam,
|
unModifyProcessIdentifierForPathParam,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
|
@ -50,12 +59,14 @@ import {
|
||||||
ProcessInstance,
|
ProcessInstance,
|
||||||
Task,
|
Task,
|
||||||
TaskDefinitionPropertiesJson,
|
TaskDefinitionPropertiesJson,
|
||||||
|
User,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
import ProcessInstanceClass from '../classes/ProcessInstanceClass';
|
import ProcessInstanceClass from '../classes/ProcessInstanceClass';
|
||||||
import TaskListTable from '../components/TaskListTable';
|
import TaskListTable from '../components/TaskListTable';
|
||||||
import useAPIError from '../hooks/UseApiError';
|
import useAPIError from '../hooks/UseApiError';
|
||||||
import ProcessInterstitial from '../components/ProcessInterstitial';
|
import ProcessInterstitial from '../components/ProcessInterstitial';
|
||||||
|
import UserSearch from '../components/UserSearch';
|
||||||
import ProcessInstanceLogList from '../components/ProcessInstanceLogList';
|
import ProcessInstanceLogList from '../components/ProcessInstanceLogList';
|
||||||
import MessageInstanceList from '../components/MessageInstanceList';
|
import MessageInstanceList from '../components/MessageInstanceList';
|
||||||
|
|
||||||
|
@ -68,6 +79,8 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const eventsThatNeedPayload = ['MessageEventDefinition'];
|
||||||
|
|
||||||
const [processInstance, setProcessInstance] =
|
const [processInstance, setProcessInstance] =
|
||||||
useState<ProcessInstance | null>(null);
|
useState<ProcessInstance | null>(null);
|
||||||
const [tasks, setTasks] = useState<Task[] | null>(null);
|
const [tasks, setTasks] = useState<Task[] | null>(null);
|
||||||
|
@ -89,6 +102,12 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
const [eventTextEditorEnabled, setEventTextEditorEnabled] =
|
const [eventTextEditorEnabled, setEventTextEditorEnabled] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
|
||||||
|
const [addingPotentialOwners, setAddingPotentialOwners] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
const [additionalPotentialOwners, setAdditionalPotentialOwners] = useState<
|
||||||
|
User[] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
const { addError, removeError } = useAPIError();
|
const { addError, removeError } = useAPIError();
|
||||||
const unModifiedProcessModelId = unModifyProcessIdentifierForPathParam(
|
const unModifiedProcessModelId = unModifyProcessIdentifierForPathParam(
|
||||||
`${params.process_model_id}`
|
`${params.process_model_id}`
|
||||||
|
@ -109,6 +128,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
[targetUris.messageInstanceListPath]: ['GET'],
|
[targetUris.messageInstanceListPath]: ['GET'],
|
||||||
[targetUris.processInstanceActionPath]: ['DELETE', 'GET'],
|
[targetUris.processInstanceActionPath]: ['DELETE', 'GET'],
|
||||||
[targetUris.processInstanceLogListPath]: ['GET'],
|
[targetUris.processInstanceLogListPath]: ['GET'],
|
||||||
|
[targetUris.processInstanceTaskAssignPath]: ['POST'],
|
||||||
[targetUris.processInstanceTaskDataPath]: ['GET', 'PUT'],
|
[targetUris.processInstanceTaskDataPath]: ['GET', 'PUT'],
|
||||||
[targetUris.processInstanceSendEventPath]: ['POST'],
|
[targetUris.processInstanceSendEventPath]: ['POST'],
|
||||||
[targetUris.processInstanceCompleteTaskPath]: ['POST'],
|
[targetUris.processInstanceCompleteTaskPath]: ['POST'],
|
||||||
|
@ -539,9 +559,22 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetTaskActionDetails = () => {
|
||||||
|
setEditingTaskData(false);
|
||||||
|
setSelectingEvent(false);
|
||||||
|
setAddingPotentialOwners(false);
|
||||||
|
initializeTaskDataToDisplay(taskToDisplay);
|
||||||
|
setEventPayload('{}');
|
||||||
|
setAdditionalPotentialOwners(null);
|
||||||
|
removeError();
|
||||||
|
};
|
||||||
|
|
||||||
const handleTaskDataDisplayClose = () => {
|
const handleTaskDataDisplayClose = () => {
|
||||||
setTaskToDisplay(null);
|
setTaskToDisplay(null);
|
||||||
initializeTaskDataToDisplay(null);
|
initializeTaskDataToDisplay(null);
|
||||||
|
if (editingTaskData || selectingEvent || addingPotentialOwners) {
|
||||||
|
resetTaskActionDetails();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTaskById = (taskId: string) => {
|
const getTaskById = (taskId: string) => {
|
||||||
|
@ -602,8 +635,9 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
|
|
||||||
const canSendEvent = (task: Task) => {
|
const canSendEvent = (task: Task) => {
|
||||||
// We actually could allow this for any waiting events
|
// We actually could allow this for any waiting events
|
||||||
const taskTypes = ['Event Based Gateway'];
|
const taskTypes = ['EventBasedGateway'];
|
||||||
return (
|
return (
|
||||||
|
!selectingEvent &&
|
||||||
processInstance &&
|
processInstance &&
|
||||||
processInstance.status === 'waiting' &&
|
processInstance.status === 'waiting' &&
|
||||||
ability.can('POST', targetUris.processInstanceSendEventPath) &&
|
ability.can('POST', targetUris.processInstanceSendEventPath) &&
|
||||||
|
@ -623,6 +657,17 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const canAddPotentialOwners = (task: Task) => {
|
||||||
|
return (
|
||||||
|
HUMAN_TASK_TYPES.includes(task.typename) &&
|
||||||
|
processInstance &&
|
||||||
|
processInstance.status === 'suspended' &&
|
||||||
|
ability.can('POST', targetUris.processInstanceTaskAssignPath) &&
|
||||||
|
isActiveTask(task) &&
|
||||||
|
showingActiveTask()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const canResetProcess = (task: Task) => {
|
const canResetProcess = (task: Task) => {
|
||||||
return (
|
return (
|
||||||
ability.can('POST', targetUris.processInstanceResetPath) &&
|
ability.can('POST', targetUris.processInstanceResetPath) &&
|
||||||
|
@ -635,7 +680,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
|
|
||||||
const getEvents = (task: Task) => {
|
const getEvents = (task: Task) => {
|
||||||
const handleMessage = (eventDefinition: EventDefinition) => {
|
const handleMessage = (eventDefinition: EventDefinition) => {
|
||||||
if (eventDefinition.typename === 'MessageEventDefinition') {
|
if (eventsThatNeedPayload.includes(eventDefinition.typename)) {
|
||||||
const newEvent = eventDefinition;
|
const newEvent = eventDefinition;
|
||||||
delete newEvent.message_var;
|
delete newEvent.message_var;
|
||||||
newEvent.payload = {};
|
newEvent.payload = {};
|
||||||
|
@ -643,22 +688,16 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
}
|
}
|
||||||
return eventDefinition;
|
return eventDefinition;
|
||||||
};
|
};
|
||||||
if (task.event_definition && task.event_definition.event_definitions)
|
const eventDefinition =
|
||||||
return task.event_definition.event_definitions.map((e: EventDefinition) =>
|
task.task_definition_properties_json.event_definition;
|
||||||
|
if (eventDefinition && eventDefinition.event_definitions)
|
||||||
|
return eventDefinition.event_definitions.map((e: EventDefinition) =>
|
||||||
handleMessage(e)
|
handleMessage(e)
|
||||||
);
|
);
|
||||||
if (task.event_definition) return [handleMessage(task.event_definition)];
|
if (eventDefinition) return [handleMessage(eventDefinition)];
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelUpdatingTask = () => {
|
|
||||||
setEditingTaskData(false);
|
|
||||||
setSelectingEvent(false);
|
|
||||||
initializeTaskDataToDisplay(taskToDisplay);
|
|
||||||
setEventPayload('{}');
|
|
||||||
removeError();
|
|
||||||
};
|
|
||||||
|
|
||||||
const taskDataStringToObject = (dataString: string) => {
|
const taskDataStringToObject = (dataString: string) => {
|
||||||
return JSON.parse(dataString);
|
return JSON.parse(dataString);
|
||||||
};
|
};
|
||||||
|
@ -673,7 +712,6 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
}; // spread operator
|
}; // spread operator
|
||||||
setTaskToDisplay(taskToDisplayCopy);
|
setTaskToDisplay(taskToDisplayCopy);
|
||||||
}
|
}
|
||||||
refreshPage();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveTaskData = () => {
|
const saveTaskData = () => {
|
||||||
|
@ -695,13 +733,35 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addPotentialOwners = () => {
|
||||||
|
if (!additionalPotentialOwners) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!taskToDisplay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeError();
|
||||||
|
|
||||||
|
const userIds = additionalPotentialOwners.map((user: User) => user.id);
|
||||||
|
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `${targetUris.processInstanceTaskAssignPath}/${taskToDisplay.guid}`,
|
||||||
|
httpMethod: 'POST',
|
||||||
|
successCallback: resetTaskActionDetails,
|
||||||
|
failureCallback: addError,
|
||||||
|
postBody: {
|
||||||
|
user_ids: userIds,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const sendEvent = () => {
|
const sendEvent = () => {
|
||||||
if ('payload' in eventToSend)
|
if ('payload' in eventToSend)
|
||||||
eventToSend.payload = JSON.parse(eventPayload);
|
eventToSend.payload = JSON.parse(eventPayload);
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: targetUris.processInstanceSendEventPath,
|
path: targetUris.processInstanceSendEventPath,
|
||||||
httpMethod: 'POST',
|
httpMethod: 'POST',
|
||||||
successCallback: saveTaskDataResult,
|
successCallback: refreshPage,
|
||||||
failureCallback: addError,
|
failureCallback: addError,
|
||||||
postBody: eventToSend,
|
postBody: eventToSend,
|
||||||
});
|
});
|
||||||
|
@ -720,18 +780,24 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
|
|
||||||
const taskDisplayButtons = (task: Task) => {
|
const taskDisplayButtons = (task: Task) => {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
if (editingTaskData || addingPotentialOwners || selectingEvent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
task.typename === 'Script Task' &&
|
task.typename === 'ScriptTask' &&
|
||||||
ability.can('PUT', targetUris.processModelShowPath)
|
ability.can('PUT', targetUris.processModelShowPath)
|
||||||
) {
|
) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
|
kind="ghost"
|
||||||
|
align="top-left"
|
||||||
|
renderIcon={RuleDraft}
|
||||||
|
iconDescription="Create Script Unit Test"
|
||||||
|
hasIconOnly
|
||||||
data-qa="create-script-unit-test-button"
|
data-qa="create-script-unit-test-button"
|
||||||
onClick={createScriptUnitTest}
|
onClick={createScriptUnitTest}
|
||||||
>
|
/>
|
||||||
Create Script Unit Test
|
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -749,90 +815,94 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editingTaskData) {
|
if (canEditTaskData(task)) {
|
||||||
buttons.push(
|
|
||||||
<Button data-qa="save-task-data-button" onClick={saveTaskData}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
data-qa="cancel-task-data-edit-button"
|
kind="ghost"
|
||||||
onClick={cancelUpdatingTask}
|
renderIcon={Edit}
|
||||||
>
|
align="top-left"
|
||||||
Cancel
|
iconDescription="Edit Task Data"
|
||||||
</Button>
|
hasIconOnly
|
||||||
|
data-qa="edit-task-data-button"
|
||||||
|
onClick={() => setEditingTaskData(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (canAddPotentialOwners(task)) {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
kind="ghost"
|
||||||
|
renderIcon={UserFollow}
|
||||||
|
align="top-left"
|
||||||
|
iconDescription="Assign user"
|
||||||
|
title="Allow an additional user to complete this task"
|
||||||
|
hasIconOnly
|
||||||
|
data-qa="add-potential-owners-button"
|
||||||
|
onClick={() => setAddingPotentialOwners(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (canCompleteTask(task)) {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
kind="ghost"
|
||||||
|
renderIcon={Play}
|
||||||
|
align="top-left"
|
||||||
|
iconDescription="Execute Task"
|
||||||
|
hasIconOnly
|
||||||
|
data-qa="execute-task-complete-button"
|
||||||
|
onClick={() => completeTask(true)}
|
||||||
|
>
|
||||||
|
Execute Task
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
kind="ghost"
|
||||||
|
renderIcon={SkipForward}
|
||||||
|
align="top-left"
|
||||||
|
iconDescription="Skip Task"
|
||||||
|
hasIconOnly
|
||||||
|
data-qa="mark-task-complete-button"
|
||||||
|
onClick={() => completeTask(false)}
|
||||||
|
>
|
||||||
|
Skip Task
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (canSendEvent(task)) {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
kind="ghost"
|
||||||
|
renderIcon={Send}
|
||||||
|
align="top-left"
|
||||||
|
iconDescription="Send Event"
|
||||||
|
hasIconOnly
|
||||||
|
data-qa="select-event-button"
|
||||||
|
onClick={() => setSelectingEvent(true)}
|
||||||
|
>
|
||||||
|
Send Event
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (canResetProcess(task)) {
|
||||||
|
let titleText =
|
||||||
|
'This will reset (rewind) the process to put it into a state as if the execution of the process never went past this task. ';
|
||||||
|
titleText += 'Yes, we invented a time machine. ';
|
||||||
|
titleText +=
|
||||||
|
'And no, you cannot change your mind after using this feature.';
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
kind="ghost"
|
||||||
|
renderIcon={Reset}
|
||||||
|
hasIconOnly
|
||||||
|
iconDescription="Reset Process Here"
|
||||||
|
title={titleText}
|
||||||
|
data-qa="reset-process-button"
|
||||||
|
onClick={() => resetProcessInstance()}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else if (selectingEvent) {
|
|
||||||
buttons.push(
|
|
||||||
<Button data-qa="send-event-button" onClick={sendEvent}>
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
data-qa="cancel-task-data-edit-button"
|
|
||||||
onClick={cancelUpdatingTask}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (canEditTaskData(task)) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
data-qa="edit-task-data-button"
|
|
||||||
onClick={() => setEditingTaskData(true)}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (canCompleteTask(task)) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
data-qa="mark-task-complete-button"
|
|
||||||
onClick={() => completeTask(false)}
|
|
||||||
>
|
|
||||||
Skip Task
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
data-qa="execute-task-complete-button"
|
|
||||||
onClick={() => completeTask(true)}
|
|
||||||
>
|
|
||||||
Execute Task
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (canSendEvent(task)) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
data-qa="select-event-button"
|
|
||||||
onClick={() => setSelectingEvent(true)}
|
|
||||||
>
|
|
||||||
Send Event
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (canResetProcess(task)) {
|
|
||||||
let titleText =
|
|
||||||
'This will reset (rewind) the process to put it into a state as if the execution of the process never went past this task. ';
|
|
||||||
titleText += 'Yes, we invented a time machine. ';
|
|
||||||
titleText += 'And no, you cannot go back after using this feature.';
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
title={titleText}
|
|
||||||
data-qa="reset-process-button"
|
|
||||||
onClick={() => resetProcessInstance()}
|
|
||||||
>
|
|
||||||
Reset Process Here
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buttons;
|
return buttons;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -841,104 +911,211 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
if (taskDataToDisplay.startsWith('ERROR:')) {
|
if (taskDataToDisplay.startsWith('ERROR:')) {
|
||||||
taskDataClassName = 'failure-string';
|
taskDataClassName = 'failure-string';
|
||||||
}
|
}
|
||||||
return editingTaskData ? (
|
const numberOfLines = taskDataToDisplay.split('\n').length;
|
||||||
<Editor
|
let heightInEm = numberOfLines + 5;
|
||||||
height={600}
|
let scrollEnabled = false;
|
||||||
width="auto"
|
let minimapEnabled = false;
|
||||||
defaultLanguage="json"
|
if (heightInEm > 30) {
|
||||||
defaultValue={taskDataToDisplay}
|
heightInEm = 30;
|
||||||
onChange={(value) => setTaskDataToDisplay(value || '')}
|
scrollEnabled = true;
|
||||||
/>
|
minimapEnabled = true;
|
||||||
) : (
|
}
|
||||||
|
let taskDataHeader = 'Task data';
|
||||||
|
let editorReadOnly = true;
|
||||||
|
let taskDataHeaderClassName = 'with-half-rem-bottom-margin';
|
||||||
|
|
||||||
|
if (editingTaskData) {
|
||||||
|
editorReadOnly = false;
|
||||||
|
taskDataHeader = 'Edit task data';
|
||||||
|
taskDataHeaderClassName = 'task-data-details-header';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!taskDataToDisplay) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
{showTaskDataLoading ? (
|
{showTaskDataLoading ? (
|
||||||
<Loading className="some-class" withOverlay={false} small />
|
<Loading className="some-class" withOverlay={false} small />
|
||||||
) : null}
|
) : null}
|
||||||
<pre className={taskDataClassName}>{taskDataToDisplay}</pre>
|
{taskDataClassName !== '' ? (
|
||||||
|
<pre className={taskDataClassName}>{taskDataToDisplay}</pre>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<h3 className={taskDataHeaderClassName}>{taskDataHeader}</h3>
|
||||||
|
<Editor
|
||||||
|
height={`${heightInEm}rem`}
|
||||||
|
width="auto"
|
||||||
|
defaultLanguage="json"
|
||||||
|
defaultValue={taskDataToDisplay}
|
||||||
|
onChange={(value) => {
|
||||||
|
setTaskDataToDisplay(value || '');
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
readOnly: editorReadOnly,
|
||||||
|
scrollBeyondLastLine: scrollEnabled,
|
||||||
|
minimap: { enabled: minimapEnabled },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const eventSelector = (candidateEvents: any) => {
|
const potentialOwnerSelector = () => {
|
||||||
const editor = (
|
return (
|
||||||
<Editor
|
|
||||||
height={300}
|
|
||||||
width="auto"
|
|
||||||
defaultLanguage="json"
|
|
||||||
defaultValue={eventPayload}
|
|
||||||
onChange={(value: any) => setEventPayload(value || '{}')}
|
|
||||||
options={{ readOnly: !eventTextEditorEnabled }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
return selectingEvent ? (
|
|
||||||
<Stack orientation="vertical">
|
<Stack orientation="vertical">
|
||||||
<Dropdown
|
<h3 className="task-data-details-header">Update task ownership</h3>
|
||||||
id="process-instance-select-event"
|
<div className="indented-content">
|
||||||
titleText="Event"
|
<p className="explanatory-message with-tiny-bottom-margin">
|
||||||
label="Select Event"
|
Select a user who should be allowed to complete this task
|
||||||
items={candidateEvents}
|
</p>
|
||||||
itemToString={(item: any) => item.name || item.label || item.typename}
|
<UserSearch
|
||||||
onChange={(value: any) => {
|
className="modal-dropdown"
|
||||||
setEventToSend(value.selectedItem);
|
onSelectedUser={(user: User) => {
|
||||||
setEventTextEditorEnabled(
|
setAdditionalPotentialOwners([user]);
|
||||||
value.selectedItem.typename === 'MessageEventDefinition'
|
}}
|
||||||
);
|
/>
|
||||||
}}
|
</div>
|
||||||
/>
|
|
||||||
{editor}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
|
||||||
taskDataContainer()
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const eventSelector = (candidateEvents: any) => {
|
||||||
|
let editor = null;
|
||||||
|
let className = 'modal-dropdown';
|
||||||
|
if (eventTextEditorEnabled) {
|
||||||
|
className = '';
|
||||||
|
editor = (
|
||||||
|
<Editor
|
||||||
|
height={300}
|
||||||
|
width="auto"
|
||||||
|
defaultLanguage="json"
|
||||||
|
defaultValue={eventPayload}
|
||||||
|
onChange={(value: any) => setEventPayload(value || '{}')}
|
||||||
|
options={{ readOnly: !eventTextEditorEnabled }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Stack orientation="vertical">
|
||||||
|
<h3 className="task-data-details-header">Choose event to send</h3>
|
||||||
|
<div className="indented-content">
|
||||||
|
<p className="explanatory-message with-tiny-bottom-margin">
|
||||||
|
Select an event to send. A message event will require a body as
|
||||||
|
well.
|
||||||
|
</p>
|
||||||
|
<Dropdown
|
||||||
|
id="process-instance-select-event"
|
||||||
|
className={className}
|
||||||
|
label="Select Event"
|
||||||
|
items={candidateEvents}
|
||||||
|
itemToString={(item: any) =>
|
||||||
|
item.name || item.label || item.typename
|
||||||
|
}
|
||||||
|
onChange={(value: any) => {
|
||||||
|
setEventToSend(value.selectedItem);
|
||||||
|
setEventTextEditorEnabled(
|
||||||
|
eventsThatNeedPayload.includes(value.selectedItem.typename)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{editor}
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const taskActionDetails = () => {
|
||||||
|
if (!taskToDisplay) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let dataArea = taskDataContainer();
|
||||||
|
if (selectingEvent) {
|
||||||
|
const candidateEvents: any = getEvents(taskToDisplay);
|
||||||
|
dataArea = eventSelector(candidateEvents);
|
||||||
|
} else if (addingPotentialOwners) {
|
||||||
|
dataArea = potentialOwnerSelector();
|
||||||
|
}
|
||||||
|
return dataArea;
|
||||||
|
};
|
||||||
|
|
||||||
const taskUpdateDisplayArea = () => {
|
const taskUpdateDisplayArea = () => {
|
||||||
if (!taskToDisplay) {
|
if (!taskToDisplay) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const taskToUse: Task = { ...taskToDisplay, data: taskDataToDisplay };
|
const taskToUse: Task = { ...taskToDisplay, data: taskDataToDisplay };
|
||||||
const candidateEvents: any = getEvents(taskToUse);
|
|
||||||
if (taskToDisplay) {
|
let primaryButtonText = 'Close';
|
||||||
let taskTitleText = taskToUse.guid;
|
let secondaryButtonText = null;
|
||||||
if (taskToUse.bpmn_name) {
|
let onRequestSubmit = handleTaskDataDisplayClose;
|
||||||
taskTitleText += ` (${taskToUse.bpmn_name})`;
|
let onSecondarySubmit = handleTaskDataDisplayClose;
|
||||||
}
|
let dangerous = false;
|
||||||
return (
|
if (editingTaskData) {
|
||||||
<Modal
|
primaryButtonText = 'Save';
|
||||||
open={!!taskToUse}
|
secondaryButtonText = 'Cancel';
|
||||||
passiveModal
|
onSecondarySubmit = resetTaskActionDetails;
|
||||||
onRequestClose={handleTaskDataDisplayClose}
|
onRequestSubmit = saveTaskData;
|
||||||
>
|
dangerous = true;
|
||||||
<Stack orientation="horizontal" gap={2}>
|
} else if (selectingEvent) {
|
||||||
<span title={taskTitleText}>{taskToUse.bpmn_identifier}</span> (
|
primaryButtonText = 'Send';
|
||||||
{taskToUse.typename}
|
secondaryButtonText = 'Cancel';
|
||||||
): {taskToUse.state}
|
onSecondarySubmit = resetTaskActionDetails;
|
||||||
{taskDisplayButtons(taskToUse)}
|
onRequestSubmit = sendEvent;
|
||||||
</Stack>
|
dangerous = true;
|
||||||
|
} else if (addingPotentialOwners) {
|
||||||
|
primaryButtonText = 'Add';
|
||||||
|
secondaryButtonText = 'Cancel';
|
||||||
|
onSecondarySubmit = resetTaskActionDetails;
|
||||||
|
onRequestSubmit = addPotentialOwners;
|
||||||
|
dangerous = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={!!taskToUse}
|
||||||
|
danger={dangerous}
|
||||||
|
primaryButtonText={primaryButtonText}
|
||||||
|
secondaryButtonText={secondaryButtonText}
|
||||||
|
onRequestClose={handleTaskDataDisplayClose}
|
||||||
|
onSecondarySubmit={onSecondarySubmit}
|
||||||
|
onRequestSubmit={onRequestSubmit}
|
||||||
|
modalHeading={`${taskToUse.bpmn_identifier} (${taskToUse.typename}
|
||||||
|
): ${taskToUse.state}`}
|
||||||
|
>
|
||||||
|
<div className="indented-content explanatory-message">
|
||||||
|
{taskToUse.bpmn_name ? (
|
||||||
|
<div>
|
||||||
|
<Stack orientation="horizontal" gap={2}>
|
||||||
|
Name: {taskToUse.bpmn_name}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Stack orientation="horizontal" gap={2}>
|
<Stack orientation="horizontal" gap={2}>
|
||||||
Guid: {taskToUse.guid}
|
Guid: {taskToUse.guid}
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
{taskToUse.state === 'COMPLETED' ? (
|
</div>
|
||||||
<div>
|
<ButtonSet>{taskDisplayButtons(taskToUse)}</ButtonSet>
|
||||||
<Stack orientation="horizontal" gap={2}>
|
{taskToUse.state === 'COMPLETED' ? (
|
||||||
{completionViewLink(
|
<div>
|
||||||
'View process instance at the time when this task was active.',
|
<Stack orientation="horizontal" gap={2}>
|
||||||
taskToUse.guid
|
{completionViewLink(
|
||||||
)}
|
'View process instance at the time when this task was active.',
|
||||||
</Stack>
|
taskToUse.guid
|
||||||
<br />
|
)}
|
||||||
<br />
|
</Stack>
|
||||||
</div>
|
<br />
|
||||||
) : null}
|
<br />
|
||||||
{selectingEvent
|
</div>
|
||||||
? eventSelector(candidateEvents)
|
) : null}
|
||||||
: taskDataContainer()}
|
{taskActionDetails()}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonIcons = () => {
|
const buttonIcons = () => {
|
||||||
|
|
|
@ -71,9 +71,6 @@ export default function SecretNew() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ButtonSet>
|
<ButtonSet>
|
||||||
<Button kind="primary" type="submit">
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
kind=""
|
kind=""
|
||||||
className="button-white-background"
|
className="button-white-background"
|
||||||
|
@ -81,6 +78,9 @@ export default function SecretNew() {
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button kind="primary" type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
</ButtonSet>
|
</ButtonSet>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
Loading…
Reference in New Issue