fix conflicts yet again

This commit is contained in:
Elizabeth Esswein 2022-12-19 15:28:44 -05:00
commit 4390e90a2b
12 changed files with 259 additions and 156 deletions

View File

@ -14,12 +14,19 @@ git_commit_message="$2"
git_branch="$3" git_branch="$3"
git_commit_username="$4" git_commit_username="$4"
git_commit_email="$5" git_commit_email="$5"
git_commit_password="$6"
if [[ -z "${5:-}" ]]; then if [[ -z "${6:-}" ]]; then
>&2 echo "usage: $(basename "$0") [bpmn_models_absolute_dir] [git_commit_message] [git_branch] [git_commit_username] [git_commit_email]" >&2 echo "usage: $(basename "$0") [bpmn_models_absolute_dir] [git_commit_message] [git_branch] [git_commit_username] [git_commit_email]"
exit 1 exit 1
fi fi
function failed_to_get_lock() {
>&2 echo "ERROR: Failed to get lock."
exit 1
}
function run() {
cd "$bpmn_models_absolute_dir" cd "$bpmn_models_absolute_dir"
git add . git add .
@ -27,8 +34,19 @@ git add .
if [ -z "$(git status --porcelain)" ]; then if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit" echo "No changes to commit"
else else
PAT="${git_commit_username}:${git_commit_password}"
AUTH=$(echo -n "$PAT" | openssl base64 | tr -d '\n')
git config --local user.name "$git_commit_username" git config --local user.name "$git_commit_username"
git config --local user.email "$git_commit_email" git config --local user.email "$git_commit_email"
git config --local http.extraHeader "Authorization: Basic $AUTH"
git commit -m "$git_commit_message" git commit -m "$git_commit_message"
git push --set-upstream origin "$git_branch" git push --set-upstream origin "$git_branch"
git config --unset --local http.extraHeader
fi fi
}
exec {lock_fd}>/var/lock/mylockfile || failed_to_get_lock
flock --timeout 60 "$lock_fd" || failed_to_get_lock
run
flock -u "$lock_fd"

View File

@ -1,8 +1,8 @@
"""empty message """empty message
Revision ID: 4d75421c0af0 Revision ID: e284612a9778
Revises: Revises:
Create Date: 2022-12-06 17:42:56.417673 Create Date: 2022-12-19 11:23:10.567440
""" """
from alembic import op from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '4d75421c0af0' revision = 'e284612a9778'
down_revision = None down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@ -77,6 +77,8 @@ def upgrade():
sa.Column('service_id', sa.String(length=255), nullable=False), sa.Column('service_id', sa.String(length=255), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True), sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('email', sa.String(length=255), nullable=True), sa.Column('email', sa.String(length=255), nullable=True),
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True),
sa.Column('created_at_in_seconds', sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('service', 'service_id', name='service_key'), sa.UniqueConstraint('service', 'service_id', name='service_key'),
sa.UniqueConstraint('uid') sa.UniqueConstraint('uid')

View File

@ -798,7 +798,7 @@ paths:
schema: schema:
$ref: "#/components/schemas/Workflow" $ref: "#/components/schemas/Workflow"
/process-instances/{modified_process_model_identifier}/{process_instance_id}/terminate: /process-instance-terminate/{modified_process_model_identifier}/{process_instance_id}:
parameters: parameters:
- name: process_instance_id - name: process_instance_id
in: path in: path
@ -819,7 +819,7 @@ paths:
schema: schema:
$ref: "#/components/schemas/OkTrue" $ref: "#/components/schemas/OkTrue"
/process-instances/{modified_process_model_identifier}/{process_instance_id}/suspend: /process-instance-suspend/{modified_process_model_identifier}/{process_instance_id}:
parameters: parameters:
- name: process_instance_id - name: process_instance_id
in: path in: path
@ -840,7 +840,7 @@ paths:
schema: schema:
$ref: "#/components/schemas/OkTrue" $ref: "#/components/schemas/OkTrue"
/process-instances/{modified_process_model_identifier}/{process_instance_id}/resume: /process-instance-resume/{modified_process_model_identifier}/{process_instance_id}:
parameters: parameters:
- name: process_instance_id - name: process_instance_id
in: path in: path

View File

@ -26,6 +26,14 @@ class ProcessInstanceNotFoundError(Exception):
"""ProcessInstanceNotFoundError.""" """ProcessInstanceNotFoundError."""
class ProcessInstanceTaskDataCannotBeUpdatedError(Exception):
"""ProcessInstanceTaskDataCannotBeUpdatedError."""
class ProcessInstanceCannotBeDeletedError(Exception):
"""ProcessInstanceCannotBeDeletedError."""
class NavigationItemSchema(Schema): class NavigationItemSchema(Schema):
"""NavigationItemSchema.""" """NavigationItemSchema."""
@ -131,6 +139,15 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
"""Validate_status.""" """Validate_status."""
return self.validate_enum_field(key, value, ProcessInstanceStatus) return self.validate_enum_field(key, value, ProcessInstanceStatus)
def has_terminal_status(self) -> bool:
"""Has_terminal_status."""
return self.status in self.terminal_statuses()
@classmethod
def terminal_statuses(cls) -> list[str]:
"""Terminal_statuses."""
return ["complete", "error", "terminated"]
class ProcessInstanceModelSchema(Schema): class ProcessInstanceModelSchema(Schema):
"""ProcessInstanceModelSchema.""" """ProcessInstanceModelSchema."""

View File

@ -37,6 +37,8 @@ class UserModel(SpiffworkflowBaseDBModel):
service_id = db.Column(db.String(255), nullable=False, unique=False) service_id = db.Column(db.String(255), nullable=False, unique=False)
name = db.Column(db.String(255)) name = db.Column(db.String(255))
email = db.Column(db.String(255)) email = db.Column(db.String(255))
updated_at_in_seconds: int = db.Column(db.Integer)
created_at_in_seconds: int = db.Column(db.Integer)
user_group_assignments = relationship("UserGroupAssignmentModel", cascade="delete") # type: ignore user_group_assignments = relationship("UserGroupAssignmentModel", cascade="delete") # type: ignore
groups = relationship( # type: ignore groups = relationship( # type: ignore

View File

@ -53,9 +53,15 @@ from spiffworkflow_backend.models.principal import PrincipalModel
from spiffworkflow_backend.models.process_group import ProcessGroup from spiffworkflow_backend.models.process_group import ProcessGroup
from spiffworkflow_backend.models.process_group import ProcessGroupSchema from spiffworkflow_backend.models.process_group import ProcessGroupSchema
from spiffworkflow_backend.models.process_instance import ProcessInstanceApiSchema from spiffworkflow_backend.models.process_instance import ProcessInstanceApiSchema
from spiffworkflow_backend.models.process_instance import (
ProcessInstanceCannotBeDeletedError,
)
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
from spiffworkflow_backend.models.process_instance import (
ProcessInstanceTaskDataCannotBeUpdatedError,
)
from spiffworkflow_backend.models.process_instance_metadata import ( from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel, ProcessInstanceMetadataModel,
) )
@ -577,6 +583,13 @@ def process_instance_run(
process_instance = ProcessInstanceService().get_process_instance( process_instance = ProcessInstanceService().get_process_instance(
process_instance_id process_instance_id
) )
if process_instance.status != "not_started":
raise ApiError(
error_code="process_instance_not_runnable",
message=f"Process Instance ({process_instance.id}) is currently running or has already run.",
status_code=400,
)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
if do_engine_steps: if do_engine_steps:
@ -935,7 +948,7 @@ def process_instance_list(
if report_filter.initiated_by_me is True: if report_filter.initiated_by_me is True:
process_instance_query = process_instance_query.filter( process_instance_query = process_instance_query.filter(
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore
) )
process_instance_query = process_instance_query.filter_by( process_instance_query = process_instance_query.filter_by(
process_initiator=g.user process_initiator=g.user
@ -944,7 +957,7 @@ def process_instance_list(
# TODO: not sure if this is exactly what is wanted # TODO: not sure if this is exactly what is wanted
if report_filter.with_tasks_completed_by_me is True: if report_filter.with_tasks_completed_by_me is True:
process_instance_query = process_instance_query.filter( process_instance_query = process_instance_query.filter(
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore
) )
# process_instance_query = process_instance_query.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id) # process_instance_query = process_instance_query.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
# process_instance_query = process_instance_query.add_columns(UserModel.username) # process_instance_query = process_instance_query.add_columns(UserModel.username)
@ -973,7 +986,7 @@ def process_instance_list(
if report_filter.with_tasks_completed_by_my_group is True: if report_filter.with_tasks_completed_by_my_group is True:
process_instance_query = process_instance_query.filter( process_instance_query = process_instance_query.filter(
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore
) )
process_instance_query = process_instance_query.join( process_instance_query = process_instance_query.join(
SpiffStepDetailsModel, SpiffStepDetailsModel,
@ -1162,6 +1175,12 @@ def process_instance_delete(
"""Create_process_instance.""" """Create_process_instance."""
process_instance = find_process_instance_by_id_or_raise(process_instance_id) process_instance = find_process_instance_by_id_or_raise(process_instance_id)
if not process_instance.has_terminal_status():
raise ProcessInstanceCannotBeDeletedError(
f"Process instance ({process_instance.id}) cannot be deleted since it does not have a terminal status. "
f"Current status is {process_instance.status}."
)
# (Pdb) db.session.delete # (Pdb) db.session.delete
# <bound method delete of <sqlalchemy.orm.scoping.scoped_session object at 0x103eaab30>> # <bound method delete of <sqlalchemy.orm.scoping.scoped_session object at 0x103eaab30>>
db.session.query(SpiffLoggingModel).filter_by( db.session.query(SpiffLoggingModel).filter_by(
@ -2112,6 +2131,11 @@ def update_task_data(
ProcessInstanceModel.id == int(process_instance_id) ProcessInstanceModel.id == int(process_instance_id)
).first() ).first()
if process_instance: if process_instance:
if process_instance.status != "suspended":
raise ProcessInstanceTaskDataCannotBeUpdatedError(
f"The process instance needs to be suspended to udpate the task-data. It is currently: {process_instance.status}"
)
process_instance_bpmn_json_dict = json.loads(process_instance.bpmn_json) process_instance_bpmn_json_dict = json.loads(process_instance.bpmn_json)
if "new_task_data" in body: if "new_task_data" in body:
new_task_data_str: str = body["new_task_data"] new_task_data_str: str = body["new_task_data"]

View File

@ -100,6 +100,7 @@ class GitService:
branch_name_to_use, branch_name_to_use,
git_username, git_username,
git_email, git_email,
current_app.config["GIT_USER_PASSWORD"],
] ]
return cls.run_shell_command_to_get_stdout(shell_command) return cls.run_shell_command_to_get_stdout(shell_command)

View File

@ -1376,7 +1376,7 @@ class TestProcessApi(BaseTest):
assert response.json is not None assert response.json is not None
response = client.post( response = client.post(
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/terminate", f"/v1.0/process-instance-terminate/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}",
headers=self.logged_in_headers(with_super_admin_user), headers=self.logged_in_headers(with_super_admin_user),
) )
assert response.status_code == 200 assert response.status_code == 200
@ -1397,15 +1397,13 @@ class TestProcessApi(BaseTest):
) -> None: ) -> None:
"""Test_process_instance_delete.""" """Test_process_instance_delete."""
process_group_id = "my_process_group" process_group_id = "my_process_group"
process_model_id = "user_task" process_model_id = "sample"
bpmn_file_name = "user_task.bpmn" bpmn_file_location = "sample"
bpmn_file_location = "user_task"
process_model_identifier = self.create_group_and_model_with_bpmn( process_model_identifier = self.create_group_and_model_with_bpmn(
client, client,
with_super_admin_user, with_super_admin_user,
process_group_id=process_group_id, process_group_id=process_group_id,
process_model_id=process_model_id, process_model_id=process_model_id,
bpmn_file_name=bpmn_file_name,
bpmn_file_location=bpmn_file_location, bpmn_file_location=bpmn_file_location,
) )
@ -1421,11 +1419,13 @@ class TestProcessApi(BaseTest):
headers=self.logged_in_headers(with_super_admin_user), headers=self.logged_in_headers(with_super_admin_user),
) )
assert response.json is not None assert response.json is not None
assert response.status_code == 200
delete_response = client.delete( delete_response = client.delete(
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}", f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}",
headers=self.logged_in_headers(with_super_admin_user), headers=self.logged_in_headers(with_super_admin_user),
) )
assert delete_response.json["ok"] is True
assert delete_response.status_code == 200 assert delete_response.status_code == 200
def test_task_show( def test_task_show(
@ -2422,7 +2422,7 @@ class TestProcessApi(BaseTest):
assert process_instance.status == "user_input_required" assert process_instance.status == "user_input_required"
client.post( client.post(
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/suspend", f"/v1.0/process-instance-suspend/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}",
headers=self.logged_in_headers(with_super_admin_user), headers=self.logged_in_headers(with_super_admin_user),
) )
process_instance = ProcessInstanceService().get_process_instance( process_instance = ProcessInstanceService().get_process_instance(
@ -2430,15 +2430,25 @@ class TestProcessApi(BaseTest):
) )
assert process_instance.status == "suspended" assert process_instance.status == "suspended"
# TODO: Why can I run a suspended process instance?
response = client.post( response = client.post(
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run", f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
headers=self.logged_in_headers(with_super_admin_user), headers=self.logged_in_headers(with_super_admin_user),
) )
process_instance = ProcessInstanceService().get_process_instance(
process_instance_id
)
assert process_instance.status == "suspended"
assert response.status_code == 400
# task = response.json['next_task'] response = client.post(
f"/v1.0/process-instance-resume/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}",
print("test_process_instance_suspend") headers=self.logged_in_headers(with_super_admin_user),
)
assert response.status_code == 200
process_instance = ProcessInstanceService().get_process_instance(
process_instance_id
)
assert process_instance.status == "waiting"
def test_script_unit_test_run( def test_script_unit_test_run(
self, self,

View File

@ -0,0 +1,5 @@
export default class ProcessInstanceClass {
static terminalStatuses() {
return ['complete', 'error', 'terminated'];
}
}

View File

@ -11,11 +11,16 @@ export const useUriListForPermissions = () => {
processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`, processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`,
processInstanceCreatePath: `/v1.0/process-instances/${params.process_model_id}`, processInstanceCreatePath: `/v1.0/process-instances/${params.process_model_id}`,
processInstanceActionPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}`, processInstanceActionPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}`,
processInstanceResumePath: `/v1.0/process-instance-resume/${params.process_model_id}/${params.process_instance_id}`,
processInstanceSuspendPath: `/v1.0/process-instance-suspend/${params.process_model_id}/${params.process_instance_id}`,
processInstanceTerminatePath: `/v1.0/process-instance-terminate/${params.process_model_id}/${params.process_instance_id}`,
processInstanceListPath: '/v1.0/process-instances', processInstanceListPath: '/v1.0/process-instances',
processInstanceLogListPath: `/v1.0/logs/${params.process_model_id}/${params.process_instance_id}`, processInstanceLogListPath: `/v1.0/logs/${params.process_model_id}/${params.process_instance_id}`,
processInstanceReportListPath: '/v1.0/process-instances/reports', processInstanceReportListPath: '/v1.0/process-instances/reports',
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`,
processInstanceTaskListDataPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`, processInstanceTaskListDataPath: `/v1.0/task-data/${params.process_model_id}/${params.process_instance_id}`,
processInstanceSendEventPath: `/v1.0/send-event/${params.process_model_id}/${params.process_instance_id}`,
processInstanceCompleteTaskPath: `/v1.0/complete-task/${params.process_model_id}/${params.process_instance_id}`,
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`, processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`, processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`, processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,

View File

@ -52,6 +52,10 @@ export interface ProcessInstance {
id: number; id: number;
process_model_identifier: string; process_model_identifier: string;
process_model_display_name: string; process_model_display_name: string;
status: string;
start_in_seconds: number | null;
end_in_seconds: number | null;
bpmn_xml_file_contents?: string;
spiff_step?: number; spiff_step?: number;
} }

View File

@ -46,6 +46,7 @@ import {
ProcessInstanceTask, ProcessInstanceTask,
} from '../interfaces'; } from '../interfaces';
import { usePermissionFetcher } from '../hooks/PermissionService'; import { usePermissionFetcher } from '../hooks/PermissionService';
import ProcessInstanceClass from '../classes/ProcessInstanceClass';
export default function ProcessInstanceShow() { export default function ProcessInstanceShow() {
const navigate = useNavigate(); const navigate = useNavigate();
@ -77,12 +78,14 @@ export default function ProcessInstanceShow() {
[targetUris.messageInstanceListPath]: ['GET'], [targetUris.messageInstanceListPath]: ['GET'],
[targetUris.processInstanceTaskListPath]: ['GET'], [targetUris.processInstanceTaskListPath]: ['GET'],
[targetUris.processInstanceTaskListDataPath]: ['GET', 'PUT'], [targetUris.processInstanceTaskListDataPath]: ['GET', 'PUT'],
[targetUris.processInstanceSendEventPath]: ['POST'],
[targetUris.processInstanceCompleteTaskPath]: ['POST'],
[targetUris.processInstanceActionPath]: ['DELETE'], [targetUris.processInstanceActionPath]: ['DELETE'],
[targetUris.processInstanceLogListPath]: ['GET'], [targetUris.processInstanceLogListPath]: ['GET'],
[targetUris.processModelShowPath]: ['PUT'], [targetUris.processModelShowPath]: ['PUT'],
[`${targetUris.processInstanceActionPath}/suspend`]: ['POST'], [`${targetUris.processInstanceResumePath}`]: ['POST'],
[`${targetUris.processInstanceActionPath}/terminate`]: ['POST'], [`${targetUris.processInstanceSuspendPath}`]: ['POST'],
[`${targetUris.processInstanceActionPath}/resume`]: ['POST'], [`${targetUris.processInstanceTerminatePath}`]: ['POST'],
}; };
const { ability, permissionsLoaded } = usePermissionFetcher( const { ability, permissionsLoaded } = usePermissionFetcher(
permissionRequestData permissionRequestData
@ -152,7 +155,7 @@ export default function ProcessInstanceShow() {
const terminateProcessInstance = () => { const terminateProcessInstance = () => {
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `${targetUris.processInstanceActionPath}/terminate`, path: `${targetUris.processInstanceTerminatePath}`,
successCallback: refreshPage, successCallback: refreshPage,
httpMethod: 'POST', httpMethod: 'POST',
}); });
@ -160,7 +163,7 @@ export default function ProcessInstanceShow() {
const suspendProcessInstance = () => { const suspendProcessInstance = () => {
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `${targetUris.processInstanceActionPath}/suspend`, path: `${targetUris.processInstanceSuspendPath}`,
successCallback: refreshPage, successCallback: refreshPage,
httpMethod: 'POST', httpMethod: 'POST',
}); });
@ -168,7 +171,7 @@ export default function ProcessInstanceShow() {
const resumeProcessInstance = () => { const resumeProcessInstance = () => {
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `${targetUris.processInstanceActionPath}/resume`, path: `${targetUris.processInstanceResumePath}`,
successCallback: refreshPage, successCallback: refreshPage,
httpMethod: 'POST', httpMethod: 'POST',
}); });
@ -189,29 +192,23 @@ export default function ProcessInstanceShow() {
return taskIds; return taskIds;
}; };
const currentSpiffStep = (processInstanceToUse: any) => { const currentSpiffStep = () => {
if (typeof params.spiff_step === 'undefined') { if (processInstance && typeof params.spiff_step === 'undefined') {
return processInstanceToUse.spiff_step; return processInstance.spiff_step || 0;
} }
return Number(params.spiff_step); return Number(params.spiff_step);
}; };
const showingFirstSpiffStep = (processInstanceToUse: any) => { const showingFirstSpiffStep = () => {
return currentSpiffStep(processInstanceToUse) === 1; return currentSpiffStep() === 1;
}; };
const showingLastSpiffStep = (processInstanceToUse: any) => { const showingLastSpiffStep = () => {
return ( return processInstance && currentSpiffStep() === processInstance.spiff_step;
currentSpiffStep(processInstanceToUse) === processInstanceToUse.spiff_step
);
}; };
const spiffStepLink = ( const spiffStepLink = (label: any, distance: number) => {
processInstanceToUse: any,
label: any,
distance: number
) => {
const processIdentifier = searchParams.get('process_identifier'); const processIdentifier = searchParams.get('process_identifier');
let queryParams = ''; let queryParams = '';
if (processIdentifier) { if (processIdentifier) {
@ -223,32 +220,35 @@ export default function ProcessInstanceShow() {
data-qa="process-instance-step-link" data-qa="process-instance-step-link"
to={`/admin/process-instances/${params.process_model_id}/${ to={`/admin/process-instances/${params.process_model_id}/${
params.process_instance_id params.process_instance_id
}/${currentSpiffStep(processInstanceToUse) + distance}${queryParams}`} }/${currentSpiffStep() + distance}${queryParams}`}
> >
{label} {label}
</Link> </Link>
); );
}; };
const previousStepLink = (processInstanceToUse: any) => { const previousStepLink = () => {
if (showingFirstSpiffStep(processInstanceToUse)) { if (showingFirstSpiffStep()) {
return null; return null;
} }
return spiffStepLink(processInstanceToUse, <CaretLeft />, -1); return spiffStepLink(<CaretLeft />, -1);
}; };
const nextStepLink = (processInstanceToUse: any) => { const nextStepLink = () => {
if (showingLastSpiffStep(processInstanceToUse)) { if (showingLastSpiffStep()) {
return null; return null;
} }
return spiffStepLink(processInstanceToUse, <CaretRight />, 1); return spiffStepLink(<CaretRight />, 1);
}; };
const getInfoTag = (processInstanceToUse: any) => { const getInfoTag = () => {
if (!processInstance) {
return null;
}
const currentEndDate = convertSecondsToFormattedDateTime( const currentEndDate = convertSecondsToFormattedDateTime(
processInstanceToUse.end_in_seconds processInstance.end_in_seconds || 0
); );
let currentEndDateTag; let currentEndDateTag;
if (currentEndDate) { if (currentEndDate) {
@ -259,7 +259,7 @@ export default function ProcessInstanceShow() {
</Column> </Column>
<Column sm={3} md={3} lg={3} className="grid-date"> <Column sm={3} md={3} lg={3} className="grid-date">
{convertSecondsToFormattedDateTime( {convertSecondsToFormattedDateTime(
processInstanceToUse.end_in_seconds processInstance.end_in_seconds || 0
) || 'N/A'} ) || 'N/A'}
</Column> </Column>
</Grid> </Grid>
@ -267,13 +267,13 @@ export default function ProcessInstanceShow() {
} }
let statusIcon = <InProgress />; let statusIcon = <InProgress />;
if (processInstanceToUse.status === 'suspended') { if (processInstance.status === 'suspended') {
statusIcon = <PauseOutline />; statusIcon = <PauseOutline />;
} else if (processInstanceToUse.status === 'complete') { } else if (processInstance.status === 'complete') {
statusIcon = <Checkmark />; statusIcon = <Checkmark />;
} else if (processInstanceToUse.status === 'terminated') { } else if (processInstance.status === 'terminated') {
statusIcon = <StopOutline />; statusIcon = <StopOutline />;
} else if (processInstanceToUse.status === 'error') { } else if (processInstance.status === 'error') {
statusIcon = <Warning />; statusIcon = <Warning />;
} }
@ -285,7 +285,7 @@ export default function ProcessInstanceShow() {
</Column> </Column>
<Column sm={3} md={3} lg={3} className="grid-date"> <Column sm={3} md={3} lg={3} className="grid-date">
{convertSecondsToFormattedDateTime( {convertSecondsToFormattedDateTime(
processInstanceToUse.start_in_seconds processInstance.start_in_seconds || 0
)} )}
</Column> </Column>
</Grid> </Grid>
@ -296,7 +296,7 @@ export default function ProcessInstanceShow() {
</Column> </Column>
<Column sm={3} md={3} lg={3}> <Column sm={3} md={3} lg={3}>
<Tag type="gray" size="sm" className="span-tag"> <Tag type="gray" size="sm" className="span-tag">
{processInstanceToUse.status} {statusIcon} {processInstance.status} {statusIcon}
</Tag> </Tag>
</Column> </Column>
</Grid> </Grid>
@ -339,11 +339,10 @@ export default function ProcessInstanceShow() {
); );
}; };
const terminateButton = (processInstanceToUse: any) => { const terminateButton = () => {
if ( if (
['complete', 'terminated', 'error'].indexOf( processInstance &&
processInstanceToUse.status !ProcessInstanceClass.terminalStatuses().includes(processInstance.status)
) === -1
) { ) {
return ( return (
<ButtonWithConfirmation <ButtonWithConfirmation
@ -351,7 +350,7 @@ export default function ProcessInstanceShow() {
renderIcon={StopOutline} renderIcon={StopOutline}
iconDescription="Terminate" iconDescription="Terminate"
hasIconOnly hasIconOnly
description={`Terminate Process Instance: ${processInstanceToUse.id}`} description={`Terminate Process Instance: ${processInstance.id}`}
onConfirmation={terminateProcessInstance} onConfirmation={terminateProcessInstance}
confirmButtonLabel="Terminate" confirmButtonLabel="Terminate"
/> />
@ -360,11 +359,12 @@ export default function ProcessInstanceShow() {
return <div />; return <div />;
}; };
const suspendButton = (processInstanceToUse: any) => { const suspendButton = () => {
if ( if (
['complete', 'terminated', 'error', 'suspended'].indexOf( processInstance &&
processInstanceToUse.status !ProcessInstanceClass.terminalStatuses()
) === -1 .concat(['suspended'])
.includes(processInstance.status)
) { ) {
return ( return (
<Button <Button
@ -380,8 +380,8 @@ export default function ProcessInstanceShow() {
return <div />; return <div />;
}; };
const resumeButton = (processInstanceToUse: any) => { const resumeButton = () => {
if (processInstanceToUse.status === 'suspended') { if (processInstance && processInstance.status === 'suspended') {
return ( return (
<Button <Button
onClick={resumeProcessInstance} onClick={resumeProcessInstance}
@ -456,9 +456,11 @@ export default function ProcessInstanceShow() {
const canEditTaskData = (task: any) => { const canEditTaskData = (task: any) => {
return ( return (
processInstance &&
ability.can('PUT', targetUris.processInstanceTaskListDataPath) && ability.can('PUT', targetUris.processInstanceTaskListDataPath) &&
task.state === 'READY' && task.state === 'READY' &&
showingLastSpiffStep(processInstance as any) processInstance.status === 'suspended' &&
showingLastSpiffStep()
); );
}; };
@ -466,9 +468,18 @@ export default function ProcessInstanceShow() {
// 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 = ['Event Based Gateway'];
return ( return (
ability.can('POST', targetUris.processInstanceSendEventPath) &&
taskTypes.filter((t) => t === task.type).length > 0 && taskTypes.filter((t) => t === task.type).length > 0 &&
task.state === 'WAITING' && task.state === 'WAITING' &&
showingLastSpiffStep(processInstance as any) showingLastSpiffStep()
);
};
const canMarkTaskComplete = (task: any) => {
return (
ability.can('POST', targetUris.processInstanceCompleteTaskPath) &&
task.state === 'READY' &&
showingLastSpiffStep()
); );
}; };
@ -511,7 +522,7 @@ export default function ProcessInstanceShow() {
}; };
const saveTaskDataFailure = (result: any) => { const saveTaskDataFailure = (result: any) => {
setErrorMessage({ message: result.toString() }); setErrorMessage({ message: result.message });
}; };
const saveTaskData = () => { const saveTaskData = () => {
@ -585,7 +596,6 @@ export default function ProcessInstanceShow() {
); );
} }
if (canEditTaskData(task) || canSendEvent(task)) {
if (editingTaskData) { if (editingTaskData) {
buttons.push( buttons.push(
<Button data-qa="save-task-data-button" onClick={saveTaskData}> <Button data-qa="save-task-data-button" onClick={saveTaskData}>
@ -615,6 +625,7 @@ export default function ProcessInstanceShow() {
</Button> </Button>
); );
} else { } else {
if (canEditTaskData(task)) {
buttons.push( buttons.push(
<Button <Button
data-qa="edit-task-data-button" data-qa="edit-task-data-button"
@ -623,16 +634,8 @@ export default function ProcessInstanceShow() {
Edit Edit
</Button> </Button>
); );
if (canSendEvent(task)) { }
buttons.push( if (canMarkTaskComplete(task)) {
<Button
data-qa="select-event-button"
onClick={() => setSelectingEvent(true)}
>
Send Event
</Button>
);
} else {
buttons.push( buttons.push(
<Button <Button
data-qa="mark-task-complete-button" data-qa="mark-task-complete-button"
@ -642,6 +645,15 @@ export default function ProcessInstanceShow() {
</Button> </Button>
); );
} }
if (canSendEvent(task)) {
buttons.push(
<Button
data-qa="select-event-button"
onClick={() => setSelectingEvent(true)}
>
Send Event
</Button>
);
} }
} }
@ -718,37 +730,41 @@ export default function ProcessInstanceShow() {
return null; return null;
}; };
const stepsElement = (processInstanceToUse: any) => { const stepsElement = () => {
if (!processInstance) {
return null;
}
return ( return (
<Grid condensed fullWidth> <Grid condensed fullWidth>
<Column sm={3} md={3} lg={3}> <Column sm={3} md={3} lg={3}>
<Stack orientation="horizontal" gap={3} className="smaller-text"> <Stack orientation="horizontal" gap={3} className="smaller-text">
{previousStepLink(processInstanceToUse)} {previousStepLink()}
Step {currentSpiffStep(processInstanceToUse)} of{' '} Step {currentSpiffStep()} of {processInstance.spiff_step}
{processInstanceToUse.spiff_step} {nextStepLink()}
{nextStepLink(processInstanceToUse)}
</Stack> </Stack>
</Column> </Column>
</Grid> </Grid>
); );
}; };
const buttonIcons = (processInstanceToUse: any) => { const buttonIcons = () => {
if (!processInstance) {
return null;
}
const elements = []; const elements = [];
if ( if (ability.can('POST', `${targetUris.processInstanceTerminatePath}`)) {
ability.can('POST', `${targetUris.processInstanceActionPath}/terminate`) elements.push(terminateButton());
) { }
elements.push(terminateButton(processInstanceToUse)); if (ability.can('POST', `${targetUris.processInstanceSuspendPath}`)) {
elements.push(suspendButton());
}
if (ability.can('POST', `${targetUris.processInstanceResumePath}`)) {
elements.push(resumeButton());
} }
if ( if (
ability.can('POST', `${targetUris.processInstanceActionPath}/suspend`) ability.can('DELETE', targetUris.processInstanceActionPath) &&
ProcessInstanceClass.terminalStatuses().includes(processInstance.status)
) { ) {
elements.push(suspendButton(processInstanceToUse));
}
if (ability.can('POST', `${targetUris.processInstanceActionPath}/resume`)) {
elements.push(resumeButton(processInstanceToUse));
}
if (ability.can('DELETE', targetUris.processInstanceActionPath)) {
elements.push( elements.push(
<ButtonWithConfirmation <ButtonWithConfirmation
data-qa="process-instance-delete" data-qa="process-instance-delete"
@ -756,7 +772,7 @@ export default function ProcessInstanceShow() {
renderIcon={TrashCan} renderIcon={TrashCan}
iconDescription="Delete" iconDescription="Delete"
hasIconOnly hasIconOnly
description={`Delete Process Instance: ${processInstanceToUse.id}`} description={`Delete Process Instance: ${processInstance.id}`}
onConfirmation={deleteProcessInstance} onConfirmation={deleteProcessInstance}
confirmButtonLabel="Delete" confirmButtonLabel="Delete"
/> />
@ -766,7 +782,6 @@ export default function ProcessInstanceShow() {
}; };
if (processInstance && (tasks || tasksCallHadError)) { if (processInstance && (tasks || tasksCallHadError)) {
const processInstanceToUse = processInstance as any;
const taskIds = getTaskIds(); const taskIds = getTaskIds();
const processModelId = unModifyProcessIdentifierForPathParam( const processModelId = unModifyProcessIdentifierForPathParam(
params.process_model_id ? params.process_model_id : '' params.process_model_id ? params.process_model_id : ''
@ -782,26 +797,26 @@ export default function ProcessInstanceShow() {
entityType: 'process-model-id', entityType: 'process-model-id',
linkLastItem: true, linkLastItem: true,
}, },
[`Process Instance Id: ${processInstanceToUse.id}`], [`Process Instance Id: ${processInstance.id}`],
]} ]}
/> />
<Stack orientation="horizontal" gap={1}> <Stack orientation="horizontal" gap={1}>
<h1 className="with-icons"> <h1 className="with-icons">
Process Instance Id: {processInstanceToUse.id} Process Instance Id: {processInstance.id}
</h1> </h1>
{buttonIcons(processInstanceToUse)} {buttonIcons()}
</Stack> </Stack>
<br /> <br />
<br /> <br />
{getInfoTag(processInstanceToUse)} {getInfoTag()}
<br /> <br />
{taskUpdateDisplayArea()} {taskUpdateDisplayArea()}
{stepsElement(processInstanceToUse)} {stepsElement()}
<br /> <br />
<ReactDiagramEditor <ReactDiagramEditor
processModelId={processModelId || ''} processModelId={processModelId || ''}
diagramXML={processInstanceToUse.bpmn_xml_file_contents || ''} diagramXML={processInstance.bpmn_xml_file_contents || ''}
fileName={processInstanceToUse.bpmn_xml_file_contents || ''} fileName={processInstance.bpmn_xml_file_contents || ''}
readyOrWaitingProcessInstanceTasks={taskIds.readyOrWaiting} readyOrWaitingProcessInstanceTasks={taskIds.readyOrWaiting}
completedProcessInstanceTasks={taskIds.completed} completedProcessInstanceTasks={taskIds.completed}
diagramType="readonly" diagramType="readonly"