Merge branch 'main' into deploy-app-dev
This commit is contained in:
commit
72519a5260
|
@ -31,6 +31,7 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- spiffdemo
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create_frontend_docker_image:
|
create_frontend_docker_image:
|
||||||
|
@ -38,6 +39,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: sartography/spiffworkflow-frontend
|
IMAGE_NAME: sartography/spiffworkflow-frontend
|
||||||
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
@ -61,6 +63,7 @@ jobs:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.description=Frontend component of SpiffWorkflow, a software development platform for building, running, and monitoring executable diagrams
|
org.opencontainers.image.description=Frontend component of SpiffWorkflow, a software development platform for building, running, and monitoring executable diagrams
|
||||||
|
org.opencontainers.image.version=${{ env.BRANCH_NAME }}-${{ steps.date.outputs.date }}
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch,suffix=-latest
|
type=ref,event=branch,suffix=-latest
|
||||||
type=ref,event=branch,suffix=-${{ steps.date.outputs.date }}
|
type=ref,event=branch,suffix=-${{ steps.date.outputs.date }}
|
||||||
|
@ -84,6 +87,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: sartography/spiffworkflow-backend
|
IMAGE_NAME: sartography/spiffworkflow-backend
|
||||||
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
@ -107,6 +111,7 @@ jobs:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.description=Backend component of SpiffWorkflow, a software development platform for building, running, and monitoring executable diagrams
|
org.opencontainers.image.description=Backend component of SpiffWorkflow, a software development platform for building, running, and monitoring executable diagrams
|
||||||
|
org.opencontainers.image.version=${{ env.BRANCH_NAME }}-${{ steps.date.outputs.date }}
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch,suffix=-latest
|
type=ref,event=branch,suffix=-latest
|
||||||
type=ref,event=branch,suffix=-${{ steps.date.outputs.date }}
|
type=ref,event=branch,suffix=-${{ steps.date.outputs.date }}
|
||||||
|
|
|
@ -2,7 +2,41 @@
|
||||||
|
|
||||||
The follow is a list of enhancements we wish to do complete in the near (or even distant future)
|
The follow is a list of enhancements we wish to do complete in the near (or even distant future)
|
||||||
|
|
||||||
## Performance Improvements
|
## Performance / System Improvements
|
||||||
|
|
||||||
|
### Benchmarking / Performance Testing
|
||||||
|
Automated tests that assure our performance remains consistent as we add features and functionality.
|
||||||
|
|
||||||
|
### Support Multiple Connector Proxies
|
||||||
|
Service Tasks have been a huge win, there are multiple reasons that supporting more than one Connector Proxy would be beneficial:
|
||||||
|
|
||||||
|
1. Connect to several separately hosted services
|
||||||
|
2. Support multiple services written in multiple languages
|
||||||
|
3. Allow some connectors to be local (http get/post) vs remote (xero/coin gecko)
|
||||||
|
4. Could support non http based connectors (git interactions could be a workflow)
|
||||||
|
|
||||||
|
### Interstitial Performance
|
||||||
|
push all processing to background so interstitial is just querying, not running (new item)
|
||||||
|
|
||||||
|
### Authentication Keys
|
||||||
|
Provide an ability to access API endpoints using an access key - or authentication process that is specifically designed for API calls. (we currently rely on the grabbing the json token to do this, which is not a real solution)
|
||||||
|
|
||||||
|
### Core BPMN features
|
||||||
|
There are a number of useful BPMN components that we do not currently support. We should evaluate these and determine which ones we should support and how we should support them. We should consider creating a list of unsuported items.
|
||||||
|
|
||||||
|
* Compensation Events (valuable, but difficult)
|
||||||
|
* Conditional events.
|
||||||
|
* Event Sub-Processes are not currently supported (low-hanging fruit, easy to add)
|
||||||
|
|
||||||
|
### Decentralized / Distributed Deployments
|
||||||
|
This is a broad topic and will be covered in a separate document. But consider a SpiffWorkflow implementation that is deployed across a cluster of systems - and manages transactions on a shared Block Chain implementation. Such a structure could assure compliance to a set of blessed BPMN diagrams. Such a system could support highly transparent and auditable processes that could drive a DAO based organization.
|
||||||
|
|
||||||
|
|
||||||
|
### Improve Parallel Processing
|
||||||
|
We should support the parallel execution of tasks within a single process whenever possible to do so. This is not as far-fetched or difficult as it may initially seem. While Python is notoriously bad at parallel execution (the lovely GIL) - we have already taken the most critical steps to assuring it is possible:
|
||||||
|
1. A team has demonstrated parallel execution using the cure SpiffWorkflow library.
|
||||||
|
2. We can keep a configurable number of "background" SpiffArena processes running that can pick up waiting tasks.
|
||||||
|
Given these things are already in place, we just need to lock processes at the task or branch level - so that ready tasks on parallel branches can be picked up by different background processes at the same time.
|
||||||
|
|
||||||
### BPMN Definitions at save time vs run time
|
### BPMN Definitions at save time vs run time
|
||||||
Improve performance by pre-processing the BPMN Specification and generating the internal JSON representation so we no longer incur the expense of doing this on a per-process basis.
|
Improve performance by pre-processing the BPMN Specification and generating the internal JSON representation so we no longer incur the expense of doing this on a per-process basis.
|
||||||
|
@ -10,6 +44,12 @@ This will also allow us to do some early and deep validation as well.
|
||||||
|
|
||||||
## End User Experience
|
## End User Experience
|
||||||
|
|
||||||
|
### UI Overview
|
||||||
|
We could really use a good UI / UX review of the site and take a stab at cleaning up the whole site to follow some consistent design patterns and resolve potential issues.
|
||||||
|
|
||||||
|
### Customizable Home Page (non Status specific)
|
||||||
|
Allow some way to define custom landing pages that create different experiences for different organizations / needs.
|
||||||
|
|
||||||
### Markdown rendering could be better
|
### Markdown rendering could be better
|
||||||
1. When creating a bulleted or numbered list, no bullets or numbers are displayed. This is a bug in our style sheets - or something that is clearing out all styles.
|
1. When creating a bulleted or numbered list, no bullets or numbers are displayed. This is a bug in our style sheets - or something that is clearing out all styles.
|
||||||
2. Limit the width of paragraphs to something reasonable. Having a line of text stretch across the entire screen is not a good experience.
|
2. Limit the width of paragraphs to something reasonable. Having a line of text stretch across the entire screen is not a good experience.
|
||||||
|
@ -21,11 +61,34 @@ Allow defining contact information at the process group and process model level,
|
||||||
This information could then be displayed when a process is in a non-functional state - such an error, suspended, or terminiated state.
|
This information could then be displayed when a process is in a non-functional state - such an error, suspended, or terminiated state.
|
||||||
It might also be available in the footer or under a help icon when displaying a process instance.
|
It might also be available in the footer or under a help icon when displaying a process instance.
|
||||||
|
|
||||||
|
### Process Heatmap
|
||||||
|
Allow administrators to see an overlay of a BPMN diagram that shows all the process instances in the system and where they are (20 people are waiting on approval, 15 are in the re-review .....)
|
||||||
|
|
||||||
## Modeler Experience
|
## Modeler Experience
|
||||||
|
|
||||||
|
### DMN Editor Sucks
|
||||||
|
Can we build a better DMN editor? Trisotech seems to do it very well. Would love to have a day or two just to research this area and see if there is just another open source project we can leverage, or if we could build our own tool.
|
||||||
|
|
||||||
|
### Modeler Checker
|
||||||
|
At run time, or when you save it would be great if we could execute a:
|
||||||
|
* Validation Report - what is wrong with the model? Is it Valid BPMN? Are there intrinsic errors?
|
||||||
|
* Linting Report! Does the model follow common naming conventions, styles, are there dead-locks, etc. Many of these tools already exist, we just need to integrate them!
|
||||||
|
|
||||||
|
### Plugins and Extensions
|
||||||
|
* Track down our previous research and add here. Color picker, etc....
|
||||||
|
|
||||||
|
### Automated Testing
|
||||||
|
Incorporate an end-to-end testing system that will allow you to quickly assure that
|
||||||
|
a bpmn model is working as expected. Imagine Cypress tests that you could define and execute in the modeler.
|
||||||
|
|
||||||
|
### Json Schemas Everywhere!
|
||||||
|
Our forms are Json Schemas (a description of the data structure) - we could do similar things for Service Tasks, Script Tasks ... such that the modeler is at all times aware of what data is available - making it possible to build and execute a task as it is created.
|
||||||
|
|
||||||
### Markdown Support for Process Groups and Models
|
### Markdown Support for Process Groups and Models
|
||||||
Allow us to define a markdown file for a process group or process model, which would be displayed in the process group or process model in the tile view, or at the top of the details page when a group or model is selected.
|
Allow us to define a markdown file for a process group or process model, which would be displayed in the process group or process model in the tile view, or at the top of the details page when a group or model is selected.
|
||||||
|
|
||||||
|
### Adding a unit test from within the script editor would be nice
|
||||||
|
|
||||||
### Form Builder
|
### Form Builder
|
||||||
1. Let's invest in a much better Form Builder experience, so that it is trivial to build new forms or modify existing simple forms. We don't want to implement everything here - but a simple builder would be very useful.
|
1. Let's invest in a much better Form Builder experience, so that it is trivial to build new forms or modify existing simple forms. We don't want to implement everything here - but a simple builder would be very useful.
|
||||||
2. RJSF says it supports markdown in the headers, but it doesn't work fur us.
|
2. RJSF says it supports markdown in the headers, but it doesn't work fur us.
|
||||||
|
@ -38,15 +101,4 @@ Right now we allow editing the Display name of a model or group, but it does
|
||||||
not change the name of the underlying directory, making it harder and harder
|
not change the name of the underlying directory, making it harder and harder
|
||||||
over time to look at GitHub or the file system and find what you are seeing in the display.
|
over time to look at GitHub or the file system and find what you are seeing in the display.
|
||||||
|
|
||||||
## System Improvements
|
|
||||||
|
|
||||||
### Support Multiple Connector Proxies
|
|
||||||
Service Tasks have been a huge win, there are multiple reasons that supporting more than one Connector Proxy would be beneficial:
|
|
||||||
|
|
||||||
1. Connect to several separately hosted services
|
|
||||||
2. Support mulitple services written in multiple languages
|
|
||||||
3. Allow some connectors to be local (http get/post) vs remote (xero/coin gecko)
|
|
||||||
4. Could support non http based connectors (git interactions could be a workflow)
|
|
||||||
|
|
||||||
### Improve Parallel Processing
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
from spiffworkflow_backend import create_app
|
||||||
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
|
from spiffworkflow_backend.services.process_instance_service import ProcessInstanceService
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
app = create_app()
|
||||||
|
with app.app_context():
|
||||||
|
execution_strategy_name = app.config["SPIFFWORKFLOW_BACKEND_ENGINE_STEP_DEFAULT_STRATEGY_BACKGROUND"]
|
||||||
|
process_instance = ProcessInstanceModel.query.filter_by(id=29).first()
|
||||||
|
ProcessInstanceService.run_process_instance_with_processor(
|
||||||
|
process_instance, execution_strategy_name=execution_strategy_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -1,44 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
function error_handler() {
|
|
||||||
>&2 echo "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}."
|
|
||||||
exit "$2"
|
|
||||||
}
|
|
||||||
trap 'error_handler ${LINENO} $?' ERR
|
|
||||||
set -o errtrace -o errexit -o nounset -o pipefail
|
|
||||||
|
|
||||||
script_dir="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
|
||||||
|
|
||||||
if [[ -z "${KEYCLOAK_BASE_URL:-}" ]]; then
|
|
||||||
# export KEYCLOAK_BASE_URL=http://localhost:7002
|
|
||||||
export KEYCLOAK_BASE_URL=https://keycloak.dev.spiffworkflow.org
|
|
||||||
fi
|
|
||||||
if [[ -z "${BACKEND_BASE_URL:-}" ]]; then
|
|
||||||
# export BACKEND_BASE_URL=http://localhost:7000
|
|
||||||
export BACKEND_BASE_URL=https://api.dev.spiffworkflow.org
|
|
||||||
fi
|
|
||||||
|
|
||||||
user_list="${1}"
|
|
||||||
if [[ -z "${1:-}" ]]; then
|
|
||||||
>&2 echo "usage: $(basename "$0") [user_list]"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
REALM_NAME=${2-spiffworkflow}
|
|
||||||
|
|
||||||
while read -r input_line; do
|
|
||||||
if ! grep -qE '(^#|email)' <<<"$input_line" ; then
|
|
||||||
username=$(awk -F '@' '{print $1}' <<<"$input_line")
|
|
||||||
password=$(awk -F ',' '{print $2}' <<<"$input_line")
|
|
||||||
if [[ -z "$password" ]]; then
|
|
||||||
password="$username"
|
|
||||||
fi
|
|
||||||
access_token=$("${script_dir}/get_token" "$username" "$password" "$REALM_NAME" || echo '')
|
|
||||||
if [[ -z "$access_token" || "$access_token" == "null" ]]; then
|
|
||||||
>&2 echo "ERROR: failed to get access token for '$username'"
|
|
||||||
else
|
|
||||||
|
|
||||||
echo "access_token: ${access_token}"
|
|
||||||
curl -v -X POST "${BACKEND_BASE_URL}/v1.0/login_with_access_token?access_token=${access_token}" -H "Authorization: Bearer $access_token"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <"$user_list"
|
|
|
@ -6,7 +6,6 @@ from spiffworkflow_backend.helpers.db_helper import try_to_connect
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Main."""
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
|
|
@ -31,6 +31,7 @@ flask-simple-crypt = "^0.3.3"
|
||||||
werkzeug = "*"
|
werkzeug = "*"
|
||||||
SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "main"}
|
SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "main"}
|
||||||
# SpiffWorkflow = {develop = true, path = "../../spiffworkflow/" }
|
# SpiffWorkflow = {develop = true, path = "../../spiffworkflow/" }
|
||||||
|
# SpiffWorkflow = {develop = true, path = "../../SpiffWorkflow/" }
|
||||||
sentry-sdk = "^1.10"
|
sentry-sdk = "^1.10"
|
||||||
# sphinx-autoapi = "^2.0"
|
# sphinx-autoapi = "^2.0"
|
||||||
mysql-connector-python = "*"
|
mysql-connector-python = "*"
|
||||||
|
|
|
@ -1819,6 +1819,12 @@ paths:
|
||||||
description: The unique id of an existing process instance.
|
description: The unique id of an existing process instance.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
|
- name: execute_tasks
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Execute ready tasks on the process instance.
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Tasks
|
- Tasks
|
||||||
|
|
|
@ -50,6 +50,11 @@ class ApiError(Exception):
|
||||||
task_name: str | None = ""
|
task_name: str | None = ""
|
||||||
task_trace: list | None = field(default_factory=list)
|
task_trace: list | None = field(default_factory=list)
|
||||||
|
|
||||||
|
# these are useful if the error response cannot be json but has to be something else
|
||||||
|
# such as returning content type 'text/event-stream' for the interstitial page
|
||||||
|
response_headers: dict | None = None
|
||||||
|
response_message: str | None = None
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Instructions to print instance as a string."""
|
"""Instructions to print instance as a string."""
|
||||||
msg = "ApiError: % s. " % self.message
|
msg = "ApiError: % s. " % self.message
|
||||||
|
@ -302,4 +307,13 @@ def handle_exception(exception: Exception) -> flask.wrappers.Response:
|
||||||
status_code=status_code,
|
status_code=status_code,
|
||||||
)
|
)
|
||||||
|
|
||||||
return make_response(jsonify(api_exception), api_exception.status_code)
|
response_message = api_exception.response_message
|
||||||
|
if response_message is None:
|
||||||
|
response_message = jsonify(api_exception)
|
||||||
|
|
||||||
|
error_response = make_response(response_message, api_exception.status_code)
|
||||||
|
if api_exception.response_headers is not None:
|
||||||
|
for header, value in api_exception.response_headers.items():
|
||||||
|
error_response.headers[header] = value
|
||||||
|
|
||||||
|
return error_response
|
||||||
|
|
|
@ -368,7 +368,9 @@ def _render_instructions_for_end_user(task_model: TaskModel, extensions: dict |
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[str, str | None, None]:
|
def _interstitial_stream(
|
||||||
|
process_instance: ProcessInstanceModel, execute_tasks: bool = True
|
||||||
|
) -> Generator[str, str | None, None]:
|
||||||
def get_reportable_tasks() -> Any:
|
def get_reportable_tasks() -> Any:
|
||||||
return processor.bpmn_process_instance.get_tasks(
|
return processor.bpmn_process_instance.get_tasks(
|
||||||
TaskState.WAITING | TaskState.STARTED | TaskState.READY | TaskState.ERROR
|
TaskState.WAITING | TaskState.STARTED | TaskState.READY | TaskState.ERROR
|
||||||
|
@ -381,11 +383,6 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
|
||||||
extensions = TaskService.get_extensions_from_task_model(task_model)
|
extensions = TaskService.get_extensions_from_task_model(task_model)
|
||||||
return _render_instructions_for_end_user(task_model, extensions)
|
return _render_instructions_for_end_user(task_model, extensions)
|
||||||
|
|
||||||
def render_data(return_type: str, entity: ApiError | Task | ProcessInstanceModel) -> str:
|
|
||||||
return_hash: dict = {"type": return_type}
|
|
||||||
return_hash[return_type] = entity
|
|
||||||
return f"data: {current_app.json.dumps(return_hash)} \n\n"
|
|
||||||
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
reported_ids = [] # A list of all the ids reported by this endpoint so far.
|
reported_ids = [] # A list of all the ids reported by this endpoint so far.
|
||||||
tasks = get_reportable_tasks()
|
tasks = get_reportable_tasks()
|
||||||
|
@ -399,28 +396,31 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
|
||||||
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
yield render_data("error", api_error)
|
yield _render_data("error", api_error)
|
||||||
raise e
|
raise e
|
||||||
if instructions and spiff_task.id not in reported_ids:
|
if instructions and spiff_task.id not in reported_ids:
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
||||||
task.properties = {"instructionsForEndUser": instructions}
|
task.properties = {"instructionsForEndUser": instructions}
|
||||||
yield render_data("task", task)
|
yield _render_data("task", task)
|
||||||
reported_ids.append(spiff_task.id)
|
reported_ids.append(spiff_task.id)
|
||||||
if spiff_task.state == TaskState.READY:
|
if spiff_task.state == TaskState.READY:
|
||||||
# do not do any processing if the instance is not currently active
|
# do not do any processing if the instance is not currently active
|
||||||
if process_instance.status not in ProcessInstanceModel.active_statuses():
|
if process_instance.status not in ProcessInstanceModel.active_statuses():
|
||||||
yield render_data("unrunnable_instance", process_instance)
|
yield _render_data("unrunnable_instance", process_instance)
|
||||||
return
|
|
||||||
try:
|
|
||||||
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
|
|
||||||
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
|
|
||||||
processor.save() # Fixme - maybe find a way not to do this on every loop?
|
|
||||||
except WorkflowTaskException as wfe:
|
|
||||||
api_error = ApiError.from_workflow_exception(
|
|
||||||
"engine_steps_error", "Failed to complete an automated task.", exp=wfe
|
|
||||||
)
|
|
||||||
yield render_data("error", api_error)
|
|
||||||
return
|
return
|
||||||
|
if execute_tasks:
|
||||||
|
try:
|
||||||
|
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
|
||||||
|
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
|
||||||
|
processor.save() # Fixme - maybe find a way not to do this on every loop?
|
||||||
|
except WorkflowTaskException as wfe:
|
||||||
|
api_error = ApiError.from_workflow_exception(
|
||||||
|
"engine_steps_error", "Failed to complete an automated task.", exp=wfe
|
||||||
|
)
|
||||||
|
yield _render_data("error", api_error)
|
||||||
|
return
|
||||||
|
if execute_tasks is False:
|
||||||
|
break
|
||||||
processor.refresh_waiting_tasks()
|
processor.refresh_waiting_tasks()
|
||||||
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)
|
||||||
tasks = get_reportable_tasks()
|
tasks = get_reportable_tasks()
|
||||||
|
@ -439,34 +439,71 @@ def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[st
|
||||||
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
yield render_data("error", api_error)
|
yield _render_data("error", api_error)
|
||||||
raise e
|
raise e
|
||||||
task.properties = {"instructionsForEndUser": instructions}
|
task.properties = {"instructionsForEndUser": instructions}
|
||||||
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])
|
||||||
|
|
||||||
|
|
||||||
def _dequeued_interstitial_stream(process_instance_id: int) -> Generator[str | None, str | None, None]:
|
def _dequeued_interstitial_stream(
|
||||||
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
process_instance_id: int, execute_tasks: bool = True
|
||||||
|
) -> Generator[str | None, str | None, None]:
|
||||||
|
try:
|
||||||
|
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
||||||
|
ProcessInstanceProcessor(process_instance)
|
||||||
|
|
||||||
# TODO: currently this just redirects back to home if the process has not been started
|
# TODO: currently this just redirects back to home if the process has not been started
|
||||||
# need something better to show?
|
# need something better to show?
|
||||||
|
if execute_tasks:
|
||||||
if not ProcessInstanceQueueService.is_enqueued_to_run_in_the_future(process_instance):
|
if not ProcessInstanceQueueService.is_enqueued_to_run_in_the_future(process_instance):
|
||||||
with ProcessInstanceQueueService.dequeued(process_instance):
|
with ProcessInstanceQueueService.dequeued(process_instance):
|
||||||
yield from _interstitial_stream(process_instance)
|
yield from _interstitial_stream(process_instance, execute_tasks=execute_tasks)
|
||||||
|
else:
|
||||||
|
# no reason to get a lock if we are reading only
|
||||||
|
yield from _interstitial_stream(process_instance, execute_tasks=execute_tasks)
|
||||||
|
except Exception as ex:
|
||||||
|
# the stream_with_context method seems to swallow exceptions so also attempt to catch errors here
|
||||||
|
api_error = ApiError(
|
||||||
|
error_code="interstitial_error",
|
||||||
|
message=(
|
||||||
|
f"Received error trying to run process instance: {process_instance_id}. "
|
||||||
|
f"Error was: {ex.__class__.__name__}: {str(ex)}"
|
||||||
|
),
|
||||||
|
status_code=500,
|
||||||
|
)
|
||||||
|
yield _render_data("error", api_error)
|
||||||
|
|
||||||
|
|
||||||
def interstitial(process_instance_id: int) -> Response:
|
def interstitial(process_instance_id: int, execute_tasks: bool = True) -> Response:
|
||||||
"""A Server Side Events Stream for watching the execution of engine tasks."""
|
"""A Server Side Events Stream for watching the execution of engine tasks."""
|
||||||
return Response(
|
try:
|
||||||
stream_with_context(_dequeued_interstitial_stream(process_instance_id)),
|
return Response(
|
||||||
mimetype="text/event-stream",
|
stream_with_context(_dequeued_interstitial_stream(process_instance_id, execute_tasks=execute_tasks)),
|
||||||
headers={"X-Accel-Buffering": "no"},
|
mimetype="text/event-stream",
|
||||||
)
|
headers={"X-Accel-Buffering": "no"},
|
||||||
|
)
|
||||||
|
except Exception as ex:
|
||||||
|
api_error = ApiError(
|
||||||
|
error_code="interstitial_error",
|
||||||
|
message=(
|
||||||
|
f"Received error trying to run process instance: {process_instance_id}. "
|
||||||
|
f"Error was: {ex.__class__.__name__}: {str(ex)}"
|
||||||
|
),
|
||||||
|
status_code=500,
|
||||||
|
response_headers={"Content-type": "text/event-stream"},
|
||||||
|
)
|
||||||
|
api_error.response_message = _render_data("error", api_error)
|
||||||
|
raise api_error from ex
|
||||||
|
|
||||||
|
|
||||||
|
def _render_data(return_type: str, entity: ApiError | Task | ProcessInstanceModel) -> str:
|
||||||
|
return_hash: dict = {"type": return_type}
|
||||||
|
return_hash[return_type] = entity
|
||||||
|
return f"data: {current_app.json.dumps(return_hash)} \n\n"
|
||||||
|
|
||||||
|
|
||||||
def task_save_draft(
|
def task_save_draft(
|
||||||
|
|
|
@ -121,6 +121,7 @@ class ServiceTaskDelegate:
|
||||||
error_response = parsed_response["error"]
|
error_response = parsed_response["error"]
|
||||||
if isinstance(error_response, list | dict):
|
if isinstance(error_response, list | dict):
|
||||||
error_response = json.dumps(parsed_response["error"])
|
error_response = json.dumps(parsed_response["error"])
|
||||||
|
|
||||||
error += error_response
|
error += error_response
|
||||||
if json_parse_error:
|
if json_parse_error:
|
||||||
error += "A critical component (The connector proxy) is not responding correctly."
|
error += "A critical component (The connector proxy) is not responding correctly."
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import MDEditor from '@uiw/react-md-editor';
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
|
import { Toggle } from '@carbon/react';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
task: any;
|
task: any;
|
||||||
defaultMessage?: string;
|
defaultMessage?: string;
|
||||||
|
allowCollapse?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function InstructionsForEndUser({
|
export default function InstructionsForEndUser({
|
||||||
task,
|
task,
|
||||||
defaultMessage = '',
|
defaultMessage = '',
|
||||||
|
allowCollapse = false,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
if (!task) {
|
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||||
return null;
|
const [collapsable, setCollapsable] = useState<boolean>(false);
|
||||||
}
|
|
||||||
let instructions = defaultMessage;
|
let instructions = defaultMessage;
|
||||||
let { properties } = task;
|
let { properties } = task;
|
||||||
if (!properties) {
|
if (!properties) {
|
||||||
|
@ -23,15 +25,80 @@ export default function InstructionsForEndUser({
|
||||||
if (instructionsForEndUser) {
|
if (instructionsForEndUser) {
|
||||||
instructions = instructionsForEndUser;
|
instructions = instructionsForEndUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxLineCount: number = 8;
|
||||||
|
const maxWordCount: number = 75;
|
||||||
|
|
||||||
|
const lineCount = (arg: string) => {
|
||||||
|
return arg.split('\n').length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const wordCount = (arg: string) => {
|
||||||
|
return arg.split(' ').length;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
allowCollapse &&
|
||||||
|
(lineCount(instructions) >= maxLineCount ||
|
||||||
|
wordCount(instructions) > maxWordCount)
|
||||||
|
) {
|
||||||
|
setCollapsable(true);
|
||||||
|
setCollapsed(true);
|
||||||
|
} else {
|
||||||
|
setCollapsable(false);
|
||||||
|
setCollapsed(false);
|
||||||
|
}
|
||||||
|
}, [allowCollapse, instructions]);
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleCollapse = () => {
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showCollapseToggle = () => {
|
||||||
|
if (collapsable) {
|
||||||
|
return (
|
||||||
|
<Toggle
|
||||||
|
labelA="Show More"
|
||||||
|
labelB="Show Less"
|
||||||
|
onToggle={toggleCollapse}
|
||||||
|
id="toggle-collapse"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let instructionsShown = instructions;
|
||||||
|
if (collapsed) {
|
||||||
|
if (wordCount(instructions) > maxWordCount) {
|
||||||
|
instructionsShown = instructions
|
||||||
|
.split(' ')
|
||||||
|
.slice(0, maxWordCount)
|
||||||
|
.join(' ');
|
||||||
|
instructionsShown += '...';
|
||||||
|
} else if (lineCount(instructions) > maxLineCount) {
|
||||||
|
instructionsShown = instructions.split('\n').slice(0, 5).join(' ');
|
||||||
|
instructionsShown += '...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="markdown">
|
<div style={{ margin: '20px 0 20px 0' }}>
|
||||||
{/*
|
<div className="markdown">
|
||||||
https://www.npmjs.com/package/@uiw/react-md-editor switches to dark mode by default by respecting @media (prefers-color-scheme: dark)
|
{/*
|
||||||
This makes it look like our site is broken, so until the rest of the site supports dark mode, turn off dark mode for this component.
|
https://www.npmjs.com/package/@uiw/react-md-editor switches to dark mode by default by respecting @media (prefers-color-scheme: dark)
|
||||||
*/}
|
This makes it look like our site is broken, so until the rest of the site supports dark mode, turn off dark mode for this component.
|
||||||
<div data-color-mode="light">
|
*/}
|
||||||
<MDEditor.Markdown source={instructions} />
|
<div data-color-mode="light">
|
||||||
|
<MDEditor.Markdown linkTarget="_blank" source={instructionsShown} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{showCollapseToggle()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,23 +3,26 @@ import { useEffect, useState } from 'react';
|
||||||
import { ErrorOutline } from '@carbon/icons-react';
|
import { ErrorOutline } from '@carbon/icons-react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Table, Modal, Button } from '@carbon/react';
|
import { Table, Modal, Button } from '@carbon/react';
|
||||||
import { Link, useParams, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
import PaginationForTable from '../components/PaginationForTable';
|
import PaginationForTable from './PaginationForTable';
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from './ProcessBreadcrumb';
|
||||||
import {
|
import {
|
||||||
convertSecondsToFormattedDateTime,
|
convertSecondsToFormattedDateTime,
|
||||||
getPageInfoFromSearchParams,
|
getPageInfoFromSearchParams,
|
||||||
modifyProcessIdentifierForPathParam,
|
modifyProcessIdentifierForPathParam,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
import { FormatProcessModelDisplayName } from '../components/MiniComponents';
|
import { FormatProcessModelDisplayName } from './MiniComponents';
|
||||||
import { MessageInstance } from '../interfaces';
|
import { MessageInstance } from '../interfaces';
|
||||||
|
|
||||||
export default function MessageInstanceList() {
|
type OwnProps = {
|
||||||
const params = useParams();
|
processInstanceId?: number;
|
||||||
const [searchParams] = useSearchParams();
|
};
|
||||||
|
|
||||||
|
export default function MessageInstanceList({ processInstanceId }: OwnProps) {
|
||||||
const [messageIntances, setMessageInstances] = useState([]);
|
const [messageIntances, setMessageInstances] = useState([]);
|
||||||
const [pagination, setPagination] = useState(null);
|
const [pagination, setPagination] = useState(null);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
const [messageInstanceForModal, setMessageInstanceForModal] =
|
const [messageInstanceForModal, setMessageInstanceForModal] =
|
||||||
useState<MessageInstance | null>(null);
|
useState<MessageInstance | null>(null);
|
||||||
|
@ -31,16 +34,15 @@ export default function MessageInstanceList() {
|
||||||
};
|
};
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||||
let queryParamString = `per_page=${perPage}&page=${page}`;
|
let queryParamString = `per_page=${perPage}&page=${page}`;
|
||||||
if (searchParams.get('process_instance_id')) {
|
if (processInstanceId) {
|
||||||
queryParamString += `&process_instance_id=${searchParams.get(
|
queryParamString += `&process_instance_id=${processInstanceId}`;
|
||||||
'process_instance_id'
|
|
||||||
)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/messages?${queryParamString}`,
|
path: `/messages?${queryParamString}`,
|
||||||
successCallback: setMessageInstanceListFromResult,
|
successCallback: setMessageInstanceListFromResult,
|
||||||
});
|
});
|
||||||
}, [searchParams, params]);
|
}, [processInstanceId, searchParams]);
|
||||||
|
|
||||||
const handleCorrelationDisplayClose = () => {
|
const handleCorrelationDisplayClose = () => {
|
||||||
setMessageInstanceForModal(null);
|
setMessageInstanceForModal(null);
|
|
@ -1705,6 +1705,7 @@ export default function ProcessInstanceListTable({
|
||||||
sm={{ span: 1, offset: 3 }}
|
sm={{ span: 1, offset: 3 }}
|
||||||
md={{ span: 1, offset: 7 }}
|
md={{ span: 1, offset: 7 }}
|
||||||
lg={{ span: 1, offset: 15 }}
|
lg={{ span: 1, offset: 15 }}
|
||||||
|
style={{ textAlign: 'right' }}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
data-qa="process-instance-list-link"
|
data-qa="process-instance-list-link"
|
||||||
|
@ -1720,12 +1721,12 @@ export default function ProcessInstanceListTable({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Grid fullWidth condensed>
|
<>
|
||||||
<Column sm={{ span: 3 }} md={{ span: 7 }} lg={{ span: 15 }}>
|
<Column sm={{ span: 3 }} md={{ span: 7 }} lg={{ span: 15 }}>
|
||||||
{headerElement}
|
{headerElement}
|
||||||
</Column>
|
</Column>
|
||||||
{filterButtonLink}
|
{filterButtonLink}
|
||||||
</Grid>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1778,15 +1779,21 @@ export default function ProcessInstanceListTable({
|
||||||
{reportColumnForm()}
|
{reportColumnForm()}
|
||||||
{advancedOptionsModal()}
|
{advancedOptionsModal()}
|
||||||
{processInstanceReportSaveTag()}
|
{processInstanceReportSaveTag()}
|
||||||
{tableTitleLine()}
|
<Grid fullWidth condensed>
|
||||||
<Filters
|
{tableTitleLine()}
|
||||||
filterOptions={filterOptions}
|
<Column sm={{ span: 4 }} md={{ span: 8 }} lg={{ span: 16 }}>
|
||||||
showFilterOptions={showFilterOptions}
|
<Filters
|
||||||
setShowFilterOptions={setShowFilterOptions}
|
filterOptions={filterOptions}
|
||||||
reportSearchComponent={reportSearchComponent}
|
showFilterOptions={showFilterOptions}
|
||||||
filtersEnabled={filtersEnabled}
|
setShowFilterOptions={setShowFilterOptions}
|
||||||
/>
|
reportSearchComponent={reportSearchComponent}
|
||||||
{resultsTable}
|
filtersEnabled={filtersEnabled}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
<Column sm={{ span: 4 }} md={{ span: 8 }} lg={{ span: 16 }}>
|
||||||
|
{resultsTable}
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,6 @@ import { useEffect, useState } from 'react';
|
||||||
import { ErrorOutline } from '@carbon/icons-react';
|
import { ErrorOutline } from '@carbon/icons-react';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
Tabs,
|
|
||||||
TabList,
|
|
||||||
Tab,
|
|
||||||
Grid,
|
Grid,
|
||||||
Column,
|
Column,
|
||||||
ButtonSet,
|
ButtonSet,
|
||||||
|
@ -14,14 +11,8 @@ import {
|
||||||
Loading,
|
Loading,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from '@carbon/react';
|
} from '@carbon/react';
|
||||||
import {
|
import { createSearchParams, Link, useSearchParams } from 'react-router-dom';
|
||||||
createSearchParams,
|
import PaginationForTable from './PaginationForTable';
|
||||||
Link,
|
|
||||||
useParams,
|
|
||||||
useSearchParams,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import PaginationForTable from '../components/PaginationForTable';
|
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
|
||||||
import {
|
import {
|
||||||
getPageInfoFromSearchParams,
|
getPageInfoFromSearchParams,
|
||||||
convertSecondsToFormattedDateTime,
|
convertSecondsToFormattedDateTime,
|
||||||
|
@ -34,23 +25,30 @@ import {
|
||||||
ProcessInstanceEventErrorDetail,
|
ProcessInstanceEventErrorDetail,
|
||||||
ProcessInstanceLogEntry,
|
ProcessInstanceLogEntry,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import Filters from '../components/Filters';
|
import Filters from './Filters';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
import {
|
import {
|
||||||
childrenForErrorObject,
|
childrenForErrorObject,
|
||||||
errorForDisplayFromProcessInstanceErrorDetail,
|
errorForDisplayFromProcessInstanceErrorDetail,
|
||||||
} from '../components/ErrorDisplay';
|
} from './ErrorDisplay';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
variant: string;
|
variant: string; // 'all' or 'for-me'
|
||||||
|
isEventsView: boolean;
|
||||||
|
processModelId: string;
|
||||||
|
processInstanceId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
export default function ProcessInstanceLogList({
|
||||||
const params = useParams();
|
variant,
|
||||||
|
isEventsView = true,
|
||||||
|
processModelId,
|
||||||
|
processInstanceId,
|
||||||
|
}: OwnProps) {
|
||||||
const [clearAll, setClearAll] = useState<boolean>(false);
|
const [clearAll, setClearAll] = useState<boolean>(false);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const [processInstanceLogs, setProcessInstanceLogs] = useState([]);
|
const [processInstanceLogs, setProcessInstanceLogs] = useState([]);
|
||||||
const [pagination, setPagination] = useState(null);
|
const [pagination, setPagination] = useState(null);
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const [taskTypes, setTaskTypes] = useState<string[]>([]);
|
const [taskTypes, setTaskTypes] = useState<string[]>([]);
|
||||||
const [eventTypes, setEventTypes] = useState<string[]>([]);
|
const [eventTypes, setEventTypes] = useState<string[]>([]);
|
||||||
|
@ -71,17 +69,18 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
||||||
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
|
const [showFilterOptions, setShowFilterOptions] = useState<boolean>(false);
|
||||||
const randomNumberBetween0and1 = Math.random();
|
const randomNumberBetween0and1 = Math.random();
|
||||||
|
|
||||||
|
searchParams.set('events', isEventsView ? 'true' : 'false');
|
||||||
|
|
||||||
let shouldDisplayClearButton = false;
|
let shouldDisplayClearButton = false;
|
||||||
if (randomNumberBetween0and1 < 0.05) {
|
if (randomNumberBetween0and1 < 0.05) {
|
||||||
// 5% chance of being here
|
// 5% chance of being here
|
||||||
shouldDisplayClearButton = true;
|
shouldDisplayClearButton = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${params.process_model_id}`;
|
let processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${processModelId}`;
|
||||||
if (variant === 'all') {
|
if (variant === 'all') {
|
||||||
processInstanceShowPageBaseUrl = `/admin/process-instances/${params.process_model_id}`;
|
processInstanceShowPageBaseUrl = `/admin/process-instances/${processModelId}`;
|
||||||
}
|
}
|
||||||
const isEventsView = searchParams.get('events') === 'true';
|
|
||||||
const taskNameHeader = isEventsView ? 'Task Name' : 'Milestone';
|
const taskNameHeader = isEventsView ? 'Task Name' : 'Milestone';
|
||||||
|
|
||||||
const updateSearchParams = (value: string, key: string) => {
|
const updateSearchParams = (value: string, key: string) => {
|
||||||
|
@ -130,7 +129,7 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
||||||
typeaheadQueryParamString = '?task_type=IntermediateThrowEvent';
|
typeaheadQueryParamString = '?task_type=IntermediateThrowEvent';
|
||||||
}
|
}
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/v1.0/logs/typeahead-filter-values/${params.process_model_id}/${params.process_instance_id}${typeaheadQueryParamString}`,
|
path: `/v1.0/logs/typeahead-filter-values/${processModelId}/${processInstanceId}${typeaheadQueryParamString}`,
|
||||||
successCallback: (result: any) => {
|
successCallback: (result: any) => {
|
||||||
setTaskTypes(result.task_types);
|
setTaskTypes(result.task_types);
|
||||||
setEventTypes(result.event_types);
|
setEventTypes(result.event_types);
|
||||||
|
@ -140,7 +139,8 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
searchParams,
|
searchParams,
|
||||||
params,
|
processInstanceId,
|
||||||
|
processModelId,
|
||||||
targetUris.processInstanceLogListPath,
|
targetUris.processInstanceLogListPath,
|
||||||
isEventsView,
|
isEventsView,
|
||||||
]);
|
]);
|
||||||
|
@ -487,60 +487,12 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs = () => {
|
|
||||||
const selectedTabIndex = isEventsView ? 1 : 0;
|
|
||||||
return (
|
|
||||||
<Tabs selectedIndex={selectedTabIndex}>
|
|
||||||
<TabList aria-label="List of tabs">
|
|
||||||
<Tab
|
|
||||||
title="Only show a subset of the logs, and show fewer columns"
|
|
||||||
data-qa="process-instance-log-milestones"
|
|
||||||
onClick={() => {
|
|
||||||
resetFilters();
|
|
||||||
searchParams.set('events', 'false');
|
|
||||||
setSearchParams(searchParams);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Milestones
|
|
||||||
</Tab>
|
|
||||||
<Tab
|
|
||||||
title="Show all logs for this process instance, and show extra columns that may be useful for debugging"
|
|
||||||
data-qa="process-instance-log-events"
|
|
||||||
onClick={() => {
|
|
||||||
resetFilters();
|
|
||||||
searchParams.set('events', 'true');
|
|
||||||
setSearchParams(searchParams);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Events
|
|
||||||
</Tab>
|
|
||||||
</TabList>
|
|
||||||
</Tabs>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
const { page, perPage } = getPageInfoFromSearchParams(searchParams);
|
||||||
if (clearAll) {
|
if (clearAll) {
|
||||||
return <p>Page cleared 👍</p>;
|
return <p>Page cleared 👍</p>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProcessBreadcrumb
|
|
||||||
hotCrumbs={[
|
|
||||||
['Process Groups', '/admin'],
|
|
||||||
{
|
|
||||||
entityToExplode: params.process_model_id || '',
|
|
||||||
entityType: 'process-model-id',
|
|
||||||
linkLastItem: true,
|
|
||||||
},
|
|
||||||
[
|
|
||||||
`Process Instance: ${params.process_instance_id}`,
|
|
||||||
`${processInstanceShowPageBaseUrl}/${params.process_instance_id}`,
|
|
||||||
],
|
|
||||||
['Logs'],
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
{tabs()}
|
|
||||||
{errorEventModal()}
|
{errorEventModal()}
|
||||||
<Filters
|
<Filters
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
|
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, 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
|
||||||
import { Loading } from '@carbon/react';
|
import { Loading, InlineNotification } from '@carbon/react';
|
||||||
import { BACKEND_BASE_URL } from '../config';
|
import { BACKEND_BASE_URL } from '../config';
|
||||||
import { getBasicHeaders } from '../services/HttpService';
|
import { getBasicHeaders } from '../services/HttpService';
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ type OwnProps = {
|
||||||
processInstanceShowPageUrl: string;
|
processInstanceShowPageUrl: string;
|
||||||
allowRedirect: boolean;
|
allowRedirect: boolean;
|
||||||
smallSpinner?: boolean;
|
smallSpinner?: boolean;
|
||||||
|
collapsableInstructions?: boolean;
|
||||||
|
executeTasks?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ProcessInterstitial({
|
export default function ProcessInterstitial({
|
||||||
|
@ -23,6 +25,8 @@ export default function ProcessInterstitial({
|
||||||
allowRedirect,
|
allowRedirect,
|
||||||
processInstanceShowPageUrl,
|
processInstanceShowPageUrl,
|
||||||
smallSpinner = false,
|
smallSpinner = false,
|
||||||
|
collapsableInstructions = false,
|
||||||
|
executeTasks = true,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [lastTask, setLastTask] = useState<any>(null);
|
const [lastTask, setLastTask] = useState<any>(null);
|
||||||
|
@ -37,23 +41,32 @@ export default function ProcessInterstitial({
|
||||||
const { addError } = useAPIError();
|
const { addError } = useAPIError();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchEventSource(`${BACKEND_BASE_URL}/tasks/${processInstanceId}`, {
|
fetchEventSource(
|
||||||
headers: getBasicHeaders(),
|
`${BACKEND_BASE_URL}/tasks/${processInstanceId}?execute_tasks=${executeTasks}`,
|
||||||
onmessage(ev) {
|
{
|
||||||
const retValue = JSON.parse(ev.data);
|
headers: getBasicHeaders(),
|
||||||
if (retValue.type === 'error') {
|
onmessage(ev) {
|
||||||
addError(retValue.error);
|
const retValue = JSON.parse(ev.data);
|
||||||
} else if (retValue.type === 'task') {
|
if (retValue.type === 'error') {
|
||||||
setData((prevData) => [retValue.task, ...prevData]);
|
addError(retValue.error);
|
||||||
setLastTask(retValue.task);
|
} else if (retValue.type === 'task') {
|
||||||
} else if (retValue.type === 'unrunnable_instance') {
|
setData((prevData) => [retValue.task, ...prevData]);
|
||||||
setProcessInstance(retValue.unrunnable_instance);
|
setLastTask(retValue.task);
|
||||||
}
|
} else if (retValue.type === 'unrunnable_instance') {
|
||||||
},
|
setProcessInstance(retValue.unrunnable_instance);
|
||||||
onclose() {
|
}
|
||||||
setState('CLOSED');
|
},
|
||||||
},
|
onerror(error: any) {
|
||||||
});
|
// if backend returns a 500 then stop attempting to load the task
|
||||||
|
setState('CLOSED');
|
||||||
|
addError(error);
|
||||||
|
throw error;
|
||||||
|
},
|
||||||
|
onclose() {
|
||||||
|
setState('CLOSED');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []); // it is critical to only run this once.
|
}, []); // it is critical to only run this once.
|
||||||
|
|
||||||
|
@ -75,13 +88,13 @@ export default function ProcessInterstitial({
|
||||||
}, [allowRedirect, state]);
|
}, [allowRedirect, state]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Added this seperate use effect so that the timer interval will be cleared if
|
// Added this separate use effect so that the timer interval will be cleared if
|
||||||
// we end up redirecting back to the TaskShow page.
|
// we end up redirecting back to the TaskShow page.
|
||||||
if (shouldRedirectToTask(lastTask)) {
|
if (shouldRedirectToTask(lastTask)) {
|
||||||
lastTask.properties.instructionsForEndUser = '';
|
lastTask.properties.instructionsForEndUser = '';
|
||||||
const timerId = setInterval(() => {
|
const timerId = setInterval(() => {
|
||||||
navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`);
|
navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`);
|
||||||
}, 2000);
|
}, 500);
|
||||||
return () => clearInterval(timerId);
|
return () => clearInterval(timerId);
|
||||||
}
|
}
|
||||||
if (shouldRedirectToProcessInstance()) {
|
if (shouldRedirectToProcessInstance()) {
|
||||||
|
@ -118,16 +131,27 @@ export default function ProcessInterstitial({
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const inlineMessage = (
|
||||||
|
title: string,
|
||||||
|
subtitle: string,
|
||||||
|
kind: string = 'info'
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<InlineNotification kind={kind} subtitle={subtitle} title={title} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const userMessageForProcessInstance = (
|
const userMessageForProcessInstance = (
|
||||||
pi: ProcessInstance,
|
pi: ProcessInstance,
|
||||||
myTask: ProcessInstanceTask | null = null
|
myTask: ProcessInstanceTask | null = null
|
||||||
) => {
|
) => {
|
||||||
if (['terminated', 'suspended'].includes(pi.status)) {
|
if (['terminated', 'suspended'].includes(pi.status)) {
|
||||||
return (
|
return inlineMessage(
|
||||||
<p>
|
`Process ${pi.status}`,
|
||||||
This process instance was {pi.status} by an administrator. Please get
|
'This process instance was {pi.status} by an administrator. Please get in touch with them for more information.',
|
||||||
in touch with them for more information.
|
'warning'
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (pi.status === 'error') {
|
if (pi.status === 'error') {
|
||||||
|
@ -135,17 +159,21 @@ export default function ProcessInterstitial({
|
||||||
if (myTask && myTask.error_message) {
|
if (myTask && myTask.error_message) {
|
||||||
errMessage = errMessage.concat(myTask.error_message);
|
errMessage = errMessage.concat(myTask.error_message);
|
||||||
}
|
}
|
||||||
return <p>{errMessage}</p>;
|
return inlineMessage(`Process Error`, errMessage, 'error');
|
||||||
}
|
}
|
||||||
// Otherwise we are not started, waiting, complete, or user_input_required
|
// Otherwise we are not started, waiting, complete, or user_input_required
|
||||||
const defaultMsg =
|
const defaultMsg =
|
||||||
'There are no additional instructions or information for this process.';
|
'There are no additional instructions or information for this process.';
|
||||||
if (myTask) {
|
if (myTask) {
|
||||||
return (
|
return (
|
||||||
<InstructionsForEndUser task={myTask} defaultMessage={defaultMsg} />
|
<InstructionsForEndUser
|
||||||
|
task={myTask}
|
||||||
|
defaultMessage={defaultMsg}
|
||||||
|
allowCollapse={collapsableInstructions}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <p>{defaultMsg}</p>;
|
return inlineMessage(`Process Error`, defaultMsg, 'info');
|
||||||
};
|
};
|
||||||
|
|
||||||
const userMessage = (myTask: ProcessInstanceTask) => {
|
const userMessage = (myTask: ProcessInstanceTask) => {
|
||||||
|
@ -154,27 +182,29 @@ export default function ProcessInterstitial({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
|
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
|
||||||
return (
|
return inlineMessage(
|
||||||
<p>
|
'',
|
||||||
This next task is assigned to a different person or team. There is no
|
`This next task is assigned to a different person or team. There is no action for you to take at this time.`
|
||||||
action for you to take at this time.
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (shouldRedirectToTask(myTask)) {
|
if (shouldRedirectToTask(myTask)) {
|
||||||
return <div>Redirecting you to the next task now ...</div>;
|
return inlineMessage('', `Redirecting ...`);
|
||||||
}
|
}
|
||||||
if (myTask && myTask.can_complete && userTasks.includes(myTask.type)) {
|
if (myTask && myTask.can_complete && userTasks.includes(myTask.type)) {
|
||||||
return `The task ${myTask.title} is ready for you to complete.`;
|
return inlineMessage(
|
||||||
|
'',
|
||||||
|
`The task "${myTask.title}" is ready for you to complete.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (myTask.error_message) {
|
if (myTask.error_message) {
|
||||||
return <div>{myTask.error_message}</div>;
|
return inlineMessage('Error', myTask.error_message, 'error');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<InstructionsForEndUser
|
<InstructionsForEndUser
|
||||||
task={myTask}
|
task={myTask}
|
||||||
defaultMessage="There are no additional instructions or information for this task."
|
defaultMessage="There are no additional instructions or information for this task."
|
||||||
|
allowCollapse={collapsableInstructions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -191,18 +221,19 @@ export default function ProcessInterstitial({
|
||||||
displayableData = [data[0]];
|
displayableData = [data[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const className = (index: number) => {
|
||||||
|
if (displayableData.length === 1) {
|
||||||
|
return 'user_instructions';
|
||||||
|
}
|
||||||
|
return index < 4 ? `user_instructions_${index}` : `user_instructions_4`;
|
||||||
|
};
|
||||||
|
|
||||||
if (lastTask) {
|
if (lastTask) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{getLoadingIcon()}
|
{getLoadingIcon()}
|
||||||
{displayableData.map((d, index) => (
|
{displayableData.map((d, index) => (
|
||||||
<div
|
<div className={className(index)}>{userMessage(d)}</div>
|
||||||
className={
|
|
||||||
index < 4 ? `user_instructions_${index}` : `user_instructions_4`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{userMessage(d)}
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -44,11 +44,7 @@ import spiffModdleExtension from 'bpmn-js-spiffworkflow/app/spiffworkflow/moddle
|
||||||
// @ts-expect-error TS(7016) FIXME
|
// @ts-expect-error TS(7016) FIXME
|
||||||
import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move';
|
import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move';
|
||||||
// @ts-expect-error TS(7016) FIXME
|
// @ts-expect-error TS(7016) FIXME
|
||||||
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
|
|
||||||
// @ts-expect-error TS(7016) FIXME
|
|
||||||
import TouchModule from 'diagram-js/lib/navigation/touch';
|
import TouchModule from 'diagram-js/lib/navigation/touch';
|
||||||
// @ts-expect-error TS(7016) FIXME
|
|
||||||
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
|
|
||||||
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -216,12 +212,7 @@ export default function ReactDiagramEditor({
|
||||||
|
|
||||||
// taken from the non-modeling components at
|
// taken from the non-modeling components at
|
||||||
// bpmn-js/lib/Modeler.js
|
// bpmn-js/lib/Modeler.js
|
||||||
additionalModules: [
|
additionalModules: [KeyboardMoveModule, TouchModule],
|
||||||
KeyboardMoveModule,
|
|
||||||
MoveCanvasModule,
|
|
||||||
TouchModule,
|
|
||||||
ZoomScrollModule,
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,12 +415,16 @@ export default function ReactDiagramEditor({
|
||||||
const canvas = (modeler as any).get('canvas');
|
const canvas = (modeler as any).get('canvas');
|
||||||
|
|
||||||
// only get the canvas if the dmn active viewer is actually
|
// only get the canvas if the dmn active viewer is actually
|
||||||
// a Modeler and not an Editor which is what it will when we are
|
// a Modeler and not an Editor which is what it will be when we are
|
||||||
// actively editing a decision table
|
// actively editing a decision table
|
||||||
if ((modeler as any).constructor.name === 'Modeler') {
|
if ((modeler as any).constructor.name === 'Modeler') {
|
||||||
canvas.zoom('fit-viewport');
|
canvas.zoom('fit-viewport');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((modeler as any).constructor.name === 'Viewer') {
|
||||||
|
canvas.zoom('fit-viewport');
|
||||||
|
}
|
||||||
|
|
||||||
// highlighting a field
|
// highlighting a field
|
||||||
// Option 3 at:
|
// Option 3 at:
|
||||||
// https://github.com/bpmn-io/bpmn-js-examples/tree/master/colors
|
// https://github.com/bpmn-io/bpmn-js-examples/tree/master/colors
|
||||||
|
|
|
@ -14,6 +14,7 @@ export const useUriListForPermissions = () => {
|
||||||
processInstanceCreatePath: `/v1.0/process-instances/${params.process_model_id}`,
|
processInstanceCreatePath: `/v1.0/process-instances/${params.process_model_id}`,
|
||||||
processInstanceErrorEventDetails: `/v1.0/event-error-details/${params.process_model_id}/${params.process_instance_id}`,
|
processInstanceErrorEventDetails: `/v1.0/event-error-details/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
processInstanceListPath: '/v1.0/process-instances',
|
processInstanceListPath: '/v1.0/process-instances',
|
||||||
|
processInstanceListForMePath: `/v1.0/process-instances/for-me`,
|
||||||
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',
|
||||||
processInstanceResetPath: `/v1.0/process-instance-reset/${params.process_model_id}/${params.process_instance_id}`,
|
processInstanceResetPath: `/v1.0/process-instance-reset/${params.process_model_id}/${params.process_instance_id}`,
|
||||||
|
|
|
@ -168,7 +168,7 @@ code {
|
||||||
}
|
}
|
||||||
|
|
||||||
.cds--breadcrumb {
|
.cds--breadcrumb {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.process-description {
|
.process-description {
|
||||||
|
@ -329,8 +329,8 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
||||||
|
|
||||||
.cds--tile.tile-process-group {
|
.cds--tile.tile-process-group {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 16px;
|
margin: 12px;
|
||||||
width: 354px;
|
width: 320px;
|
||||||
height: 264px;
|
height: 264px;
|
||||||
background: #F4F4F4;
|
background: #F4F4F4;
|
||||||
order: 1;
|
order: 1;
|
||||||
|
@ -338,7 +338,7 @@ 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: 354px;
|
width: 320px;
|
||||||
height: 264px;
|
height: 264px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -492,6 +492,11 @@ svg.notification-icon {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user_instructions {
|
||||||
|
filter: opacity(1);
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.user_instructions_0 {
|
.user_instructions_0 {
|
||||||
filter: opacity(1);
|
filter: opacity(1);
|
||||||
|
|
|
@ -15,3 +15,37 @@
|
||||||
@use '@carbon/colors';
|
@use '@carbon/colors';
|
||||||
// @use '@carbon/react/scss/colors';
|
// @use '@carbon/react/scss/colors';
|
||||||
@use '@carbon/react/scss/themes';
|
@use '@carbon/react/scss/themes';
|
||||||
|
|
||||||
|
// Not certain the best location for this, but here are some global
|
||||||
|
// tweaks the markdown styles.
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 1440px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, li, h1, h2, h3, h4, h5, h6, blockquote {
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
li.cds--accordion__item {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.show-page, div.markdown, div.markdown-collapsed, div.markdown-collapsable {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 1000px;
|
||||||
|
padding: 15px 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wmde-markdown ol {
|
||||||
|
list-style: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wmde-markdown ul {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cds--tag svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
|
@ -55,9 +55,6 @@ function CheckboxesWidget({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormLabel required={required} htmlFor={id}>
|
|
||||||
{label || schema.title}
|
|
||||||
</FormLabel>
|
|
||||||
<FormGroup id={id} row={!!inline}>
|
<FormGroup id={id} row={!!inline}>
|
||||||
{Array.isArray(enumOptions) &&
|
{Array.isArray(enumOptions) &&
|
||||||
enumOptions.map((option, index: number) => {
|
enumOptions.map((option, index: number) => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import ProcessGroupList from './ProcessGroupList';
|
import ProcessGroupList from './ProcessGroupList';
|
||||||
import ProcessGroupShow from './ProcessGroupShow';
|
import ProcessGroupShow from './ProcessGroupShow';
|
||||||
import ProcessGroupNew from './ProcessGroupNew';
|
import ProcessGroupNew from './ProcessGroupNew';
|
||||||
|
@ -16,13 +16,12 @@ import ProcessInstanceReportList from './ProcessInstanceReportList';
|
||||||
import ProcessInstanceReportNew from './ProcessInstanceReportNew';
|
import ProcessInstanceReportNew from './ProcessInstanceReportNew';
|
||||||
import ProcessInstanceReportEdit from './ProcessInstanceReportEdit';
|
import ProcessInstanceReportEdit from './ProcessInstanceReportEdit';
|
||||||
import ReactFormEditor from './ReactFormEditor';
|
import ReactFormEditor from './ReactFormEditor';
|
||||||
import ProcessInstanceLogList from './ProcessInstanceLogList';
|
|
||||||
import MessageInstanceList from './MessageInstanceList';
|
|
||||||
import Configuration from './Configuration';
|
import Configuration from './Configuration';
|
||||||
import JsonSchemaFormBuilder from './JsonSchemaFormBuilder';
|
import JsonSchemaFormBuilder from './JsonSchemaFormBuilder';
|
||||||
import ProcessModelNewExperimental from './ProcessModelNewExperimental';
|
import ProcessModelNewExperimental from './ProcessModelNewExperimental';
|
||||||
import ProcessInstanceFindById from './ProcessInstanceFindById';
|
import ProcessInstanceFindById from './ProcessInstanceFindById';
|
||||||
import ProcessInterstitialPage from './ProcessInterstitialPage';
|
import ProcessInterstitialPage from './ProcessInterstitialPage';
|
||||||
|
import MessageListPage from './MessageListPage';
|
||||||
|
|
||||||
export default function AdminRoutes() {
|
export default function AdminRoutes() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -112,14 +111,6 @@ export default function AdminRoutes() {
|
||||||
path="process-models/:process_model_id/form/:file_name"
|
path="process-models/:process_model_id/form/:file_name"
|
||||||
element={<ReactFormEditor />}
|
element={<ReactFormEditor />}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
path="logs/:process_model_id/:process_instance_id"
|
|
||||||
element={<ProcessInstanceLogList variant="all" />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="logs/for-me/:process_model_id/:process_instance_id"
|
|
||||||
element={<ProcessInstanceLogList variant="for-me" />}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
path="process-instances"
|
path="process-instances"
|
||||||
element={<ProcessInstanceList variant="for-me" />}
|
element={<ProcessInstanceList variant="for-me" />}
|
||||||
|
@ -132,7 +123,6 @@ export default function AdminRoutes() {
|
||||||
path="process-instances/all"
|
path="process-instances/all"
|
||||||
element={<ProcessInstanceList variant="all" />}
|
element={<ProcessInstanceList variant="all" />}
|
||||||
/>
|
/>
|
||||||
<Route path="messages" element={<MessageInstanceList />} />
|
|
||||||
<Route path="configuration/*" element={<Configuration />} />
|
<Route path="configuration/*" element={<Configuration />} />
|
||||||
<Route
|
<Route
|
||||||
path="process-models/:process_model_id/form-builder"
|
path="process-models/:process_model_id/form-builder"
|
||||||
|
@ -142,6 +132,7 @@ export default function AdminRoutes() {
|
||||||
path="process-instances/find-by-id"
|
path="process-instances/find-by-id"
|
||||||
element={<ProcessInstanceFindById />}
|
element={<ProcessInstanceFindById />}
|
||||||
/>
|
/>
|
||||||
|
<Route path="messages" element={<MessageListPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import MessageInstanceList from '../components/MessageInstanceList';
|
||||||
|
|
||||||
|
export default function MessageListPage() {
|
||||||
|
return <MessageInstanceList />;
|
||||||
|
}
|
|
@ -20,15 +20,18 @@ import {
|
||||||
Grid,
|
Grid,
|
||||||
Column,
|
Column,
|
||||||
Button,
|
Button,
|
||||||
ButtonSet,
|
|
||||||
Tag,
|
Tag,
|
||||||
Modal,
|
Modal,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Stack,
|
Stack,
|
||||||
Loading,
|
Loading,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
TabList,
|
||||||
|
TabPanels,
|
||||||
|
TabPanel,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from '@carbon/react';
|
} from '@carbon/react';
|
||||||
import { Can } from '@casl/react';
|
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
import ReactDiagramEditor from '../components/ReactDiagramEditor';
|
import ReactDiagramEditor from '../components/ReactDiagramEditor';
|
||||||
|
@ -45,7 +48,6 @@ import {
|
||||||
PermissionsToCheck,
|
PermissionsToCheck,
|
||||||
ProcessData,
|
ProcessData,
|
||||||
ProcessInstance,
|
ProcessInstance,
|
||||||
ProcessInstanceMetadata,
|
|
||||||
Task,
|
Task,
|
||||||
TaskDefinitionPropertiesJson,
|
TaskDefinitionPropertiesJson,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
|
@ -54,6 +56,8 @@ 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 ProcessInstanceLogList from '../components/ProcessInstanceLogList';
|
||||||
|
import MessageInstanceList from '../components/MessageInstanceList';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
variant: string;
|
variant: string;
|
||||||
|
@ -84,8 +88,6 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
const [eventPayload, setEventPayload] = useState<string>('{}');
|
const [eventPayload, setEventPayload] = useState<string>('{}');
|
||||||
const [eventTextEditorEnabled, setEventTextEditorEnabled] =
|
const [eventTextEditorEnabled, setEventTextEditorEnabled] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [showProcessInstanceMetadata, setShowProcessInstanceMetadata] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
|
|
||||||
const { addError, removeError } = useAPIError();
|
const { addError, removeError } = useAPIError();
|
||||||
const unModifiedProcessModelId = unModifyProcessIdentifierForPathParam(
|
const unModifiedProcessModelId = unModifyProcessIdentifierForPathParam(
|
||||||
|
@ -124,11 +126,9 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
let processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}`;
|
let processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${params.process_model_id}/${params.process_instance_id}`;
|
||||||
let processInstanceLogListPageBaseUrl = `/admin/logs/for-me/${params.process_model_id}/${params.process_instance_id}`;
|
|
||||||
const processInstanceShowPageBaseUrlAllVariant = `/admin/process-instances/${params.process_model_id}/${params.process_instance_id}`;
|
const processInstanceShowPageBaseUrlAllVariant = `/admin/process-instances/${params.process_model_id}/${params.process_instance_id}`;
|
||||||
if (variant === 'all') {
|
if (variant === 'all') {
|
||||||
processInstanceShowPageBaseUrl = processInstanceShowPageBaseUrlAllVariant;
|
processInstanceShowPageBaseUrl = processInstanceShowPageBaseUrlAllVariant;
|
||||||
processInstanceLogListPageBaseUrl = `/admin/logs/${params.process_model_id}/${params.process_instance_id}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddErrorInUseEffect = useCallback((value: ErrorForDisplay) => {
|
const handleAddErrorInUseEffect = useCallback((value: ErrorForDisplay) => {
|
||||||
|
@ -313,39 +313,52 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
}
|
}
|
||||||
const lastUpdatedTimeTag = (
|
const lastUpdatedTimeTag = (
|
||||||
<Grid condensed fullWidth>
|
<Grid condensed fullWidth>
|
||||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
<Column sm={2} md={2} lg={3} className="grid-list-title">
|
||||||
{lastUpdatedTimeLabel}:{' '}
|
{lastUpdatedTimeLabel}:{' '}
|
||||||
</Column>
|
</Column>
|
||||||
<Column sm={3} md={3} lg={3} className="grid-date">
|
<Column sm={3} md={6} lg={8} className="grid-date">
|
||||||
{convertSecondsToFormattedDateTime(lastUpdatedTime || 0) || 'N/A'}
|
{convertSecondsToFormattedDateTime(lastUpdatedTime || 0) || 'N/A'}
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
|
||||||
let statusIcon = <InProgress />;
|
let statusIcon = <InProgress />;
|
||||||
|
let statusColor = 'gray';
|
||||||
if (processInstance.status === 'suspended') {
|
if (processInstance.status === 'suspended') {
|
||||||
statusIcon = <PauseOutline />;
|
statusIcon = <PauseOutline />;
|
||||||
} else if (processInstance.status === 'complete') {
|
} else if (processInstance.status === 'complete') {
|
||||||
statusIcon = <Checkmark />;
|
statusIcon = <Checkmark />;
|
||||||
|
statusColor = 'green';
|
||||||
} else if (processInstance.status === 'terminated') {
|
} else if (processInstance.status === 'terminated') {
|
||||||
statusIcon = <StopOutline />;
|
statusIcon = <StopOutline />;
|
||||||
} else if (processInstance.status === 'error') {
|
} else if (processInstance.status === 'error') {
|
||||||
statusIcon = <Warning />;
|
statusIcon = <Warning />;
|
||||||
|
statusColor = 'red';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid condensed fullWidth>
|
<Grid condensed fullWidth>
|
||||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
<Column sm={2} md={2} lg={3} className="grid-list-title">
|
||||||
|
Status:{' '}
|
||||||
|
</Column>
|
||||||
|
<Column sm={3} md={6} lg={8}>
|
||||||
|
<Tag type={statusColor} size="sm" className="span-tag">
|
||||||
|
{processInstance.status} {statusIcon}
|
||||||
|
</Tag>
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
<Grid condensed fullWidth>
|
||||||
|
<Column sm={2} md={2} lg={3} className="grid-list-title">
|
||||||
Started By:{' '}
|
Started By:{' '}
|
||||||
</Column>
|
</Column>
|
||||||
<Column sm={3} md={3} lg={3} className="grid-date">
|
<Column sm={3} md={6} lg={8} className="grid-date">
|
||||||
{processInstance.process_initiator_username}
|
{processInstance.process_initiator_username}
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
{processInstance.process_model_with_diagram_identifier ? (
|
{processInstance.process_model_with_diagram_identifier ? (
|
||||||
<Grid condensed fullWidth>
|
<Grid condensed fullWidth>
|
||||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
<Column sm={2} md={2} lg={3} className="grid-list-title">
|
||||||
Current Diagram:{' '}
|
Current Diagram:{' '}
|
||||||
</Column>
|
</Column>
|
||||||
<Column sm={4} md={6} lg={8} className="grid-date">
|
<Column sm={4} md={6} lg={8} className="grid-date">
|
||||||
|
@ -361,7 +374,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
<Grid condensed fullWidth>
|
<Grid condensed fullWidth>
|
||||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
<Column sm={2} md={2} lg={3} className="grid-list-title">
|
||||||
Started:{' '}
|
Started:{' '}
|
||||||
</Column>
|
</Column>
|
||||||
<Column
|
<Column
|
||||||
|
@ -380,72 +393,26 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
</Grid>
|
</Grid>
|
||||||
{lastUpdatedTimeTag}
|
{lastUpdatedTimeTag}
|
||||||
<Grid condensed fullWidth>
|
<Grid condensed fullWidth>
|
||||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
<Column sm={2} md={2} lg={3} className="grid-list-title">
|
||||||
Process model revision:{' '}
|
Process model revision:{' '}
|
||||||
</Column>
|
</Column>
|
||||||
<Column sm={3} md={3} lg={3} className="grid-date">
|
<Column sm={3} md={6} lg={8} className="grid-date">
|
||||||
{processInstance.bpmn_version_control_identifier} (
|
{processInstance.bpmn_version_control_identifier} (
|
||||||
{processInstance.bpmn_version_control_type})
|
{processInstance.bpmn_version_control_type})
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid condensed fullWidth>
|
{(processInstance.process_metadata || []).map(
|
||||||
<Column sm={1} md={1} lg={2} className="grid-list-title">
|
(processInstanceMetadata) => (
|
||||||
Status:{' '}
|
<Grid condensed fullWidth>
|
||||||
</Column>
|
<Column sm={2} md={2} lg={3} className="grid-list-title">
|
||||||
<Column sm={3} md={3} lg={3}>
|
{processInstanceMetadata.key}:
|
||||||
<Tag type="gray" size="sm" className="span-tag">
|
</Column>
|
||||||
{processInstance.status} {statusIcon}
|
<Column sm={3} md={6} lg={8} className="grid-date">
|
||||||
</Tag>
|
{processInstanceMetadata.value}
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
<br />
|
)
|
||||||
<Grid condensed fullWidth>
|
)}
|
||||||
<Column sm={2} md={2} lg={2}>
|
|
||||||
<ButtonSet>
|
|
||||||
<Can
|
|
||||||
I="GET"
|
|
||||||
a={targetUris.processInstanceLogListPath}
|
|
||||||
ability={ability}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="button-white-background"
|
|
||||||
data-qa="process-instance-log-list-link"
|
|
||||||
href={`${processInstanceLogListPageBaseUrl}`}
|
|
||||||
>
|
|
||||||
Logs
|
|
||||||
</Button>
|
|
||||||
</Can>
|
|
||||||
<Can
|
|
||||||
I="GET"
|
|
||||||
a={targetUris.messageInstanceListPath}
|
|
||||||
ability={ability}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="button-white-background"
|
|
||||||
data-qa="process-instance-message-instance-list-link"
|
|
||||||
href={`/admin/messages?process_model_id=${params.process_model_id}&process_instance_id=${params.process_instance_id}`}
|
|
||||||
>
|
|
||||||
Messages
|
|
||||||
</Button>
|
|
||||||
</Can>
|
|
||||||
{processInstance.process_metadata &&
|
|
||||||
processInstance.process_metadata.length > 0 ? (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="button-white-background"
|
|
||||||
data-qa="process-instance-show-metadata"
|
|
||||||
onClick={() => {
|
|
||||||
setShowProcessInstanceMetadata(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Details
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</ButtonSet>
|
|
||||||
</Column>
|
|
||||||
</Grid>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -943,41 +910,6 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const processInstanceMetadataArea = () => {
|
|
||||||
if (
|
|
||||||
!processInstance ||
|
|
||||||
(processInstance.process_metadata &&
|
|
||||||
processInstance.process_metadata.length < 1)
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const metadataComponents: any[] = [];
|
|
||||||
(processInstance.process_metadata || []).forEach(
|
|
||||||
(processInstanceMetadata: ProcessInstanceMetadata) => {
|
|
||||||
metadataComponents.push(
|
|
||||||
<Grid condensed fullWidth>
|
|
||||||
<Column sm={3} md={3} lg={5} className="grid-list-title">
|
|
||||||
{processInstanceMetadata.key}
|
|
||||||
</Column>
|
|
||||||
<Column sm={3} md={3} lg={3} className="grid-date">
|
|
||||||
{processInstanceMetadata.value}
|
|
||||||
</Column>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
open={showProcessInstanceMetadata}
|
|
||||||
modalHeading="Details"
|
|
||||||
passiveModal
|
|
||||||
onRequestClose={() => setShowProcessInstanceMetadata(false)}
|
|
||||||
>
|
|
||||||
{metadataComponents}
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const taskUpdateDisplayArea = () => {
|
const taskUpdateDisplayArea = () => {
|
||||||
if (!taskToDisplay) {
|
if (!taskToDisplay) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1094,76 +1026,126 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (processInstance && (tasks || tasksCallHadError)) {
|
if (processInstance && (tasks || tasksCallHadError) && permissionsLoaded) {
|
||||||
const processModelId = unModifyProcessIdentifierForPathParam(
|
const processModelId = unModifyProcessIdentifierForPathParam(
|
||||||
params.process_model_id ? params.process_model_id : ''
|
params.process_model_id ? params.process_model_id : ''
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getTabs = () => {
|
||||||
|
const canViewLogs = ability.can(
|
||||||
|
'GET',
|
||||||
|
targetUris.processInstanceLogListPath
|
||||||
|
);
|
||||||
|
const canViewMsgs = ability.can(
|
||||||
|
'GET',
|
||||||
|
targetUris.messageInstanceListPath
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMessageDisplay = () => {
|
||||||
|
if (canViewMsgs) {
|
||||||
|
return <MessageInstanceList processInstanceId={processInstance.id} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs>
|
||||||
|
<TabList aria-label="List of tabs">
|
||||||
|
<Tab>Diagram</Tab>
|
||||||
|
<Tab disabled={!canViewLogs}>Milestones</Tab>
|
||||||
|
<Tab disabled={!canViewLogs}>Events</Tab>
|
||||||
|
<Tab disabled={!canViewMsgs}>Messages</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanels>
|
||||||
|
<TabPanel>
|
||||||
|
<ReactDiagramEditor
|
||||||
|
processModelId={processModelId || ''}
|
||||||
|
diagramXML={processInstance.bpmn_xml_file_contents || ''}
|
||||||
|
fileName={processInstance.bpmn_xml_file_contents || ''}
|
||||||
|
tasks={tasks}
|
||||||
|
diagramType="readonly"
|
||||||
|
onElementClick={handleClickedDiagramTask}
|
||||||
|
/>
|
||||||
|
<div id="diagram-container" />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<ProcessInstanceLogList
|
||||||
|
variant={variant}
|
||||||
|
isEventsView={false}
|
||||||
|
processModelId={modifiedProcessModelId || ''}
|
||||||
|
processInstanceId={processInstance.id}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<ProcessInstanceLogList
|
||||||
|
variant={variant}
|
||||||
|
isEventsView
|
||||||
|
processModelId={modifiedProcessModelId || ''}
|
||||||
|
processInstanceId={processInstance.id}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>{getMessageDisplay()}</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProcessBreadcrumb
|
<div className="show-page">
|
||||||
hotCrumbs={[
|
<ProcessBreadcrumb
|
||||||
['Process Groups', '/admin'],
|
hotCrumbs={[
|
||||||
{
|
['Process Groups', '/admin'],
|
||||||
entityToExplode: processModelId,
|
{
|
||||||
entityType: 'process-model-id',
|
entityToExplode: processModelId,
|
||||||
linkLastItem: true,
|
entityType: 'process-model-id',
|
||||||
},
|
linkLastItem: true,
|
||||||
[`Process Instance Id: ${processInstance.id}`],
|
},
|
||||||
]}
|
[`Process Instance Id: ${processInstance.id}`],
|
||||||
/>
|
]}
|
||||||
<Stack orientation="horizontal" gap={1}>
|
/>
|
||||||
<h1 className="with-icons">
|
<Stack orientation="horizontal" gap={1}>
|
||||||
Process Instance Id: {processInstance.id}
|
<h1 className="with-icons">
|
||||||
</h1>
|
Process Instance Id: {processInstance.id}
|
||||||
{buttonIcons()}
|
</h1>
|
||||||
</Stack>
|
{buttonIcons()}
|
||||||
<ProcessInterstitial
|
</Stack>
|
||||||
processInstanceId={processInstance.id}
|
{getInfoTag()}
|
||||||
processInstanceShowPageUrl={processInstanceShowPageBaseUrl}
|
<ProcessInterstitial
|
||||||
allowRedirect={false}
|
processInstanceId={processInstance.id}
|
||||||
smallSpinner
|
processInstanceShowPageUrl={processInstanceShowPageBaseUrl}
|
||||||
/>
|
allowRedirect={false}
|
||||||
<br />
|
smallSpinner
|
||||||
<br />
|
collapsableInstructions
|
||||||
<Grid condensed fullWidth>
|
executeTasks={false}
|
||||||
<Column md={6} lg={8} sm={4}>
|
/>
|
||||||
<TaskListTable
|
<Grid condensed fullWidth>
|
||||||
apiPath="/tasks"
|
<Column md={6} lg={8} sm={4}>
|
||||||
additionalParams={`process_instance_id=${processInstance.id}`}
|
<TaskListTable
|
||||||
tableTitle="Tasks I can complete"
|
apiPath="/tasks"
|
||||||
tableDescription="These are tasks that can be completed by you, either because they were assigned to a group you are in, or because they were assigned directly to you."
|
additionalParams={`process_instance_id=${processInstance.id}`}
|
||||||
paginationClassName="with-large-bottom-margin"
|
tableTitle="Tasks I can complete"
|
||||||
textToShowIfEmpty="There are no tasks you can complete for this process instance."
|
tableDescription="These are tasks that can be completed by you, either because they were assigned to a group you are in, or because they were assigned directly to you."
|
||||||
shouldPaginateTable={false}
|
paginationClassName="with-large-bottom-margin"
|
||||||
showProcessModelIdentifier={false}
|
textToShowIfEmpty="There are no tasks you can complete for this process instance."
|
||||||
showProcessId={false}
|
shouldPaginateTable={false}
|
||||||
showStartedBy={false}
|
showProcessModelIdentifier={false}
|
||||||
showTableDescriptionAsTooltip
|
showProcessId={false}
|
||||||
showDateStarted={false}
|
showStartedBy={false}
|
||||||
showLastUpdated={false}
|
showTableDescriptionAsTooltip
|
||||||
hideIfNoTasks
|
showDateStarted={false}
|
||||||
canCompleteAllTasks
|
showLastUpdated={false}
|
||||||
/>
|
hideIfNoTasks
|
||||||
</Column>
|
canCompleteAllTasks
|
||||||
</Grid>
|
/>
|
||||||
{getInfoTag()}
|
</Column>
|
||||||
<br />
|
</Grid>
|
||||||
{taskUpdateDisplayArea()}
|
{taskUpdateDisplayArea()}
|
||||||
{processDataDisplayArea()}
|
{processDataDisplayArea()}
|
||||||
{processInstanceMetadataArea()}
|
<br />
|
||||||
<br />
|
{viewMostRecentStateComponent()}
|
||||||
{viewMostRecentStateComponent()}
|
</div>
|
||||||
<ReactDiagramEditor
|
{getTabs()}
|
||||||
processModelId={processModelId || ''}
|
|
||||||
diagramXML={processInstance.bpmn_xml_file_contents || ''}
|
|
||||||
fileName={processInstance.bpmn_xml_file_contents || ''}
|
|
||||||
tasks={tasks}
|
|
||||||
diagramType="readonly"
|
|
||||||
onElementClick={handleClickedDiagramTask}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div id="diagram-container" />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default function ProcessInterstitialPage({ variant }: OwnProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="show-page">
|
||||||
<ProcessBreadcrumb
|
<ProcessBreadcrumb
|
||||||
hotCrumbs={[
|
hotCrumbs={[
|
||||||
['Process Groups', '/admin'],
|
['Process Groups', '/admin'],
|
||||||
|
@ -36,6 +36,6 @@ export default function ProcessInterstitialPage({ variant }: OwnProps) {
|
||||||
processInstanceShowPageUrl={processInstanceShowPageUrl}
|
processInstanceShowPageUrl={processInstanceShowPageUrl}
|
||||||
allowRedirect
|
allowRedirect
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default function ProcessModelShow() {
|
||||||
[targetUris.processModelShowPath]: ['PUT', 'DELETE'],
|
[targetUris.processModelShowPath]: ['PUT', 'DELETE'],
|
||||||
[targetUris.processModelTestsPath]: ['POST'],
|
[targetUris.processModelTestsPath]: ['POST'],
|
||||||
[targetUris.processModelPublishPath]: ['POST'],
|
[targetUris.processModelPublishPath]: ['POST'],
|
||||||
[targetUris.processInstanceListPath]: ['GET'],
|
[targetUris.processInstanceListForMePath]: ['POST'],
|
||||||
[targetUris.processInstanceCreatePath]: ['POST'],
|
[targetUris.processInstanceCreatePath]: ['POST'],
|
||||||
[targetUris.processModelFileCreatePath]: ['POST', 'PUT', 'GET', 'DELETE'],
|
[targetUris.processModelFileCreatePath]: ['POST', 'PUT', 'GET', 'DELETE'],
|
||||||
};
|
};
|
||||||
|
@ -616,7 +616,7 @@ export default function ProcessModelShow() {
|
||||||
|
|
||||||
if (processModel) {
|
if (processModel) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="show-page">
|
||||||
{fileUploadModal()}
|
{fileUploadModal()}
|
||||||
{confirmOverwriteFileDialog()}
|
{confirmOverwriteFileDialog()}
|
||||||
<ProcessBreadcrumb
|
<ProcessBreadcrumb
|
||||||
|
@ -697,7 +697,11 @@ export default function ProcessModelShow() {
|
||||||
</Can>
|
</Can>
|
||||||
</Stack>
|
</Stack>
|
||||||
{processModelFilesSection()}
|
{processModelFilesSection()}
|
||||||
<Can I="GET" a={targetUris.processInstanceListPath} ability={ability}>
|
<Can
|
||||||
|
I="POST"
|
||||||
|
a={targetUris.processInstanceListForMePath}
|
||||||
|
ability={ability}
|
||||||
|
>
|
||||||
<ProcessInstanceListTable
|
<ProcessInstanceListTable
|
||||||
headerElement={<h2>My Process Instances</h2>}
|
headerElement={<h2>My Process Instances</h2>}
|
||||||
filtersEnabled={false}
|
filtersEnabled={false}
|
||||||
|
@ -714,7 +718,7 @@ export default function ProcessModelShow() {
|
||||||
/>
|
/>
|
||||||
<span data-qa="process-model-show-permissions-loaded" />
|
<span data-qa="process-model-show-permissions-loaded" />
|
||||||
</Can>
|
</Can>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -502,7 +502,7 @@ export default function TaskShow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<div className="show-page">
|
||||||
<ProcessBreadcrumb
|
<ProcessBreadcrumb
|
||||||
hotCrumbs={[
|
hotCrumbs={[
|
||||||
[
|
[
|
||||||
|
@ -521,7 +521,7 @@ export default function TaskShow() {
|
||||||
</h3>
|
</h3>
|
||||||
<InstructionsForEndUser task={task} />
|
<InstructionsForEndUser task={task} />
|
||||||
{formElement()}
|
{formElement()}
|
||||||
</main>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue