mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-01-13 19:55:24 +00:00
multiinstance ui (#469)
* multiinstance ui * fix black * added migration merge file --------- Co-authored-by: burnettk <burnettk@users.noreply.github.com> Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
ef297b1eeb
commit
75e6007ef3
32
spiffworkflow-backend/migrations/versions/5579975401dd_.py
Normal file
32
spiffworkflow-backend/migrations/versions/5579975401dd_.py
Normal file
@ -0,0 +1,32 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 5579975401dd
|
||||
Revises: 1073364bc015
|
||||
Create Date: 2023-09-01 11:07:39.816184
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5579975401dd'
|
||||
down_revision = '1073364bc015'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('task', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('runtime_info', sa.JSON(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('task', schema=None) as batch_op:
|
||||
batch_op.drop_column('runtime_info')
|
||||
|
||||
# ### end Alembic commands ###
|
@ -0,0 +1,24 @@
|
||||
"""merging two heads
|
||||
|
||||
Revision ID: 57df21dc569d
|
||||
Revises: 5579975401dd, f04cbd9f43ec
|
||||
Create Date: 2023-09-07 13:54:23.061873
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '57df21dc569d'
|
||||
down_revision = ('5579975401dd', 'f04cbd9f43ec')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
pass
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
@ -65,6 +65,7 @@ class TaskModel(SpiffworkflowBaseDBModel):
|
||||
|
||||
json_data_hash: str = db.Column(db.String(255), nullable=False, index=True)
|
||||
python_env_data_hash: str = db.Column(db.String(255), nullable=False, index=True)
|
||||
runtime_info: dict = db.Column(db.JSON)
|
||||
|
||||
start_in_seconds: float | None = db.Column(db.DECIMAL(17, 6))
|
||||
end_in_seconds: float | None = db.Column(db.DECIMAL(17, 6))
|
||||
@ -140,6 +141,7 @@ class Task:
|
||||
multi_instance_index: str = "",
|
||||
process_identifier: str = "",
|
||||
properties: dict | None = None,
|
||||
runtime_info: dict | None = None,
|
||||
process_instance_id: int | None = None,
|
||||
process_instance_status: str | None = None,
|
||||
process_model_display_name: str | None = None,
|
||||
@ -297,6 +299,7 @@ class TaskSchema(Schema):
|
||||
"form_schema",
|
||||
"form_ui_schema",
|
||||
"event_definition",
|
||||
"runtime_info",
|
||||
]
|
||||
|
||||
multi_instance_type = EnumField(MultiInstanceType)
|
||||
|
@ -500,10 +500,14 @@ def process_instance_task_list(
|
||||
task_models_of_parent_bpmn_processes,
|
||||
) = TaskService.task_models_of_parent_bpmn_processes(to_task_model)
|
||||
task_models_of_parent_bpmn_processes_guids = [p.guid for p in task_models_of_parent_bpmn_processes if p.guid]
|
||||
if "instance" in to_task_model.runtime_info or "iteration" in to_task_model.runtime_info:
|
||||
to_task_model_parent = [to_task_model.properties_json["parent"]]
|
||||
else:
|
||||
to_task_model_parent = []
|
||||
task_model_query = task_model_query.filter(
|
||||
or_(
|
||||
TaskModel.end_in_seconds <= to_task_model.end_in_seconds, # type: ignore
|
||||
TaskModel.guid.in_(task_models_of_parent_bpmn_processes_guids), # type: ignore
|
||||
TaskModel.guid.in_(task_models_of_parent_bpmn_processes_guids + to_task_model_parent), # type: ignore
|
||||
)
|
||||
)
|
||||
|
||||
@ -545,6 +549,7 @@ def process_instance_task_list(
|
||||
TaskModel.state,
|
||||
TaskModel.end_in_seconds,
|
||||
TaskModel.start_in_seconds,
|
||||
TaskModel.runtime_info,
|
||||
)
|
||||
)
|
||||
|
||||
@ -554,6 +559,7 @@ def process_instance_task_list(
|
||||
task_models = task_model_query.all()
|
||||
if most_recent_tasks_only:
|
||||
most_recent_tasks = {}
|
||||
additional_tasks = []
|
||||
|
||||
# if you have a loop and there is a subprocess, and you are going around for the second time,
|
||||
# ignore the tasks in the "first loop" subprocess
|
||||
@ -573,9 +579,13 @@ def process_instance_task_list(
|
||||
most_recent_tasks[row_key] = task_model
|
||||
if task_model.typename in ["SubWorkflowTask", "CallActivity"]:
|
||||
relevant_subprocess_guids.add(task_model.guid)
|
||||
elif "instance" in task_model.runtime_info or "iteration" in task_model.runtime_info:
|
||||
# This handles adding all instances of a MI and iterations of loop tasks
|
||||
additional_tasks.append(task_model)
|
||||
|
||||
task_models = [
|
||||
task_model
|
||||
for task_model in most_recent_tasks.values()
|
||||
for task_model in list(most_recent_tasks.values()) + additional_tasks
|
||||
if task_model.bpmn_process_guid in relevant_subprocess_guids
|
||||
]
|
||||
|
||||
|
@ -276,6 +276,7 @@ class TaskService:
|
||||
self.json_data_dicts[json_data_dict["hash"]] = json_data_dict
|
||||
if python_env_dict is not None:
|
||||
self.json_data_dicts[python_env_dict["hash"]] = python_env_dict
|
||||
task_model.runtime_info = spiff_task.task_spec.task_info(spiff_task)
|
||||
|
||||
def find_or_create_task_model_from_spiff_task(
|
||||
self,
|
||||
|
@ -86,6 +86,7 @@ export interface Task extends BasicTask {
|
||||
|
||||
event_definition?: EventDefinition;
|
||||
saved_form_data?: any;
|
||||
runtime_info?: any;
|
||||
}
|
||||
|
||||
// Currently used like ApiTask in backend
|
||||
|
@ -23,9 +23,12 @@ import {
|
||||
Warning,
|
||||
} from '@carbon/icons-react';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
Grid,
|
||||
Column,
|
||||
Button,
|
||||
ButtonSet,
|
||||
Tag,
|
||||
Modal,
|
||||
Dropdown,
|
||||
@ -1008,7 +1011,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
height={`${heightInEm}rem`}
|
||||
width="auto"
|
||||
defaultLanguage="json"
|
||||
defaultValue={taskDataToDisplay}
|
||||
value={taskDataToDisplay}
|
||||
onChange={(value) => {
|
||||
setTaskDataToDisplay(value || '');
|
||||
}}
|
||||
@ -1102,6 +1105,98 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
return dataArea;
|
||||
};
|
||||
|
||||
const switchToTask = (taskId: string) => {
|
||||
if (tasks) {
|
||||
const task = tasks.find((t: Task) => t.guid === taskId);
|
||||
if (task) {
|
||||
setTaskToDisplay(task);
|
||||
initializeTaskDataToDisplay(task);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const multiInstanceSelector = () => {
|
||||
if (!taskToDisplay) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clickAction = (item: any) => {
|
||||
return () => {
|
||||
switchToTask(taskToDisplay.runtime_info.instance_map[item]);
|
||||
};
|
||||
};
|
||||
const createButtonSet = (instances: string[]) => {
|
||||
return (
|
||||
<ButtonSet stacked>
|
||||
{instances.map((v: any) => (
|
||||
<Button kind="ghost" onClick={clickAction(v)}>
|
||||
{v}
|
||||
</Button>
|
||||
))}
|
||||
</ButtonSet>
|
||||
);
|
||||
};
|
||||
|
||||
if (
|
||||
taskToDisplay.typename === 'ParallelMultiInstanceTask' ||
|
||||
taskToDisplay.typename === 'SequentialMultiInstanceTask'
|
||||
) {
|
||||
let completedInstances = null;
|
||||
if (taskToDisplay.runtime_info.completed.length > 0) {
|
||||
completedInstances = createButtonSet(
|
||||
taskToDisplay.runtime_info.completed
|
||||
);
|
||||
}
|
||||
let runningInstances = null;
|
||||
if (taskToDisplay.runtime_info.running.length > 0) {
|
||||
runningInstances = createButtonSet(taskToDisplay.runtime_info.running);
|
||||
}
|
||||
let futureInstances = null;
|
||||
if (taskToDisplay.runtime_info.future.length > 0) {
|
||||
futureInstances = createButtonSet(taskToDisplay.runtime_info.future);
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionItem title="Completed instances">
|
||||
{completedInstances}
|
||||
</AccordionItem>
|
||||
<AccordionItem title="Running instances">
|
||||
{runningInstances}
|
||||
</AccordionItem>
|
||||
<AccordionItem title="Future instances">
|
||||
{futureInstances}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
if (taskToDisplay.typename === 'StandardLoopTask') {
|
||||
const buttons = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i < taskToDisplay.runtime_info.iterations_completed;
|
||||
i += 1
|
||||
)
|
||||
buttons.push(
|
||||
<Button kind="ghost" onClick={clickAction(i)}>
|
||||
{i}
|
||||
</Button>
|
||||
);
|
||||
let text = 'Loop iterations';
|
||||
if (
|
||||
typeof taskToDisplay.runtime_info.iterations_remaining !== 'undefined'
|
||||
)
|
||||
text += ` (${taskToDisplay.runtime_info.iterations_remaining} remaining)`;
|
||||
return (
|
||||
<div>
|
||||
<div>{text}</div>
|
||||
<div>{buttons}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const taskUpdateDisplayArea = () => {
|
||||
if (!taskToDisplay) {
|
||||
return null;
|
||||
@ -1133,6 +1228,18 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
dangerous = true;
|
||||
}
|
||||
|
||||
if (typeof taskToUse.runtime_info.instance !== 'undefined') {
|
||||
secondaryButtonText = 'Return to MultiInstance Task';
|
||||
onSecondarySubmit = () => {
|
||||
switchToTask(taskToUse.properties_json.parent);
|
||||
};
|
||||
} else if (typeof taskToUse.runtime_info.iteration !== 'undefined') {
|
||||
secondaryButtonText = 'Return to Loop Task';
|
||||
onSecondarySubmit = () => {
|
||||
switchToTask(taskToUse.properties_json.parent);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={!!taskToUse}
|
||||
@ -1174,6 +1281,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
||||
</div>
|
||||
) : null}
|
||||
{taskActionDetails()}
|
||||
{multiInstanceSelector()}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user