run_pyl
This commit is contained in:
parent
0f3ef00d72
commit
0030a46938
|
@ -0,0 +1,139 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 4ab08bf12666
|
||||||
|
Revises: b581ff2351ad
|
||||||
|
Create Date: 2023-02-23 07:25:54.135765
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4ab08bf12666'
|
||||||
|
down_revision = 'b581ff2351ad'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('message_instance_correlation',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('message_instance_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=50), nullable=False),
|
||||||
|
sa.Column('expected_value', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('retrieval_expression', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('created_at_in_seconds', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['message_instance_id'], ['message_instance.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('message_instance_id', 'name', name='message_instance_id_name_unique')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_message_instance_correlation_expected_value'), 'message_instance_correlation', ['expected_value'], unique=False)
|
||||||
|
op.create_index(op.f('ix_message_instance_correlation_message_instance_id'), 'message_instance_correlation', ['message_instance_id'], unique=False)
|
||||||
|
|
||||||
|
op.drop_index('ix_message_correlation_property_identifier', table_name='message_correlation_property')
|
||||||
|
op.drop_index('message_correlation_property_unique', table_name='message_correlation_property')
|
||||||
|
op.drop_constraint(u'message_correlation_ibfk_1', 'message_correlation', type_='foreignkey')
|
||||||
|
op.drop_table('message_correlation_property')
|
||||||
|
op.drop_index('ix_message_correlation_message_correlation_property_id', table_name='message_correlation')
|
||||||
|
op.drop_index('ix_message_correlation_name', table_name='message_correlation')
|
||||||
|
op.drop_index('ix_message_correlation_process_instance_id', table_name='message_correlation')
|
||||||
|
op.drop_index('ix_message_correlation_value', table_name='message_correlation')
|
||||||
|
op.drop_constraint(u'message_correlation_message_instance_ibfk_1', 'message_correlation_message_instance', type_='foreignkey')
|
||||||
|
op.drop_constraint(u'message_triggerable_process_model_ibfk_1', 'message_triggerable_process_model', type_='foreignkey')
|
||||||
|
op.drop_table('message_correlation')
|
||||||
|
op.drop_index('ix_message_model_identifier', table_name='message_model')
|
||||||
|
op.drop_index('ix_message_model_name', table_name='message_model')
|
||||||
|
op.drop_constraint(u'message_instance_ibfk_1', 'message_instance', type_='foreignkey')
|
||||||
|
op.drop_table('message_model')
|
||||||
|
op.drop_table('message_correlation_message_instance')
|
||||||
|
op.add_column('message_instance', sa.Column('name', sa.String(length=255), nullable=True))
|
||||||
|
op.add_column('message_instance', sa.Column('user_id', sa.Integer(), nullable=False))
|
||||||
|
op.add_column('message_instance', sa.Column('counterpart_id', sa.Integer(), nullable=True))
|
||||||
|
op.alter_column('message_instance', 'process_instance_id',
|
||||||
|
existing_type=mysql.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
op.create_foreign_key(None, 'message_instance', 'user', ['user_id'], ['id'])
|
||||||
|
op.drop_column('message_instance', 'message_model_id')
|
||||||
|
op.add_column('message_triggerable_process_model', sa.Column('message_name', sa.String(length=255), nullable=True))
|
||||||
|
op.drop_index('message_model_id', table_name='message_triggerable_process_model')
|
||||||
|
op.drop_column('message_triggerable_process_model', 'message_model_id')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('message_triggerable_process_model', sa.Column('message_model_id', mysql.INTEGER(), autoincrement=False, nullable=False))
|
||||||
|
op.create_foreign_key('message_triggerable_process_model_ibfk_1', 'message_triggerable_process_model', 'message_model', ['message_model_id'], ['id'])
|
||||||
|
op.create_index('message_model_id', 'message_triggerable_process_model', ['message_model_id'], unique=False)
|
||||||
|
op.drop_column('message_triggerable_process_model', 'name')
|
||||||
|
op.add_column('message_instance', sa.Column('message_model_id', mysql.INTEGER(), autoincrement=False, nullable=False))
|
||||||
|
op.drop_constraint(None, 'message_instance', type_='foreignkey')
|
||||||
|
op.create_foreign_key('message_instance_ibfk_1', 'message_instance', 'message_model', ['message_model_id'], ['id'])
|
||||||
|
op.alter_column('message_instance', 'process_instance_id',
|
||||||
|
existing_type=mysql.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_column('message_instance', 'counterpart_id')
|
||||||
|
op.drop_column('message_instance', 'user_id')
|
||||||
|
op.drop_column('message_instance', 'name')
|
||||||
|
op.create_table('message_correlation_message_instance',
|
||||||
|
sa.Column('message_instance_id', mysql.INTEGER(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('message_correlation_id', mysql.INTEGER(), autoincrement=False, nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['message_correlation_id'], ['message_correlation.id'], name='message_correlation_message_instance_ibfk_1'),
|
||||||
|
sa.ForeignKeyConstraint(['message_instance_id'], ['message_instance.id'], name='message_correlation_message_instance_ibfk_2'),
|
||||||
|
sa.PrimaryKeyConstraint('message_instance_id', 'message_correlation_id'),
|
||||||
|
mysql_collate='utf8mb4_0900_ai_ci',
|
||||||
|
mysql_default_charset='utf8mb4',
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
op.create_table('message_model',
|
||||||
|
sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('identifier', mysql.VARCHAR(length=50), nullable=True),
|
||||||
|
sa.Column('name', mysql.VARCHAR(length=50), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
mysql_collate='utf8mb4_0900_ai_ci',
|
||||||
|
mysql_default_charset='utf8mb4',
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
op.create_index('ix_message_model_name', 'message_model', ['name'], unique=False)
|
||||||
|
op.create_index('ix_message_model_identifier', 'message_model', ['identifier'], unique=False)
|
||||||
|
op.create_table('message_correlation',
|
||||||
|
sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('process_instance_id', mysql.INTEGER(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('message_correlation_property_id', mysql.INTEGER(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('name', mysql.VARCHAR(length=255), nullable=False),
|
||||||
|
sa.Column('value', mysql.VARCHAR(length=255), nullable=False),
|
||||||
|
sa.Column('updated_at_in_seconds', mysql.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('created_at_in_seconds', mysql.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['message_correlation_property_id'], ['message_correlation_property.id'], name='message_correlation_ibfk_1'),
|
||||||
|
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], name='message_correlation_ibfk_2'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
mysql_collate='utf8mb4_0900_ai_ci',
|
||||||
|
mysql_default_charset='utf8mb4',
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
op.create_index('message_instance_id_name_unique', 'message_correlation', ['process_instance_id', 'message_correlation_property_id', 'name'], unique=False)
|
||||||
|
op.create_index('ix_message_correlation_value', 'message_correlation', ['value'], unique=False)
|
||||||
|
op.create_index('ix_message_correlation_process_instance_id', 'message_correlation', ['process_instance_id'], unique=False)
|
||||||
|
op.create_index('ix_message_correlation_name', 'message_correlation', ['name'], unique=False)
|
||||||
|
op.create_index('ix_message_correlation_message_correlation_property_id', 'message_correlation', ['message_correlation_property_id'], unique=False)
|
||||||
|
op.create_table('message_correlation_property',
|
||||||
|
sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('identifier', mysql.VARCHAR(length=50), nullable=True),
|
||||||
|
sa.Column('message_model_id', mysql.INTEGER(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('updated_at_in_seconds', mysql.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('created_at_in_seconds', mysql.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], name='message_correlation_property_ibfk_1'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
mysql_collate='utf8mb4_0900_ai_ci',
|
||||||
|
mysql_default_charset='utf8mb4',
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
||||||
|
op.create_index('message_correlation_property_unique', 'message_correlation_property', ['identifier', 'message_model_id'], unique=False)
|
||||||
|
op.create_index('ix_message_correlation_property_identifier', 'message_correlation_property', ['identifier'], unique=False)
|
||||||
|
op.drop_index(op.f('ix_message_instance_correlation_message_instance_id'), table_name='message_instance_correlation')
|
||||||
|
op.drop_index(op.f('ix_message_instance_correlation_expected_value'), table_name='message_instance_correlation')
|
||||||
|
op.drop_table('message_instance_correlation')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -3,17 +3,24 @@ import enum
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine # type: ignore
|
||||||
from sqlalchemy import ForeignKey
|
from sqlalchemy import ForeignKey
|
||||||
from sqlalchemy.event import listens_for
|
from sqlalchemy.event import listens_for
|
||||||
from sqlalchemy.orm import Session, relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy.orm import validates
|
from sqlalchemy.orm import validates
|
||||||
|
|
||||||
from spiffworkflow_backend.models.db import db
|
from spiffworkflow_backend.models.db import db
|
||||||
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine # type: ignore
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from spiffworkflow_backend.models.message_instance_correlation import ( # noqa: F401
|
||||||
|
MessageInstanceCorrelationModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MessageTypes(enum.Enum):
|
class MessageTypes(enum.Enum):
|
||||||
|
@ -46,11 +53,15 @@ class MessageInstanceModel(SpiffworkflowBaseDBModel):
|
||||||
status: str = db.Column(db.String(20), nullable=False, default="ready")
|
status: str = db.Column(db.String(20), nullable=False, default="ready")
|
||||||
user_id: int = db.Column(ForeignKey(UserModel.id), nullable=False) # type: ignore
|
user_id: int = db.Column(ForeignKey(UserModel.id), nullable=False) # type: ignore
|
||||||
user = relationship("UserModel")
|
user = relationship("UserModel")
|
||||||
counterpart_id: int = db.Column(db.Integer) # Not enforcing self-referential foreign key so we can delete messages.
|
counterpart_id: int = db.Column(
|
||||||
|
db.Integer
|
||||||
|
) # Not enforcing self-referential foreign key so we can delete messages.
|
||||||
failure_cause: str = db.Column(db.Text())
|
failure_cause: str = db.Column(db.Text())
|
||||||
updated_at_in_seconds: int = db.Column(db.Integer)
|
updated_at_in_seconds: int = db.Column(db.Integer)
|
||||||
created_at_in_seconds: int = db.Column(db.Integer)
|
created_at_in_seconds: int = db.Column(db.Integer)
|
||||||
correlations = relationship("MessageInstanceCorrelationModel", back_populates="message_instance")
|
correlations = relationship(
|
||||||
|
"MessageInstanceCorrelationModel", back_populates="message_instance"
|
||||||
|
)
|
||||||
|
|
||||||
@validates("message_type")
|
@validates("message_type")
|
||||||
def validate_message_type(self, key: str, value: Any) -> Any:
|
def validate_message_type(self, key: str, value: Any) -> Any:
|
||||||
|
@ -62,7 +73,9 @@ class MessageInstanceModel(SpiffworkflowBaseDBModel):
|
||||||
"""Validate_status."""
|
"""Validate_status."""
|
||||||
return self.validate_enum_field(key, value, MessageStatuses)
|
return self.validate_enum_field(key, value, MessageStatuses)
|
||||||
|
|
||||||
def correlates(self, other_message_instance: Any, expression_engine: PythonScriptEngine) -> bool:
|
def correlates(
|
||||||
|
self, other_message_instance: Any, expression_engine: PythonScriptEngine
|
||||||
|
) -> bool:
|
||||||
# This must be a receive message, and the other must be a send (otherwise we reverse the call)
|
# This must be a receive message, and the other must be a send (otherwise we reverse the call)
|
||||||
# We evaluate the other messages payload and run our correlation's
|
# We evaluate the other messages payload and run our correlation's
|
||||||
# retrieval expressions against it, then compare it against our
|
# retrieval expressions against it, then compare it against our
|
||||||
|
@ -76,26 +89,33 @@ class MessageInstanceModel(SpiffworkflowBaseDBModel):
|
||||||
payload = other_message_instance.payload
|
payload = other_message_instance.payload
|
||||||
for corr in self.correlations:
|
for corr in self.correlations:
|
||||||
try:
|
try:
|
||||||
result = expression_engine._evaluate(corr.retrieval_expression, payload)
|
result = expression_engine._evaluate(
|
||||||
except Exception as e:
|
corr.retrieval_expression, payload
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
# the failure of a payload evaluation may not mean that matches for these
|
# the failure of a payload evaluation may not mean that matches for these
|
||||||
# message instances can't happen with other messages. So don't error up.
|
# message instances can't happen with other messages. So don't error up.
|
||||||
# fixme: Perhaps log some sort of error.
|
# fixme: Perhaps log some sort of error.
|
||||||
return False
|
return False
|
||||||
if corr.expected_value is None:
|
if corr.expected_value is None:
|
||||||
continue # We will accept any value
|
continue # We will accept any value
|
||||||
elif corr.expected_value != str(result): # fixme: Don't require conversion to string
|
elif corr.expected_value != str(
|
||||||
|
result
|
||||||
|
): # fixme: Don't require conversion to string
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
elif other_message_instance.message_type == MessageTypes.receive.value:
|
elif other_message_instance.message_type == MessageTypes.receive.value:
|
||||||
return other_message_instance.correlates(self, expression_engine)
|
return other_message_instance.correlates(self, expression_engine)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# This runs for ALL db flushes for ANY model, not just this one even if it's in the MessageInstanceModel class
|
# This runs for ALL db flushes for ANY model, not just this one even if it's in the MessageInstanceModel class
|
||||||
# so this may not be worth it or there may be a better way to do it
|
# so this may not be worth it or there may be a better way to do it
|
||||||
#
|
#
|
||||||
# https://stackoverflow.com/questions/32555829/flask-validates-decorator-multiple-fields-simultaneously/33025472#33025472
|
# https://stackoverflow.com/questions/32555829/flask-validates-decorator-multiple-fields-simultaneously/33025472#33025472
|
||||||
# https://docs.sqlalchemy.org/en/14/orm/session_events.html#before-flush
|
# https://docs.sqlalchemy.org/en/14/orm/session_events.html#before-flush
|
||||||
|
|
||||||
|
|
||||||
@listens_for(Session, "before_flush") # type: ignore
|
@listens_for(Session, "before_flush") # type: ignore
|
||||||
def ensure_failure_cause_is_set_if_message_instance_failed(
|
def ensure_failure_cause_is_set_if_message_instance_failed(
|
||||||
session: Any, _flush_context: Optional[Any], _instances: Optional[Any]
|
session: Any, _flush_context: Optional[Any], _instances: Optional[Any]
|
||||||
|
|
|
@ -8,13 +8,16 @@ from spiffworkflow_backend.models.db import db
|
||||||
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||||
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MessageInstanceCorrelationModel(SpiffworkflowBaseDBModel):
|
class MessageInstanceCorrelationModel(SpiffworkflowBaseDBModel):
|
||||||
"""These are the correlations of a specific Message Instance - these will
|
"""These are the correlations of a specific Message Instance.
|
||||||
only exist on receive messages. It provides the expression to run on a
|
|
||||||
send messages payload which must match the expected value to be considered
|
These will only exist on receive messages. It provides the expression to run on
|
||||||
a valid match. If the expected value is null, then it does not need to
|
a send messages payload which must match the expected value to be considered
|
||||||
match, but the expression should still evaluate and produce a result."""
|
a valid match. If the expected value is null, then it does not need to
|
||||||
|
match, but the expression should still evaluate and produce a result.
|
||||||
|
"""
|
||||||
|
|
||||||
__tablename__ = "message_instance_correlation"
|
__tablename__ = "message_instance_correlation"
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
|
@ -34,4 +37,6 @@ class MessageInstanceCorrelationModel(SpiffworkflowBaseDBModel):
|
||||||
retrieval_expression: str = db.Column(db.String(255))
|
retrieval_expression: str = db.Column(db.String(255))
|
||||||
updated_at_in_seconds: int = db.Column(db.Integer)
|
updated_at_in_seconds: int = db.Column(db.Integer)
|
||||||
created_at_in_seconds: int = db.Column(db.Integer)
|
created_at_in_seconds: int = db.Column(db.Integer)
|
||||||
message_instance = relationship("MessageInstanceModel", back_populates="correlations")
|
message_instance = relationship(
|
||||||
|
"MessageInstanceModel", back_populates="correlations"
|
||||||
|
)
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
"""Message_correlation_property."""
|
"""Message_correlation_property."""
|
||||||
from sqlalchemy import ForeignKey
|
|
||||||
|
|
||||||
from spiffworkflow_backend.models.db import db
|
from spiffworkflow_backend.models.db import db
|
||||||
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from flask.wrappers import Response
|
||||||
|
|
||||||
from spiffworkflow_backend import db
|
from spiffworkflow_backend import db
|
||||||
from spiffworkflow_backend.exceptions.api_error import ApiError
|
from spiffworkflow_backend.exceptions.api_error import ApiError
|
||||||
from spiffworkflow_backend.models.message_instance import MessageInstanceModel, MessageStatuses
|
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
||||||
from spiffworkflow_backend.services.message_service import MessageService
|
from spiffworkflow_backend.services.message_service import MessageService
|
||||||
|
@ -37,7 +37,7 @@ def message_instance_list(
|
||||||
MessageInstanceModel.created_at_in_seconds.desc(), # type: ignore
|
MessageInstanceModel.created_at_in_seconds.desc(), # type: ignore
|
||||||
MessageInstanceModel.id.desc(), # type: ignore
|
MessageInstanceModel.id.desc(), # type: ignore
|
||||||
)
|
)
|
||||||
.outerjoin(ProcessInstanceModel) # Not all messages were created by a process
|
.outerjoin(ProcessInstanceModel) # Not all messages were created by a process
|
||||||
.add_columns(
|
.add_columns(
|
||||||
ProcessInstanceModel.process_model_identifier,
|
ProcessInstanceModel.process_model_identifier,
|
||||||
ProcessInstanceModel.process_model_display_name,
|
ProcessInstanceModel.process_model_display_name,
|
||||||
|
@ -66,7 +66,6 @@ def message_send(
|
||||||
body: Dict[str, Any],
|
body: Dict[str, Any],
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Message_start."""
|
"""Message_start."""
|
||||||
|
|
||||||
if "payload" not in body:
|
if "payload" not in body:
|
||||||
raise (
|
raise (
|
||||||
ApiError(
|
ApiError(
|
||||||
|
@ -83,10 +82,9 @@ def message_send(
|
||||||
|
|
||||||
# Create the send message
|
# Create the send message
|
||||||
message_instance = MessageInstanceModel(
|
message_instance = MessageInstanceModel(
|
||||||
process_instance_id=None,
|
|
||||||
message_type="send",
|
message_type="send",
|
||||||
name=message_name,
|
name=message_name,
|
||||||
payload=body['payload'],
|
payload=body["payload"],
|
||||||
user_id=g.user.id,
|
user_id=g.user.id,
|
||||||
correlations=[],
|
correlations=[],
|
||||||
)
|
)
|
||||||
|
@ -102,18 +100,21 @@ def message_send(
|
||||||
db.session.delete(message_instance)
|
db.session.delete(message_instance)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
raise (
|
raise (
|
||||||
ApiError(
|
ApiError(
|
||||||
error_code="message_not_accepted",
|
error_code="message_not_accepted",
|
||||||
message=(
|
message=(
|
||||||
"No running process instances correlate with the given message name of"
|
"No running process instances correlate with the given message"
|
||||||
f" '{message_name}'. And this message name is not"
|
f" name of '{message_name}'. And this message name is not"
|
||||||
" currently associated with any process Start Event. Nothing to do."
|
" currently associated with any process Start Event. Nothing"
|
||||||
),
|
" to do."
|
||||||
status_code=400,
|
),
|
||||||
)
|
status_code=400,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
process_instance = ProcessInstanceModel.query.filter_by(id=receiver_message.process_instance_id).first()
|
process_instance = ProcessInstanceModel.query.filter_by(
|
||||||
|
id=receiver_message.process_instance_id
|
||||||
|
).first()
|
||||||
return Response(
|
return Response(
|
||||||
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
|
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
|
||||||
status=200,
|
status=200,
|
||||||
|
|
|
@ -91,7 +91,6 @@ class ErrorHandlingService:
|
||||||
|
|
||||||
# Create the send message
|
# Create the send message
|
||||||
message_instance = MessageInstanceModel(
|
message_instance = MessageInstanceModel(
|
||||||
process_instance_id=None,
|
|
||||||
message_type="send",
|
message_type="send",
|
||||||
name=message_name,
|
name=message_name,
|
||||||
payload=message_payload,
|
payload=message_payload,
|
||||||
|
@ -102,8 +101,7 @@ class ErrorHandlingService:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
process_instance = MessageService.start_process_with_message(
|
process_instance = MessageService.start_process_with_message(
|
||||||
message_triggerable_process_model,
|
message_triggerable_process_model, message_instance
|
||||||
message_instance
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
|
|
|
@ -7,7 +7,10 @@ from spiffworkflow_backend.models.message_triggerable_process_model import (
|
||||||
)
|
)
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.services.process_instance_processor import (
|
from spiffworkflow_backend.services.process_instance_processor import (
|
||||||
ProcessInstanceProcessor, CustomBpmnScriptEngine,
|
CustomBpmnScriptEngine,
|
||||||
|
)
|
||||||
|
from spiffworkflow_backend.services.process_instance_processor import (
|
||||||
|
ProcessInstanceProcessor,
|
||||||
)
|
)
|
||||||
from spiffworkflow_backend.services.process_instance_service import (
|
from spiffworkflow_backend.services.process_instance_service import (
|
||||||
ProcessInstanceService,
|
ProcessInstanceService,
|
||||||
|
@ -16,16 +19,20 @@ from spiffworkflow_backend.services.process_instance_service import (
|
||||||
|
|
||||||
class MessageServiceError(Exception):
|
class MessageServiceError(Exception):
|
||||||
"""MessageServiceError."""
|
"""MessageServiceError."""
|
||||||
def __init__(self, msg, is_fatal=False):
|
|
||||||
self.is_fatal = is_fatal
|
|
||||||
super().__init__(msg)
|
|
||||||
|
|
||||||
class MessageService:
|
class MessageService:
|
||||||
"""MessageService."""
|
"""MessageService."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def correlate_send_message(cls, message_instance_send: MessageInstanceModel):
|
def correlate_send_message(
|
||||||
|
cls, message_instance_send: MessageInstanceModel
|
||||||
|
) -> MessageInstanceModel | None:
|
||||||
|
"""Connects the given send message to a 'receive' message if possible.
|
||||||
|
|
||||||
|
:param message_instance_send:
|
||||||
|
:return: the message instance that received this message.
|
||||||
|
"""
|
||||||
# Thread safe via db locking - don't try to progress the same send message over multiple instances
|
# Thread safe via db locking - don't try to progress the same send message over multiple instances
|
||||||
if message_instance_send.status != MessageStatuses.ready.value:
|
if message_instance_send.status != MessageStatuses.ready.value:
|
||||||
return None
|
return None
|
||||||
|
@ -35,13 +42,14 @@ class MessageService:
|
||||||
|
|
||||||
# Find available messages that might match
|
# Find available messages that might match
|
||||||
available_receive_messages = MessageInstanceModel.query.filter_by(
|
available_receive_messages = MessageInstanceModel.query.filter_by(
|
||||||
name=message_instance_send.name,
|
name=message_instance_send.name, status=MessageStatuses.ready.value
|
||||||
status=MessageStatuses.ready.value
|
|
||||||
).all()
|
).all()
|
||||||
message_instance_receive = None
|
message_instance_receive = None
|
||||||
try:
|
try:
|
||||||
for message_instance in available_receive_messages:
|
for message_instance in available_receive_messages:
|
||||||
if message_instance.correlates(message_instance_send, CustomBpmnScriptEngine()):
|
if message_instance.correlates(
|
||||||
|
message_instance_send, CustomBpmnScriptEngine()
|
||||||
|
):
|
||||||
message_instance_receive = message_instance
|
message_instance_receive = message_instance
|
||||||
|
|
||||||
if message_instance_receive is None:
|
if message_instance_receive is None:
|
||||||
|
@ -52,18 +60,26 @@ class MessageService:
|
||||||
).first()
|
).first()
|
||||||
)
|
)
|
||||||
if message_triggerable_process_model:
|
if message_triggerable_process_model:
|
||||||
receiving_process = MessageService.start_process_with_message(message_triggerable_process_model,
|
receiving_process = MessageService.start_process_with_message(
|
||||||
message_instance_send)
|
message_triggerable_process_model, message_instance_send
|
||||||
|
)
|
||||||
message_instance_receive = MessageInstanceModel.query.filter_by(
|
message_instance_receive = MessageInstanceModel.query.filter_by(
|
||||||
process_instance_id=receiving_process.id,
|
process_instance_id=receiving_process.id,
|
||||||
message_type="receive", status="ready"
|
message_type="receive",
|
||||||
|
status="ready",
|
||||||
).first()
|
).first()
|
||||||
else:
|
else:
|
||||||
receiving_process = MessageService.get_process_instance_for_message_instance(
|
receiving_process = (
|
||||||
message_instance_receive)
|
MessageService.get_process_instance_for_message_instance(
|
||||||
|
message_instance_receive
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Assure we can send the message, otherwise keep going.
|
# Assure we can send the message, otherwise keep going.
|
||||||
if message_instance_receive is None or not receiving_process.can_receive_message():
|
if (
|
||||||
|
message_instance_receive is None
|
||||||
|
or not receiving_process.can_receive_message()
|
||||||
|
):
|
||||||
message_instance_send.status = "ready"
|
message_instance_send.status = "ready"
|
||||||
message_instance_send.status = "ready"
|
message_instance_send.status = "ready"
|
||||||
db.session.add(message_instance_send)
|
db.session.add(message_instance_send)
|
||||||
|
@ -102,7 +118,7 @@ class MessageService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def correlate_all_message_instances(cls) -> None:
|
def correlate_all_message_instances(cls) -> None:
|
||||||
"""Look at ALL the Send and Receive Messages and attempt to find correlations """
|
"""Look at ALL the Send and Receive Messages and attempt to find correlations."""
|
||||||
message_instances_send = MessageInstanceModel.query.filter_by(
|
message_instances_send = MessageInstanceModel.query.filter_by(
|
||||||
message_type="send", status="ready"
|
message_type="send", status="ready"
|
||||||
).all()
|
).all()
|
||||||
|
@ -110,11 +126,10 @@ class MessageService:
|
||||||
for message_instance_send in message_instances_send:
|
for message_instance_send in message_instances_send:
|
||||||
cls.correlate_send_message(message_instance_send)
|
cls.correlate_send_message(message_instance_send)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def start_process_with_message(
|
def start_process_with_message(
|
||||||
message_triggerable_process_model: MessageTriggerableProcessModel,
|
message_triggerable_process_model: MessageTriggerableProcessModel,
|
||||||
message_instance: MessageInstanceModel
|
message_instance: MessageInstanceModel,
|
||||||
) -> ProcessInstanceModel:
|
) -> ProcessInstanceModel:
|
||||||
"""Start up a process instance, so it is ready to catch the event."""
|
"""Start up a process instance, so it is ready to catch the event."""
|
||||||
process_instance_receive = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
process_instance_receive = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||||
|
@ -126,8 +141,8 @@ class MessageService:
|
||||||
return process_instance_receive
|
return process_instance_receive
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_process_instance_for_message_instance (
|
def get_process_instance_for_message_instance(
|
||||||
message_instance_receive: MessageInstanceModel,
|
message_instance_receive: MessageInstanceModel,
|
||||||
) -> ProcessInstanceModel:
|
) -> ProcessInstanceModel:
|
||||||
"""Process_message_receive."""
|
"""Process_message_receive."""
|
||||||
process_instance_receive = ProcessInstanceModel.query.filter_by(
|
process_instance_receive = ProcessInstanceModel.query.filter_by(
|
||||||
|
@ -141,23 +156,21 @@ class MessageService:
|
||||||
f" {message_instance_receive.id}.Tried with id"
|
f" {message_instance_receive.id}.Tried with id"
|
||||||
f" {message_instance_receive.process_instance_id}"
|
f" {message_instance_receive.process_instance_id}"
|
||||||
),
|
),
|
||||||
), is_fatal=True
|
)
|
||||||
)
|
)
|
||||||
return process_instance_receive
|
return process_instance_receive
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_message_receive (
|
def process_message_receive(
|
||||||
process_instance_receive: ProcessInstanceModel,
|
process_instance_receive: ProcessInstanceModel,
|
||||||
message_instance_receive: MessageInstanceModel,
|
message_instance_receive: MessageInstanceModel,
|
||||||
message_model_name: str,
|
message_model_name: str,
|
||||||
message_payload: dict,
|
message_payload: dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
""" """
|
"""process_message_receive."""
|
||||||
|
|
||||||
processor_receive = ProcessInstanceProcessor(process_instance_receive)
|
processor_receive = ProcessInstanceProcessor(process_instance_receive)
|
||||||
processor_receive.bpmn_process_instance.catch_bpmn_message(
|
processor_receive.bpmn_process_instance.catch_bpmn_message(
|
||||||
message_model_name,
|
message_model_name, message_payload
|
||||||
message_payload
|
|
||||||
)
|
)
|
||||||
processor_receive.do_engine_steps(save=True)
|
processor_receive.do_engine_steps(save=True)
|
||||||
message_instance_receive.status = MessageStatuses.completed.value
|
message_instance_receive.status = MessageStatuses.completed.value
|
||||||
|
|
|
@ -61,7 +61,9 @@ from spiffworkflow_backend.models.group import GroupModel
|
||||||
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
||||||
from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel
|
from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel
|
||||||
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
||||||
from spiffworkflow_backend.models.message_instance_correlation import MessageInstanceCorrelationModel
|
from spiffworkflow_backend.models.message_instance_correlation import (
|
||||||
|
MessageInstanceCorrelationModel,
|
||||||
|
)
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||||
|
@ -1340,7 +1342,7 @@ class ProcessInstanceProcessor:
|
||||||
for bpmn_message in bpmn_messages:
|
for bpmn_message in bpmn_messages:
|
||||||
message_instance = MessageInstanceModel(
|
message_instance = MessageInstanceModel(
|
||||||
process_instance_id=self.process_instance_model.id,
|
process_instance_id=self.process_instance_model.id,
|
||||||
user_id=self.process_instance_model.process_initiator_id, # TODO: use the correct swimlane user when that is set up
|
user_id=self.process_instance_model.process_initiator_id, # TODO: use the correct swimlane user when that is set up
|
||||||
message_type="send",
|
message_type="send",
|
||||||
name=bpmn_message.name,
|
name=bpmn_message.name,
|
||||||
payload=bpmn_message.payload,
|
payload=bpmn_message.payload,
|
||||||
|
@ -1352,15 +1354,20 @@ class ProcessInstanceProcessor:
|
||||||
def queue_waiting_receive_messages(self) -> None:
|
def queue_waiting_receive_messages(self) -> None:
|
||||||
"""Queue_waiting_receive_messages."""
|
"""Queue_waiting_receive_messages."""
|
||||||
waiting_events = self.bpmn_process_instance.waiting_events()
|
waiting_events = self.bpmn_process_instance.waiting_events()
|
||||||
waiting_message_events = filter(lambda e: e['event_type'] == "Message", waiting_events)
|
waiting_message_events = filter(
|
||||||
|
lambda e: e["event_type"] == "Message", waiting_events
|
||||||
|
)
|
||||||
|
|
||||||
for event in waiting_message_events:
|
for event in waiting_message_events:
|
||||||
|
|
||||||
# Ensure we are only creating one message instance for each waiting message
|
# Ensure we are only creating one message instance for each waiting message
|
||||||
if MessageInstanceModel.query.filter_by(
|
if (
|
||||||
process_instance_id=self.process_instance_model.id,
|
MessageInstanceModel.query.filter_by(
|
||||||
message_type="receive",
|
process_instance_id=self.process_instance_model.id,
|
||||||
name=event['name']).count() > 0:
|
message_type="receive",
|
||||||
|
name=event["name"],
|
||||||
|
).count()
|
||||||
|
> 0
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Create a new Message Instance
|
# Create a new Message Instance
|
||||||
|
@ -1368,14 +1375,14 @@ class ProcessInstanceProcessor:
|
||||||
process_instance_id=self.process_instance_model.id,
|
process_instance_id=self.process_instance_model.id,
|
||||||
user_id=self.process_instance_model.process_initiator_id,
|
user_id=self.process_instance_model.process_initiator_id,
|
||||||
message_type="receive",
|
message_type="receive",
|
||||||
name=event['name'],
|
name=event["name"],
|
||||||
)
|
)
|
||||||
for correlation_property in event['value']:
|
for correlation_property in event["value"]:
|
||||||
message_correlation = MessageInstanceCorrelationModel (
|
message_correlation = MessageInstanceCorrelationModel(
|
||||||
message_instance_id=message_instance.id,
|
message_instance_id=message_instance.id,
|
||||||
name=correlation_property.name,
|
name=correlation_property.name,
|
||||||
expected_value=correlation_property.expected_value,
|
expected_value=correlation_property.expected_value,
|
||||||
retrieval_expression=correlation_property.retrieval_expression
|
retrieval_expression=correlation_property.retrieval_expression,
|
||||||
)
|
)
|
||||||
message_instance.correlations.append(message_correlation)
|
message_instance.correlations.append(message_correlation)
|
||||||
db.session.add(message_instance)
|
db.session.add(message_instance)
|
||||||
|
@ -1467,7 +1474,7 @@ class ProcessInstanceProcessor:
|
||||||
spiff_logger = logging.getLogger("spiff")
|
spiff_logger = logging.getLogger("spiff")
|
||||||
for handler in spiff_logger.handlers:
|
for handler in spiff_logger.handlers:
|
||||||
if hasattr(handler, "bulk_insert_logs"):
|
if hasattr(handler, "bulk_insert_logs"):
|
||||||
handler.bulk_insert_logs() # type: ignore
|
handler.bulk_insert_logs()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
if save:
|
if save:
|
||||||
|
|
|
@ -332,7 +332,6 @@ class SpecFileService(FileSystemService):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_message_cache(ref: SpecReference) -> None:
|
def update_message_cache(ref: SpecReference) -> None:
|
||||||
"""Assure we have a record in the database of all possible message ids and names."""
|
"""Assure we have a record in the database of all possible message ids and names."""
|
||||||
pass
|
|
||||||
# for message_model_identifier in ref.messages.keys():
|
# for message_model_identifier in ref.messages.keys():
|
||||||
# message_model = MessageModel.query.filter_by(
|
# message_model = MessageModel.query.filter_by(
|
||||||
# identifier=message_model_identifier
|
# identifier=message_model_identifier
|
||||||
|
@ -374,7 +373,6 @@ class SpecFileService(FileSystemService):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_correlation_cache(ref: SpecReference) -> None:
|
def update_correlation_cache(ref: SpecReference) -> None:
|
||||||
"""Update_correlation_cache."""
|
"""Update_correlation_cache."""
|
||||||
pass
|
|
||||||
# for correlation_identifier in ref.correlations.keys():
|
# for correlation_identifier in ref.correlations.keys():
|
||||||
# correlation_property_retrieval_expressions = ref.correlations[
|
# correlation_property_retrieval_expressions = ref.correlations[
|
||||||
# correlation_identifier
|
# correlation_identifier
|
||||||
|
|
|
@ -2330,7 +2330,7 @@ class TestProcessApi(BaseTest):
|
||||||
assert response.json is not None
|
assert response.json is not None
|
||||||
process_instance_id_one = response.json["id"]
|
process_instance_id_one = response.json["id"]
|
||||||
|
|
||||||
payload['po_number'] = "1002"
|
payload["po_number"] = "1002"
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/v1.0/messages/{message_model_identifier}",
|
f"/v1.0/messages/{message_model_identifier}",
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
|
@ -2347,7 +2347,9 @@ class TestProcessApi(BaseTest):
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json is not None
|
assert response.json is not None
|
||||||
assert len(response.json["results"]) == 2 # Two messages, one is the completed receive, the other is new send
|
assert (
|
||||||
|
len(response.json["results"]) == 2
|
||||||
|
) # Two messages, one is the completed receive, the other is new send
|
||||||
assert (
|
assert (
|
||||||
response.json["results"][0]["process_instance_id"]
|
response.json["results"][0]["process_instance_id"]
|
||||||
== process_instance_id_one
|
== process_instance_id_one
|
||||||
|
|
|
@ -53,8 +53,8 @@ class TestMessageInstance(BaseTest):
|
||||||
process_instance_id=process_instance.id,
|
process_instance_id=process_instance.id,
|
||||||
user_id=process_instance.process_initiator_id,
|
user_id=process_instance.process_initiator_id,
|
||||||
message_type="send",
|
message_type="send",
|
||||||
name =message_name,
|
name=message_name,
|
||||||
payload={"Word":"Eat At Mashita's, delicious!"}
|
payload={"Word": "Eat At Mashita's, delicious!"},
|
||||||
)
|
)
|
||||||
db.session.add(queued_message)
|
db.session.add(queued_message)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -138,7 +138,7 @@ class TestMessageInstance(BaseTest):
|
||||||
process_instance_id=process_instance.id,
|
process_instance_id=process_instance.id,
|
||||||
user_id=process_instance.process_initiator_id,
|
user_id=process_instance.process_initiator_id,
|
||||||
message_type="BAD_MESSAGE_TYPE",
|
message_type="BAD_MESSAGE_TYPE",
|
||||||
name=message_name
|
name=message_name,
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
str(exception.value)
|
str(exception.value)
|
||||||
|
@ -213,5 +213,3 @@ class TestMessageInstance(BaseTest):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
assert queued_message.id is not None
|
assert queued_message.id is not None
|
||||||
assert queued_message.failure_cause == "THIS TEST FAILURE"
|
assert queued_message.failure_cause == "THIS TEST FAILURE"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -208,9 +208,7 @@ class TestMessageService(BaseTest):
|
||||||
) -> None:
|
) -> None:
|
||||||
# Correlation Properties should match up
|
# Correlation Properties should match up
|
||||||
po_curr = next(c for c in message.correlations if c.name == "po_number")
|
po_curr = next(c for c in message.correlations if c.name == "po_number")
|
||||||
customer_curr = next(
|
customer_curr = next(c for c in message.correlations if c.name == "customer_id")
|
||||||
c for c in message.correlations if c.name == "customer_id"
|
|
||||||
)
|
|
||||||
assert po_curr is not None
|
assert po_curr is not None
|
||||||
assert customer_curr is not None
|
assert customer_curr is not None
|
||||||
assert po_curr.expected_value == "1001"
|
assert po_curr.expected_value == "1001"
|
||||||
|
@ -273,13 +271,12 @@ class TestMessageService(BaseTest):
|
||||||
assert len(orig_send_messages) == 2
|
assert len(orig_send_messages) == 2
|
||||||
assert MessageInstanceModel.query.filter_by(message_type="receive").count() == 1
|
assert MessageInstanceModel.query.filter_by(message_type="receive").count() == 1
|
||||||
|
|
||||||
|
|
||||||
# process message instances
|
# process message instances
|
||||||
MessageService.correlate_all_message_instances()
|
MessageService.correlate_all_message_instances()
|
||||||
# Once complete the original send messages should be completed and two new instances
|
# Once complete the original send messages should be completed and two new instances
|
||||||
# should now exist, one for each of the process instances ...
|
# should now exist, one for each of the process instances ...
|
||||||
# for osm in orig_send_messages:
|
# for osm in orig_send_messages:
|
||||||
# assert osm.status == "completed"
|
# assert osm.status == "completed"
|
||||||
|
|
||||||
process_instance_result = ProcessInstanceModel.query.all()
|
process_instance_result = ProcessInstanceModel.query.all()
|
||||||
assert len(process_instance_result) == 3
|
assert len(process_instance_result) == 3
|
||||||
|
|
Loading…
Reference in New Issue