this is now working and stores state in a sqlite3 db w/ burnettk
This commit is contained in:
parent
4330fdb7ee
commit
4b3a91c13c
|
@ -0,0 +1,26 @@
|
|||
#!/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
|
||||
|
||||
if [[ "${1:-}" == "c" ]]; then
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{}'
|
||||
elif grep -qE '^[0-9]$' <<<"${1:-}" ; then
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d "{ \"task_identifier\": \"${1}\"}"
|
||||
else
|
||||
rm -rf currentstate.json
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Product Name": "G", "Quantity": "2"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Sleeve Type": "Short"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Continue shopping?": "N"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Shipping Method": "Overnight"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Shipping Address": "Somewhere"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Place Order": "Y"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Card Number": "MY_CARD"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "2", "answer": {"Was the customer charged?": "Y"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Was the product available?": "Y"}}'
|
||||
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Was the order shipped?": "Y"}}'
|
||||
fi
|
|
@ -0,0 +1 @@
|
|||
Single-database configuration for Flask.
|
|
@ -0,0 +1,50 @@
|
|||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic,flask_migrate
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[logger_flask_migrate]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = flask_migrate
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
|
@ -0,0 +1,91 @@
|
|||
from __future__ import with_statement
|
||||
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from alembic import context
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
config.set_main_option(
|
||||
'sqlalchemy.url',
|
||||
str(current_app.extensions['migrate'].db.get_engine().url).replace(
|
||||
'%', '%%'))
|
||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url, target_metadata=target_metadata, literal_binds=True
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
connectable = current_app.extensions['migrate'].db.get_engine()
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
**current_app.extensions['migrate'].configure_args
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
|
@ -0,0 +1,24 @@
|
|||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
|
@ -0,0 +1,38 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: c73669985652
|
||||
Revises:
|
||||
Create Date: 2022-05-16 15:19:43.112086
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'c73669985652'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('process_models',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('bpmn_json', sa.JSON(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=50), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('user')
|
||||
op.drop_table('process_models')
|
||||
# ### end Alembic commands ###
|
|
@ -1,122 +0,0 @@
|
|||
"""Methods to talk to the database."""
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from spiff_workflow_webapp.models.data_store import DataStoreModel
|
||||
from spiff_workflow_webapp.models.data_store import DataStoreSchema
|
||||
from spiff_workflow_webapp.services.data_store_service import DataStoreBase
|
||||
from flask import Blueprint
|
||||
from sqlalchemy.orm import Session # type: ignore
|
||||
|
||||
from spiff_workflow_webapp.api.api_error import ApiError
|
||||
|
||||
# from crc import session
|
||||
|
||||
|
||||
def construct_blueprint(database_session: Session) -> Blueprint:
|
||||
"""Construct_blueprint."""
|
||||
data_store_blueprint = Blueprint("data_store", __name__)
|
||||
database_session = database_session
|
||||
|
||||
def study_multi_get(study_id: str) -> Any:
|
||||
"""Get all data_store values for a given study_id study."""
|
||||
if study_id is None:
|
||||
raise ApiError("unknown_study", "Please provide a valid Study ID.")
|
||||
|
||||
dsb = DataStoreBase()
|
||||
retval = dsb.get_multi_common(study_id, None)
|
||||
results = DataStoreSchema(many=True).dump(retval)
|
||||
return results
|
||||
|
||||
def user_multi_get(user_id: str) -> Any:
|
||||
"""Get all data values in the data_store for a userid."""
|
||||
if user_id is None:
|
||||
raise ApiError("unknown_study", "Please provide a valid UserID.")
|
||||
|
||||
dsb = DataStoreBase()
|
||||
retval = dsb.get_multi_common(None, user_id)
|
||||
results = DataStoreSchema(many=True).dump(retval)
|
||||
return results
|
||||
|
||||
def file_multi_get(file_id: str) -> Any:
|
||||
"""Get all data values in the data store for a file_id."""
|
||||
if file_id is None:
|
||||
raise ApiError(
|
||||
code="unknown_file", message="Please provide a valid file id."
|
||||
)
|
||||
dsb = DataStoreBase()
|
||||
retval = dsb.get_multi_common(None, None, file_id=file_id)
|
||||
results = DataStoreSchema(many=True).dump(retval)
|
||||
return results
|
||||
|
||||
def datastore_del(id: str) -> Any:
|
||||
"""Delete a data store item for a key."""
|
||||
database_session.query(DataStoreModel).filter_by(id=id).delete()
|
||||
database_session.commit()
|
||||
json_value = json.dumps("deleted", ensure_ascii=False, indent=2)
|
||||
return json_value
|
||||
|
||||
def datastore_get(id: str) -> Any:
|
||||
"""Retrieve a data store item by a key."""
|
||||
item = database_session.query(DataStoreModel).filter_by(id=id).first()
|
||||
results = DataStoreSchema(many=False).dump(item)
|
||||
return results
|
||||
|
||||
def update_datastore(id: str, body: dict) -> Any:
|
||||
"""Allow a modification to a datastore item."""
|
||||
if id is None:
|
||||
raise ApiError("unknown_id", "Please provide a valid ID.")
|
||||
|
||||
item = database_session.query(DataStoreModel).filter_by(id=id).first()
|
||||
if item is None:
|
||||
raise ApiError("unknown_item", 'The item "' + id + '" is not recognized.')
|
||||
|
||||
DataStoreSchema().load(body, instance=item, database_session=database_session)
|
||||
item.last_updated = datetime.utcnow()
|
||||
database_session.add(item)
|
||||
database_session.commit()
|
||||
return DataStoreSchema().dump(item)
|
||||
|
||||
def add_datastore(body: dict) -> Any:
|
||||
"""Add a new datastore item."""
|
||||
if body.get(id, None):
|
||||
raise ApiError(
|
||||
"id_specified", "You may not specify an id for a new datastore item"
|
||||
)
|
||||
|
||||
if "key" not in body:
|
||||
raise ApiError(
|
||||
"no_key", "You need to specify a key to add a datastore item"
|
||||
)
|
||||
|
||||
if "value" not in body:
|
||||
raise ApiError(
|
||||
"no_value", "You need to specify a value to add a datastore item"
|
||||
)
|
||||
|
||||
if (
|
||||
("user_id" not in body)
|
||||
and ("study_id" not in body)
|
||||
and ("file_id" not in body)
|
||||
):
|
||||
raise ApiError(
|
||||
"conflicting_values",
|
||||
"A datastore item should have either a study_id, user_id or file_id ",
|
||||
)
|
||||
|
||||
present = 0
|
||||
for field in ["user_id", "study_id", "file_id"]:
|
||||
if field in body:
|
||||
present = present + 1
|
||||
if present > 1:
|
||||
message = "A datastore item should have one of a study_id, user_id or a file_id but not more than one of these"
|
||||
raise ApiError("conflicting_values", message)
|
||||
|
||||
item = DataStoreSchema().load(body)
|
||||
# item.last_updated = datetime.utcnow() # Do this in the database
|
||||
database_session.add(item)
|
||||
database_session.commit()
|
||||
return DataStoreSchema().dump(item)
|
||||
|
||||
return data_store_blueprint
|
Binary file not shown.
|
@ -1,32 +0,0 @@
|
|||
"""Data_store."""
|
||||
from crc import db
|
||||
from flask_marshmallow.sqla import SQLAlchemyAutoSchema
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
class DataStoreModel(db.Model):
|
||||
"""DataStoreModel."""
|
||||
|
||||
__tablename__ = "data_store"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
last_updated = db.Column(db.DateTime(timezone=True), server_default=func.now())
|
||||
key = db.Column(db.String, nullable=False)
|
||||
workflow_id = db.Column(db.Integer)
|
||||
study_id = db.Column(db.Integer, nullable=True)
|
||||
task_spec = db.Column(db.String)
|
||||
spec_id = db.Column(db.String)
|
||||
user_id = db.Column(db.String, nullable=True)
|
||||
file_id = db.Column(db.Integer, db.ForeignKey("file.id"), nullable=True)
|
||||
value = db.Column(db.String)
|
||||
|
||||
|
||||
class DataStoreSchema(SQLAlchemyAutoSchema):
|
||||
"""DataStoreSchema."""
|
||||
|
||||
class Meta:
|
||||
"""Meta."""
|
||||
|
||||
model = DataStoreModel
|
||||
load_instance = True
|
||||
include_fk = True
|
||||
sqla_session = db.session
|
|
@ -0,0 +1,8 @@
|
|||
from ..extensions import db
|
||||
from sqlalchemy.orm import deferred
|
||||
|
||||
|
||||
class ProcessModel(db.Model):
|
||||
__tablename__ = 'process_models'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
bpmn_json = deferred(db.Column(db.JSON))
|
|
@ -2,26 +2,37 @@
|
|||
import os
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import request
|
||||
|
||||
from ..models.user import User
|
||||
from spiff_workflow_webapp.spiff_workflow_connector import parse
|
||||
from spiff_workflow_webapp.spiff_workflow_connector import run
|
||||
|
||||
from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer
|
||||
from SpiffWorkflow.camunda.serializer.task_spec_converters import UserTaskConverter
|
||||
from SpiffWorkflow.dmn.serializer.task_spec_converters import BusinessRuleTaskConverter
|
||||
from spiff_workflow_webapp.models.process_model import ProcessModel
|
||||
|
||||
wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter([ UserTaskConverter, BusinessRuleTaskConverter ])
|
||||
serializer = BpmnWorkflowSerializer(wf_spec_converter)
|
||||
|
||||
api = Blueprint("api", __name__)
|
||||
|
||||
|
||||
@api.route("/user/<name>")
|
||||
def create_user(name):
|
||||
"""Create_user."""
|
||||
user = User.query.filter_by(name="Anthony").first()
|
||||
user = User.query.filter_by(name=name).first()
|
||||
|
||||
return {"user": user.name}
|
||||
|
||||
|
||||
@api.route("/run_process", defaults={"answer": None, "task_identifier": None})
|
||||
@api.route("/run_process/<task_identifier>", defaults={"answer": None})
|
||||
@api.route("/run_process/<task_identifier>/<answer>")
|
||||
def run_process(task_identifier, answer):
|
||||
# @api.route("/run_process", defaults={"answer": None, "task_identifier": None})
|
||||
# @api.route("/run_process/<task_identifier>", defaults={"answer": None})
|
||||
# @api.route("/run_process/<task_identifier>/<answer>")
|
||||
# def run_process(task_identifier, answer):
|
||||
@api.route("/run_process", methods=['POST'])
|
||||
def run_process():
|
||||
"""Run_process."""
|
||||
# parser = argparse.ArgumentParser("Simple BPMN runner")
|
||||
# parser.add_argument(
|
||||
|
@ -47,7 +58,9 @@ def run_process(task_identifier, answer):
|
|||
# )
|
||||
# args = parser.parse_args()
|
||||
|
||||
# content = request.json
|
||||
content = request.json
|
||||
# if 'task_identifier' in content:
|
||||
|
||||
homedir = os.environ.get("HOME")
|
||||
process = "order_product"
|
||||
dmn = [
|
||||
|
@ -64,8 +77,13 @@ def run_process(task_identifier, answer):
|
|||
# with open(args.restore) as state:
|
||||
# wf = serializer.deserialize_json(state.read())
|
||||
# else:
|
||||
workflow = parse(process, bpmn, dmn)
|
||||
response = run(workflow, task_identifier, answer)
|
||||
workflow = None
|
||||
process_model = ProcessModel.query.filter().first()
|
||||
if process_model is None:
|
||||
workflow = parse(process, bpmn, dmn)
|
||||
else:
|
||||
workflow = serializer.deserialize_json(process_model.bpmn_json)
|
||||
response = run(workflow, content.get("task_identifier"), content.get("answer"))
|
||||
# except Exception:
|
||||
# sys.stderr.write(traceback.format_exc())
|
||||
# sys.exit(1)
|
||||
|
|
|
@ -1,219 +0,0 @@
|
|||
"""Data_store_service."""
|
||||
from crc import session
|
||||
from crc.models.data_store import DataStoreModel
|
||||
from crc.models.workflow import WorkflowModel
|
||||
from flask import g
|
||||
import sqlalchemy
|
||||
from sqlalchemy import desc
|
||||
from spiff_workflow_webapp.api.api_error import ApiError
|
||||
from typing import Any
|
||||
|
||||
|
||||
class DataStoreBase:
|
||||
"""DataStoreBase."""
|
||||
|
||||
def set_validate_common(
|
||||
self, task_id, study_id, workflow_id, script_name, user_id, file_id, *args
|
||||
):
|
||||
"""Set_validate_common."""
|
||||
self.check_args_2(args, script_name)
|
||||
key = args[0]
|
||||
value = args[1]
|
||||
if script_name == "study_data_set":
|
||||
record = {
|
||||
"task_id": task_id,
|
||||
"study_id": study_id,
|
||||
"workflow_id": workflow_id,
|
||||
key: value,
|
||||
}
|
||||
elif script_name == "file_data_set":
|
||||
record = {
|
||||
"task_id": task_id,
|
||||
"study_id": study_id,
|
||||
"workflow_id": workflow_id,
|
||||
"file_id": file_id,
|
||||
key: value,
|
||||
}
|
||||
elif script_name == "user_data_set":
|
||||
record = {
|
||||
"task_id": task_id,
|
||||
"study_id": study_id,
|
||||
"workflow_id": workflow_id,
|
||||
"user_id": user_id,
|
||||
key: value,
|
||||
}
|
||||
g.validation_data_store.append(record)
|
||||
return record
|
||||
|
||||
def get_validate_common(
|
||||
self, script_name, study_id=None, user_id=None, file_id=None, *args
|
||||
):
|
||||
"""This method uses a temporary validation_data_store that is only available for the current validation request.
|
||||
|
||||
This allows us to set data_store values during validation that don't affect the real data_store.
|
||||
For data_store `gets`, we first look in the temporary validation_data_store.
|
||||
If we don't find an entry in validation_data_store, we look in the real data_store.
|
||||
"""
|
||||
key = args[0]
|
||||
if script_name == "study_data_get":
|
||||
# If it's in the validation data store, return it
|
||||
for record in g.validation_data_store:
|
||||
if (
|
||||
"study_id" in record
|
||||
and record["study_id"] == study_id
|
||||
and key in record
|
||||
):
|
||||
return record[key]
|
||||
# If not in validation_data_store, look in the actual data_store
|
||||
return self.get_data_common(
|
||||
study_id, user_id, "study_data_get", file_id, *args
|
||||
)
|
||||
elif script_name == "file_data_get":
|
||||
for record in g.validation_data_store:
|
||||
if (
|
||||
"file_id" in record
|
||||
and record["file_id"] == file_id
|
||||
and key in record
|
||||
):
|
||||
return record[key]
|
||||
return self.get_data_common(
|
||||
study_id, user_id, "file_data_get", file_id, *args
|
||||
)
|
||||
elif script_name == "user_data_get":
|
||||
for record in g.validation_data_store:
|
||||
if (
|
||||
"user_id" in record
|
||||
and record["user_id"] == user_id
|
||||
and key in record
|
||||
):
|
||||
return record[key]
|
||||
return self.get_data_common(
|
||||
study_id, user_id, "user_data_get", file_id, *args
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def check_args(args: Any, maxlen: int = 1, script_name: str = "study_data_get") -> None:
|
||||
"""Check_args."""
|
||||
if len(args) < 1 or len(args) > maxlen:
|
||||
raise ApiError(
|
||||
code="missing_argument",
|
||||
message=f"The {script_name} script takes either one or two arguments, "
|
||||
f"starting with the key and an optional default",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def check_args_2(args: Tuple[str, str], script_name: str = "study_data_set"):
|
||||
"""Check_args_2."""
|
||||
if len(args) != 2:
|
||||
raise ApiError(
|
||||
code="missing_argument",
|
||||
message=f"The {script_name} script takes two arguments, key and value, in that order.",
|
||||
)
|
||||
|
||||
def set_data_common(
|
||||
self, task_spec, study_id, user_id, workflow_id, script_name, file_id, *args
|
||||
):
|
||||
"""Set_data_common."""
|
||||
self.check_args_2(args, script_name=script_name)
|
||||
key = args[0]
|
||||
value = args[1]
|
||||
if value == "" or value is None:
|
||||
# We delete the data store if the value is empty
|
||||
return self.delete_data_store(study_id, user_id, file_id, *args)
|
||||
workflow_spec_id = None
|
||||
if workflow_id is not None:
|
||||
workflow = (
|
||||
session.query(WorkflowModel)
|
||||
.filter(WorkflowModel.id == workflow_id)
|
||||
.first()
|
||||
)
|
||||
workflow_spec_id = workflow.workflow_spec_id
|
||||
|
||||
# Check if this data store is previously set
|
||||
query = session.query(DataStoreModel).filter(DataStoreModel.key == key)
|
||||
if study_id:
|
||||
query = query.filter(DataStoreModel.study_id == study_id)
|
||||
elif file_id:
|
||||
query = query.filter(DataStoreModel.file_id == file_id)
|
||||
elif user_id:
|
||||
query = query.filter(DataStoreModel.user_id == user_id)
|
||||
result = query.order_by(desc(DataStoreModel.last_updated)).all()
|
||||
if result:
|
||||
dsm = result[0]
|
||||
dsm.value = value
|
||||
if task_spec:
|
||||
dsm.task_spec = task_spec
|
||||
if workflow_id:
|
||||
dsm.workflow_id = workflow_id
|
||||
if workflow_spec_id:
|
||||
dsm.spec_id = workflow_spec_id
|
||||
if len(result) > 1:
|
||||
# We had a bug where we had created new records instead of updating values of existing records
|
||||
# This just gets rid of all the old unused records
|
||||
self.delete_extra_data_stores(result[1:])
|
||||
else:
|
||||
dsm = DataStoreModel(
|
||||
key=key,
|
||||
value=value,
|
||||
study_id=study_id,
|
||||
task_spec=task_spec,
|
||||
user_id=user_id, # Make this available to any User
|
||||
file_id=file_id,
|
||||
workflow_id=workflow_id,
|
||||
spec_id=workflow_spec_id,
|
||||
)
|
||||
session.add(dsm)
|
||||
session.commit()
|
||||
|
||||
return dsm.value
|
||||
|
||||
def get_data_common(self, study_id: str, user_id: str, script_name: str, file_id: (str | None) = None, *args: Any) -> Any:
|
||||
"""Get_data_common."""
|
||||
self.check_args(args, 2, script_name)
|
||||
record = (
|
||||
session.query(DataStoreModel)
|
||||
.filter_by(study_id=study_id, user_id=user_id, file_id=file_id, key=args[0])
|
||||
.first()
|
||||
)
|
||||
if record:
|
||||
return record.value
|
||||
else:
|
||||
# This is a possible default value passed in from the data_store get methods
|
||||
if len(args) == 2:
|
||||
return args[1]
|
||||
|
||||
@staticmethod
|
||||
def get_multi_common(study_id: str, user_id: str, file_id: (str | None) = None) -> sqlalchemy.orm.Query:
|
||||
"""Get_multi_common."""
|
||||
results = session.query(DataStoreModel).filter_by(
|
||||
study_id=study_id, user_id=user_id, file_id=file_id
|
||||
)
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def delete_data_store(study_id: str, user_id: str, file_id: str, *args: Any) -> None:
|
||||
"""Delete_data_store."""
|
||||
query = session.query(DataStoreModel).filter(DataStoreModel.key == args[0])
|
||||
if user_id:
|
||||
query = query.filter(DataStoreModel.user_id == user_id)
|
||||
elif file_id:
|
||||
query = query.filter(DataStoreModel.file_id == file_id)
|
||||
elif study_id:
|
||||
query = query.filter(DataStoreModel.study_id == study_id)
|
||||
record = query.first()
|
||||
if record is not None:
|
||||
session.delete(record)
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def delete_extra_data_stores(records: list[DataStoreModel]) -> None:
|
||||
"""We had a bug where we created new records instead of updating existing records.
|
||||
|
||||
We use this to clean up all the extra records.
|
||||
We may remove this method in the future.
|
||||
"""
|
||||
for record in records:
|
||||
session.query(DataStoreModel).filter(
|
||||
DataStoreModel.id == record.id
|
||||
).delete()
|
||||
session.commit()
|
|
@ -19,6 +19,9 @@ from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser
|
|||
from SpiffWorkflow.dmn.serializer.task_spec_converters import BusinessRuleTaskConverter
|
||||
from SpiffWorkflow.task import Task
|
||||
|
||||
from spiff_workflow_webapp.extensions import db
|
||||
from spiff_workflow_webapp.models.process_model import ProcessModel
|
||||
|
||||
# from custom_script_engine import CustomScriptEngine
|
||||
|
||||
wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter(
|
||||
|
@ -43,15 +46,6 @@ def parse(process, bpmn_files, dmn_files):
|
|||
return BpmnWorkflow(parser.get_spec(process))
|
||||
|
||||
|
||||
def select_option(prompt, options):
|
||||
"""Select_option."""
|
||||
option = input(prompt)
|
||||
while option not in options:
|
||||
print("Invalid selection")
|
||||
option = input(prompt)
|
||||
return option
|
||||
|
||||
|
||||
def display_task(task):
|
||||
"""Display_task."""
|
||||
print(f"\n{task.task_spec.description}")
|
||||
|
@ -84,29 +78,30 @@ def complete_user_task(task, answer=None):
|
|||
if answer is None:
|
||||
required_user_input_fields[field.label] = options
|
||||
else:
|
||||
response = option_map[answer]
|
||||
response = option_map[answer[field.label]]
|
||||
elif field.type == "string":
|
||||
if answer is None:
|
||||
required_user_input_fields[field.label] = "STRING"
|
||||
else:
|
||||
response = answer[field.label]
|
||||
else:
|
||||
if answer is None:
|
||||
required_user_input_fields[field.label] = "(1..)"
|
||||
else:
|
||||
if field.type == "long":
|
||||
response = int(answer)
|
||||
task.update_data_var(field.id, response)
|
||||
response = int(answer[field.label])
|
||||
if answer:
|
||||
task.update_data_var(field.id, response)
|
||||
return required_user_input_fields
|
||||
|
||||
|
||||
def complete_manual_task(task):
|
||||
"""Complete_manual_task."""
|
||||
display_task(task)
|
||||
input("Press any key to mark task complete")
|
||||
|
||||
|
||||
def print_state(workflow):
|
||||
"""Print_state."""
|
||||
task = workflow.last_task
|
||||
print("\nLast Task")
|
||||
print(format_task(task))
|
||||
print(json.dumps(task.data, indent=2, separators=[", ", ": "]))
|
||||
# print("\nLast Task")
|
||||
# print(format_task(task))
|
||||
# print(json.dumps(task.data, indent=2, separators=[", ", ": "]))
|
||||
return_json = {"last_task": format_task(task)}
|
||||
|
||||
display_types = (UserTask, ManualTask, ScriptTask, ThrowingEvent, CatchingEvent)
|
||||
all_tasks = [
|
||||
|
@ -118,13 +113,14 @@ def print_state(workflow):
|
|||
task for task in all_tasks if task.state in [Task.READY, Task.WAITING]
|
||||
]
|
||||
|
||||
print("\nUpcoming Tasks")
|
||||
return_json['upcoming_tasks'] = []
|
||||
for _idx, task in enumerate(upcoming_tasks):
|
||||
print(format_task(task))
|
||||
return_json['upcoming_tasks'].append(format_task(task))
|
||||
|
||||
if input("\nShow all tasks? ").lower() == "y":
|
||||
for _idx, task in enumerate(all_tasks):
|
||||
print(format_task(task))
|
||||
# if input("\nShow all tasks? ").lower() == "y":
|
||||
# for _idx, task in enumerate(all_tasks):
|
||||
# print(format_task(task))
|
||||
return return_json
|
||||
|
||||
|
||||
def run(workflow, task_identifier=None, answer=None):
|
||||
|
@ -146,19 +142,6 @@ def run(workflow, task_identifier=None, answer=None):
|
|||
if task_identifier is None:
|
||||
return formatted_options
|
||||
|
||||
# selected = None
|
||||
# while selected not in options and selected not in ["", "D", "d", "exit"]:
|
||||
# selected = input(
|
||||
# "Select task to complete, enter to wait, or D to dump the workflow state: "
|
||||
# )
|
||||
|
||||
# if selected.lower() == "d":
|
||||
# filename = input("Enter filename: ")
|
||||
# state = serializer.serialize_json(workflow)
|
||||
# with open(filename, "w") as dump:
|
||||
# dump.write(state)
|
||||
# elif selected == "exit":
|
||||
# exit()
|
||||
next_task = options[task_identifier]
|
||||
if isinstance(next_task.task_spec, UserTask):
|
||||
if answer is None:
|
||||
|
@ -167,18 +150,34 @@ def run(workflow, task_identifier=None, answer=None):
|
|||
complete_user_task(next_task, answer)
|
||||
next_task.complete()
|
||||
elif isinstance(next_task.task_spec, ManualTask):
|
||||
complete_manual_task(next_task)
|
||||
next_task.complete()
|
||||
else:
|
||||
next_task.complete()
|
||||
|
||||
workflow.refresh_waiting_tasks()
|
||||
workflow.do_engine_steps()
|
||||
tasks_status = {}
|
||||
if step:
|
||||
print_state(workflow)
|
||||
tasks_status = print_state(workflow)
|
||||
|
||||
print("\nWorkflow Data")
|
||||
print(json.dumps(workflow.data, indent=2, separators=[", ", ": "]))
|
||||
ready_tasks = workflow.get_ready_user_tasks()
|
||||
formatted_options = {}
|
||||
for idx, task in enumerate(ready_tasks):
|
||||
option = format_task(task, False)
|
||||
formatted_options[str(idx + 1)] = option
|
||||
|
||||
state = serializer.serialize_json(workflow)
|
||||
process_model = ProcessModel.query.filter().first()
|
||||
if process_model is None:
|
||||
process_model = ProcessModel()
|
||||
process_model.bpmn_json = state
|
||||
db.session.add(process_model)
|
||||
db.session.commit()
|
||||
|
||||
# with open("currentstate.json", "w") as dump:
|
||||
# dump.write(state)
|
||||
tasks_status["next_activity"] = formatted_options
|
||||
return tasks_status
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
Loading…
Reference in New Issue