mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-23 13:18:35 +00:00
Merge pull request #209 from sartography/dev
Email Script, Sentry User details, Workflow Spec Transfers, Navigation refactor
This commit is contained in:
commit
cdd3c13dff
133
Pipfile.lock
generated
133
Pipfile.lock
generated
@ -33,10 +33,10 @@
|
||||
},
|
||||
"aniso8601": {
|
||||
"hashes": [
|
||||
"sha256:529dcb1f5f26ee0df6c0a1ee84b7b27197c3c50fc3a6321d66c544689237d072",
|
||||
"sha256:c033f63d028b9a58e3ab0c2c7d0532ab4bfa7452bfc788fbfe3ddabd327b181a"
|
||||
"sha256:246bf8d3611527030889e6df970878969d3a2f760ba3eb694fa1fb10e6ce53f9",
|
||||
"sha256:51047d4fb51d7b8afd522b70f2d21a1b2487cbb7f7bd84ea852e9aa7808e7704"
|
||||
],
|
||||
"version": "==8.0.0"
|
||||
"version": "==8.1.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
@ -80,10 +80,10 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
|
||||
"sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
|
||||
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||
],
|
||||
"version": "==2020.11.8"
|
||||
"version": "==2020.12.5"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
@ -108,12 +108,14 @@
|
||||
"sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35",
|
||||
"sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26",
|
||||
"sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b",
|
||||
"sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01",
|
||||
"sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb",
|
||||
"sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293",
|
||||
"sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd",
|
||||
"sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d",
|
||||
"sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3",
|
||||
"sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d",
|
||||
"sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e",
|
||||
"sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca",
|
||||
"sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d",
|
||||
"sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775",
|
||||
@ -548,40 +550,40 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
|
||||
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
|
||||
"sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
|
||||
"sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
|
||||
],
|
||||
"version": "==20.4"
|
||||
"version": "==20.8"
|
||||
},
|
||||
"pandas": {
|
||||
"hashes": [
|
||||
"sha256:09e0503758ad61afe81c9069505f8cb8c1e36ea8cc1e6826a95823ef5b327daf",
|
||||
"sha256:0a11a6290ef3667575cbd4785a1b62d658c25a2fd70a5adedba32e156a8f1773",
|
||||
"sha256:0d9a38a59242a2f6298fff45d09768b78b6eb0c52af5919ea9e45965d7ba56d9",
|
||||
"sha256:112c5ba0f9ea0f60b2cc38c25f87ca1d5ca10f71efbee8e0f1bee9cf584ed5d5",
|
||||
"sha256:185cf8c8f38b169dbf7001e1a88c511f653fbb9dfa3e048f5e19c38049e991dc",
|
||||
"sha256:3aa8e10768c730cc1b610aca688f588831fa70b65a26cb549fbb9f35049a05e0",
|
||||
"sha256:41746d520f2b50409dffdba29a15c42caa7babae15616bcf80800d8cfcae3d3e",
|
||||
"sha256:43cea38cbcadb900829858884f49745eb1f42f92609d368cabcc674b03e90efc",
|
||||
"sha256:5378f58172bd63d8c16dd5d008d7dcdd55bf803fcdbe7da2dcb65dbbf322f05b",
|
||||
"sha256:54404abb1cd3f89d01f1fb5350607815326790efb4789be60508f458cdd5ccbf",
|
||||
"sha256:5dac3aeaac5feb1016e94bde851eb2012d1733a222b8afa788202b836c97dad5",
|
||||
"sha256:5fdb2a61e477ce58d3f1fdf2470ee142d9f0dde4969032edaf0b8f1a9dafeaa2",
|
||||
"sha256:6613c7815ee0b20222178ad32ec144061cb07e6a746970c9160af1ebe3ad43b4",
|
||||
"sha256:6d2b5b58e7df46b2c010ec78d7fb9ab20abf1d306d0614d3432e7478993fbdb0",
|
||||
"sha256:8a5d7e57b9df2c0a9a202840b2881bb1f7a648eba12dd2d919ac07a33a36a97f",
|
||||
"sha256:8b4c2055ebd6e497e5ecc06efa5b8aa76f59d15233356eb10dad22a03b757805",
|
||||
"sha256:a15653480e5b92ee376f8458197a58cca89a6e95d12cccb4c2d933df5cecc63f",
|
||||
"sha256:a7d2547b601ecc9a53fd41561de49a43d2231728ad65c7713d6b616cd02ddbed",
|
||||
"sha256:a979d0404b135c63954dea79e6246c45dd45371a88631cdbb4877d844e6de3b6",
|
||||
"sha256:b1f8111635700de7ac350b639e7e452b06fc541a328cf6193cf8fc638804bab8",
|
||||
"sha256:c5a3597880a7a29a31ebd39b73b2c824316ae63a05c3c8a5ce2aea3fc68afe35",
|
||||
"sha256:c681e8fcc47a767bf868341d8f0d76923733cbdcabd6ec3a3560695c69f14a1e",
|
||||
"sha256:cf135a08f306ebbcfea6da8bf775217613917be23e5074c69215b91e180caab4",
|
||||
"sha256:e2b8557fe6d0a18db4d61c028c6af61bfed44ef90e419ed6fadbdc079eba141e"
|
||||
"sha256:0a643bae4283a37732ddfcecab3f62dd082996021b980f580903f4e8e01b3c5b",
|
||||
"sha256:0de3ddb414d30798cbf56e642d82cac30a80223ad6fe484d66c0ce01a84d6f2f",
|
||||
"sha256:19a2148a1d02791352e9fa637899a78e371a3516ac6da5c4edc718f60cbae648",
|
||||
"sha256:21b5a2b033380adbdd36b3116faaf9a4663e375325831dac1b519a44f9e439bb",
|
||||
"sha256:24c7f8d4aee71bfa6401faeba367dd654f696a77151a8a28bc2013f7ced4af98",
|
||||
"sha256:26fa92d3ac743a149a31b21d6f4337b0594b6302ea5575b37af9ca9611e8981a",
|
||||
"sha256:2860a97cbb25444ffc0088b457da0a79dc79f9c601238a3e0644312fcc14bf11",
|
||||
"sha256:2b1c6cd28a0dfda75c7b5957363333f01d370936e4c6276b7b8e696dd500582a",
|
||||
"sha256:2c2f7c670ea4e60318e4b7e474d56447cf0c7d83b3c2a5405a0dbb2600b9c48e",
|
||||
"sha256:3be7a7a0ca71a2640e81d9276f526bca63505850add10206d0da2e8a0a325dae",
|
||||
"sha256:4c62e94d5d49db116bef1bd5c2486723a292d79409fc9abd51adf9e05329101d",
|
||||
"sha256:5008374ebb990dad9ed48b0f5d0038124c73748f5384cc8c46904dace27082d9",
|
||||
"sha256:5447ea7af4005b0daf695a316a423b96374c9c73ffbd4533209c5ddc369e644b",
|
||||
"sha256:573fba5b05bf2c69271a32e52399c8de599e4a15ab7cec47d3b9c904125ab788",
|
||||
"sha256:5a780260afc88268a9d3ac3511d8f494fdcf637eece62fb9eb656a63d53eb7ca",
|
||||
"sha256:70865f96bb38fec46f7ebd66d4b5cfd0aa6b842073f298d621385ae3898d28b5",
|
||||
"sha256:731568be71fba1e13cae212c362f3d2ca8932e83cb1b85e3f1b4dd77d019254a",
|
||||
"sha256:b61080750d19a0122469ab59b087380721d6b72a4e7d962e4d7e63e0c4504814",
|
||||
"sha256:bf23a3b54d128b50f4f9d4675b3c1857a688cc6731a32f931837d72effb2698d",
|
||||
"sha256:c16d59c15d946111d2716856dd5479221c9e4f2f5c7bc2d617f39d870031e086",
|
||||
"sha256:c61c043aafb69329d0f961b19faa30b1dab709dd34c9388143fc55680059e55a",
|
||||
"sha256:c94ff2780a1fd89f190390130d6d36173ca59fcfb3fe0ff596f9a56518191ccb",
|
||||
"sha256:edda9bacc3843dfbeebaf7a701763e68e741b08fccb889c003b0a52f0ee95782",
|
||||
"sha256:f10fc41ee3c75a474d3bdf68d396f10782d013d7f67db99c0efbfd0acb99701b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.4"
|
||||
"version": "==1.1.5"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
@ -640,18 +642,18 @@
|
||||
},
|
||||
"pygithub": {
|
||||
"hashes": [
|
||||
"sha256:776befaddab9d8fddd525d52a6ca1ac228cf62b5b1e271836d766f4925e1452e",
|
||||
"sha256:8ad656bf79958e775ec59f7f5a3dbcbadac12147ae3dc42708b951064096af15"
|
||||
"sha256:053f1b8d553a344ebd3ca3972765d923ee7e8ecc3ea55bd203683f164348fa1a",
|
||||
"sha256:14c96d55e3c0e295598e52fbbbf2a7862a293723482ae9000cb9c816faab4fb4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.53"
|
||||
"version": "==1.54"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0",
|
||||
"sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"
|
||||
"sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716",
|
||||
"sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"
|
||||
],
|
||||
"version": "==2.7.2"
|
||||
"version": "==2.7.3"
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
@ -746,11 +748,11 @@
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
|
||||
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
|
||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.25.0"
|
||||
"version": "==2.24.0"
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"extras": [
|
||||
@ -779,11 +781,11 @@
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
"sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55",
|
||||
"sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232"
|
||||
"sha256:4bb21a6ee4707bf43b61230e80740e71bfe56e55d1f1f50924b087bb2975c851",
|
||||
"sha256:6dc52924dc0bc710a5d16794e6b3480b2c7c08b07729505feab2b2c16661ff6e"
|
||||
],
|
||||
"markers": "python_version >= '3.0'",
|
||||
"version": "==2.0.1"
|
||||
"version": "==2.1"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
@ -837,7 +839,7 @@
|
||||
},
|
||||
"spiffworkflow": {
|
||||
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
||||
"ref": "6b2ed24bb340ebd31049312bd321f66ebf7b6b26"
|
||||
"ref": "450c07b886ce6bd8425974f0349248daade90fa0"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
@ -892,10 +894,10 @@
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
|
||||
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
|
||||
"sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
|
||||
"sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
|
||||
],
|
||||
"version": "==1.26.2"
|
||||
"version": "==1.25.11"
|
||||
},
|
||||
"waitress": {
|
||||
"hashes": [
|
||||
@ -942,11 +944,11 @@
|
||||
},
|
||||
"xlrd": {
|
||||
"hashes": [
|
||||
"sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2",
|
||||
"sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde"
|
||||
"sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd",
|
||||
"sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.0"
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"xlsxwriter": {
|
||||
"hashes": [
|
||||
@ -1014,10 +1016,10 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
|
||||
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
|
||||
"sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
|
||||
"sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
|
||||
],
|
||||
"version": "==20.4"
|
||||
"version": "==20.8"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
@ -1036,10 +1038,10 @@
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
|
||||
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
|
||||
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
|
||||
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
|
||||
],
|
||||
"version": "==1.9.0"
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
@ -1050,18 +1052,11 @@
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe",
|
||||
"sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"
|
||||
"sha256:b12e09409c5bdedc28d308469e156127004a436b41e9b44f9bff6446cbab9152",
|
||||
"sha256:d69e1a80b34fe4d596c9142f35d9e523d98a2838976f1a68419a8f051b24cec6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.1.2"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"version": "==1.15.0"
|
||||
"version": "==6.2.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
|
@ -6,6 +6,14 @@ basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
JSON_SORT_KEYS = False # CRITICAL. Do not sort the data when returning values to the front end.
|
||||
|
||||
# The API_TOKEN is used to ensure that the
|
||||
# workflow synch can work without a lot of
|
||||
# back and forth.
|
||||
# you may want to change this to something simple for testing!!
|
||||
# NB, if you change this in the local endpoint,
|
||||
# it needs to be changed in the remote endpoint as well
|
||||
API_TOKEN = environ.get('API_TOKEN', default = 'af95596f327c9ecc007b60414fc84b61')
|
||||
|
||||
NAME = "CR Connect Workflow"
|
||||
FLASK_PORT = environ.get('PORT0') or environ.get('FLASK_PORT', default="5000")
|
||||
CORS_ALLOW_ORIGINS = re.split(r',\s*', environ.get('CORS_ALLOW_ORIGINS', default="localhost:4200, localhost:5002"))
|
||||
|
310
crc/api.yml
310
crc/api.yml
@ -100,6 +100,200 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Study"
|
||||
/workflow_sync/pullall:
|
||||
get:
|
||||
operationId: crc.api.workflow_sync.sync_all_changed_workflows
|
||||
summary: Sync all workflows that have changed on the remote side and provide a list of the results
|
||||
security:
|
||||
- ApiKeyAuth : []
|
||||
# in the endpoint
|
||||
parameters:
|
||||
- name: remote
|
||||
in: query
|
||||
required: true
|
||||
description: The remote endpoint
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- Workflow Sync API
|
||||
responses:
|
||||
'200':
|
||||
description: An array of workflow specs that were synced from remote.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example : ['top_level_workflow','3b495037-f7d4-4509-bf58-cee41c0c6b0e']
|
||||
|
||||
|
||||
|
||||
|
||||
/workflow_sync/diff:
|
||||
get:
|
||||
operationId: crc.api.workflow_sync.get_changed_workflows
|
||||
summary: Provides a list of workflow that differ from remote and if it is new or not
|
||||
security :
|
||||
- ApiKeyAuth : []
|
||||
# in the endpoint
|
||||
parameters:
|
||||
- name: remote
|
||||
in: query
|
||||
required: true
|
||||
description: The remote endpoint
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- Workflow Sync API
|
||||
responses:
|
||||
'200':
|
||||
description: An array of workflow specs, with last touched date and which one is most recent.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/WorkflowSpecDiffList"
|
||||
|
||||
/workflow_sync/{workflow_spec_id}/spec:
|
||||
parameters:
|
||||
- name: workflow_spec_id
|
||||
in: path
|
||||
required: false
|
||||
description: The unique id of an existing workflow specification to modify.
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
operationId: crc.api.workflow_sync.get_sync_workflow_specification
|
||||
summary: Returns a single workflow specification
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
tags:
|
||||
- Workflow Sync API
|
||||
responses:
|
||||
'200':
|
||||
description: Workflow specification.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkflowSpec"
|
||||
|
||||
|
||||
/workflow_sync/{workflow_spec_id}/files:
|
||||
get:
|
||||
operationId: crc.api.workflow_sync.get_workflow_spec_files
|
||||
summary: Provides a list of files for a workflow spec on this machine.
|
||||
security :
|
||||
- ApiKeyAuth : []
|
||||
parameters:
|
||||
- name: workflow_spec_id
|
||||
in: path
|
||||
required: true
|
||||
description: The workflow_spec id
|
||||
schema:
|
||||
type: string
|
||||
|
||||
tags:
|
||||
- Workflow Sync API
|
||||
responses:
|
||||
'200':
|
||||
description: An array of files for a workflow spec on the local system, with details.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/WorkflowSpecFilesList"
|
||||
|
||||
/workflow_sync/{workflow_spec_id}/files/sync:
|
||||
get:
|
||||
operationId: crc.api.workflow_sync.sync_changed_files
|
||||
summary: Syncs files from a workflow on a remote system and provides a list of files that were updated
|
||||
security :
|
||||
- ApiKeyAuth : []
|
||||
parameters:
|
||||
- name: workflow_spec_id
|
||||
in: path
|
||||
required: true
|
||||
description: The workflow_spec id
|
||||
schema:
|
||||
type: string
|
||||
- name: remote
|
||||
in: query
|
||||
required: true
|
||||
description: The remote endpoint
|
||||
schema:
|
||||
type: string
|
||||
|
||||
tags:
|
||||
- Workflow Sync API
|
||||
responses:
|
||||
'200':
|
||||
description: A list of files that were synced for the workflow.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type : string
|
||||
example : ["data_security_plan.dmn",'some_other_file.xml']
|
||||
|
||||
|
||||
/workflow_sync/{workflow_spec_id}/files/diff:
|
||||
get:
|
||||
operationId: crc.api.workflow_sync.get_changed_files
|
||||
summary: Provides a list of files for a workflow specs that differ from remote and their signature.
|
||||
security :
|
||||
- ApiKeyAuth : []
|
||||
|
||||
parameters:
|
||||
- name: workflow_spec_id
|
||||
in: path
|
||||
required: true
|
||||
description: The workflow_spec id
|
||||
schema:
|
||||
type: string
|
||||
- name: remote
|
||||
in: query
|
||||
required: true
|
||||
description: The remote endpoint
|
||||
schema:
|
||||
type: string
|
||||
|
||||
tags:
|
||||
- Workflow Sync API
|
||||
responses:
|
||||
'200':
|
||||
description: An array of files that are different from remote, with last touched date and file signature.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/WorkflowSpecFilesDiff"
|
||||
|
||||
|
||||
/workflow_sync/all:
|
||||
get:
|
||||
operationId: crc.api.workflow_sync.get_all_spec_state
|
||||
summary: Provides a list of workflow specs, last update date and thumbprint
|
||||
security:
|
||||
- ApiKeyAuth : []
|
||||
|
||||
tags:
|
||||
- Workflow Sync API
|
||||
responses:
|
||||
'200':
|
||||
description: An array of workflow specs, with last touched date and file signature.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/WorkflowSpecAll"
|
||||
|
||||
|
||||
/study/all:
|
||||
get:
|
||||
operationId: crc.api.study.all_studies
|
||||
@ -474,6 +668,30 @@ paths:
|
||||
responses:
|
||||
'204':
|
||||
description: The file has been removed.
|
||||
/file/{md5_hash}/hash_data:
|
||||
parameters:
|
||||
- name: md5_hash
|
||||
in: path
|
||||
required: true
|
||||
description: The md5 hash of the file requested
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
operationId: crc.api.file.get_file_data_by_hash
|
||||
summary: Returns only the file contents
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
tags:
|
||||
- Files
|
||||
responses:
|
||||
'200':
|
||||
description: Returns the actual file
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
example: '<?xml version="1.0" encoding="UTF-8"?><bpmn:definitions></bpmn:definitions>'
|
||||
/file/{file_id}/data:
|
||||
parameters:
|
||||
- name: file_id
|
||||
@ -1154,6 +1372,12 @@ components:
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
x-bearerInfoFunc: crc.api.user.verify_token_admin
|
||||
ApiKeyAuth :
|
||||
type : apiKey
|
||||
in : header
|
||||
name : X-CR-API-KEY
|
||||
x-apikeyInfoFunc: crc.api.workflow_sync.verify_token
|
||||
|
||||
schemas:
|
||||
User:
|
||||
properties:
|
||||
@ -1177,6 +1401,92 @@ components:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
WorkflowSpecDiffList:
|
||||
properties:
|
||||
workflow_spec_id:
|
||||
type: string
|
||||
example : top_level_workflow
|
||||
date_created :
|
||||
type: string
|
||||
example : 2020-12-09 16:55:12.951500+00:00
|
||||
location :
|
||||
type : string
|
||||
example : remote
|
||||
new :
|
||||
type : boolean
|
||||
example : false
|
||||
WorkflowSpecFilesList:
|
||||
properties:
|
||||
file_model_id:
|
||||
type : integer
|
||||
example : 171
|
||||
workflow_spec_id :
|
||||
type: string
|
||||
example : top_level_workflow
|
||||
filename :
|
||||
type: string
|
||||
example : data_security_plan.dmn
|
||||
date_created :
|
||||
type: string
|
||||
example : 2020-12-01 13:58:12.420333+00:00
|
||||
type:
|
||||
type : string
|
||||
example : dmn
|
||||
primary :
|
||||
type : boolean
|
||||
example : false
|
||||
content_type:
|
||||
type: string
|
||||
example : text/xml
|
||||
primary_process_id:
|
||||
type : string
|
||||
example : null
|
||||
md5_hash:
|
||||
type: string
|
||||
example: f12e2bbd-a20c-673b-ccb8-a8a1ea9c5b7b
|
||||
|
||||
|
||||
WorkflowSpecFilesDiff:
|
||||
properties:
|
||||
filename :
|
||||
type: string
|
||||
example : data_security_plan.dmn
|
||||
date_created :
|
||||
type: string
|
||||
example : 2020-12-01 13:58:12.420333+00:00
|
||||
type:
|
||||
type : string
|
||||
example : dmn
|
||||
primary :
|
||||
type : boolean
|
||||
example : false
|
||||
content_type:
|
||||
type: string
|
||||
example : text/xml
|
||||
primary_process_id:
|
||||
type : string
|
||||
example : null
|
||||
md5_hash:
|
||||
type: string
|
||||
example: f12e2bbd-a20c-673b-ccb8-a8a1ea9c5b7b
|
||||
location:
|
||||
type : string
|
||||
example : remote
|
||||
new:
|
||||
type: boolean
|
||||
example : false
|
||||
|
||||
WorkflowSpecAll:
|
||||
properties:
|
||||
workflow_spec_id :
|
||||
type: string
|
||||
example : acaf1258-43b4-437e-8846-f612afa66811
|
||||
date_created :
|
||||
type: string
|
||||
example : 2020-12-01 13:58:12.420333+00:00
|
||||
md5_hash:
|
||||
type: string
|
||||
example: c30fd597f21715018eab12f97f9d4956
|
||||
Study:
|
||||
properties:
|
||||
id:
|
||||
|
@ -4,6 +4,8 @@ from flask import g
|
||||
|
||||
from crc import ma, app
|
||||
|
||||
import sentry_sdk
|
||||
|
||||
|
||||
class ApiError(Exception):
|
||||
def __init__(self, code, message, status_code=400,
|
||||
@ -16,6 +18,13 @@ class ApiError(Exception):
|
||||
self.file_name = file_name or "" # OPTIONAL: The file that caused the error.
|
||||
self.tag = tag or "" # OPTIONAL: The XML Tag that caused the issue.
|
||||
self.task_data = task_data or "" # OPTIONAL: A snapshot of data connected to the task when error ocurred.
|
||||
if hasattr(g,'user'):
|
||||
user = g.user.uid
|
||||
else:
|
||||
user = 'Unknown'
|
||||
self.task_user = user
|
||||
# This is for sentry logging into Slack
|
||||
sentry_sdk.set_context("User", {'user': user})
|
||||
Exception.__init__(self, self.message)
|
||||
|
||||
@classmethod
|
||||
@ -59,7 +68,7 @@ class ApiError(Exception):
|
||||
class ApiErrorSchema(ma.Schema):
|
||||
class Meta:
|
||||
fields = ("code", "message", "workflow_name", "file_name", "task_name", "task_id",
|
||||
"task_data")
|
||||
"task_data", "task_user")
|
||||
|
||||
|
||||
@app.errorhandler(ApiError)
|
||||
|
@ -6,7 +6,7 @@ from flask import send_file
|
||||
|
||||
from crc import session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.file import FileSchema, FileModel, File, FileModelSchema
|
||||
from crc.models.file import FileSchema, FileModel, File, FileModelSchema, FileDataModel
|
||||
from crc.models.workflow import WorkflowSpecModel
|
||||
from crc.services.file_service import FileService
|
||||
|
||||
@ -99,6 +99,9 @@ def update_file_data(file_id):
|
||||
file_model = FileService.update_file(file_model, file.stream.read(), file.content_type)
|
||||
return FileSchema().dump(to_file_api(file_model))
|
||||
|
||||
def get_file_data_by_hash(md5_hash):
|
||||
filedatamodel = session.query(FileDataModel).filter(FileDataModel.md5_hash == md5_hash).first()
|
||||
return get_file_data(filedatamodel.file_model_id,version=filedatamodel.version)
|
||||
|
||||
def get_file_data(file_id, version=None):
|
||||
file_data = FileService.get_file_data(file_id, version)
|
||||
|
@ -1,13 +1,13 @@
|
||||
import uuid
|
||||
|
||||
from SpiffWorkflow.util.deep_merge import DeepMerge
|
||||
from flask import g
|
||||
from crc import session, app
|
||||
|
||||
from crc import session
|
||||
from crc.api.common import ApiError, ApiErrorSchema
|
||||
from crc.models.api_models import WorkflowApi, WorkflowApiSchema, NavigationItem, NavigationItemSchema
|
||||
from crc.models.api_models import WorkflowApiSchema
|
||||
from crc.models.file import FileModel, LookupDataSchema
|
||||
from crc.models.study import StudyModel, WorkflowMetadata
|
||||
from crc.models.task_event import TaskEventModel, TaskEventModelSchema, TaskEvent, TaskEventSchema
|
||||
from crc.models.task_event import TaskEventModel, TaskEvent, TaskEventSchema
|
||||
from crc.models.workflow import WorkflowModel, WorkflowSpecModelSchema, WorkflowSpecModel, WorkflowSpecCategoryModel, \
|
||||
WorkflowSpecCategoryModelSchema
|
||||
from crc.services.file_service import FileService
|
||||
|
313
crc/api/workflow_sync.py
Normal file
313
crc/api/workflow_sync.py
Normal file
@ -0,0 +1,313 @@
|
||||
import hashlib
|
||||
import json
|
||||
import pandas as pd
|
||||
import requests
|
||||
from crc import session, app
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.file import FileModel, FileDataModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecCategoryModel
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.workflow_sync import WorkflowSyncService
|
||||
from crc.api.workflow import get_workflow_specification
|
||||
|
||||
|
||||
def get_sync_workflow_specification(workflow_spec_id):
|
||||
return get_workflow_specification(workflow_spec_id)
|
||||
|
||||
def join_uuids(uuids):
|
||||
"""Joins a pandas Series of uuids and combines them in one hash"""
|
||||
combined_uuids = ''.join([str(uuid) for uuid in uuids.sort_values()]) # ensure that values are always
|
||||
# in the same order
|
||||
return hashlib.md5(combined_uuids.encode('utf8')).hexdigest() # make a hash of the hashes
|
||||
|
||||
def verify_token(token, required_scopes):
|
||||
if token == app.config['API_TOKEN']:
|
||||
return {'scope':['any']}
|
||||
else:
|
||||
raise ApiError("permission_denied", "API Token information is not correct")
|
||||
|
||||
|
||||
def get_changed_workflows(remote,as_df=False):
|
||||
"""
|
||||
gets a remote endpoint - gets the workflows and then
|
||||
determines what workflows are different from the remote endpoint
|
||||
"""
|
||||
|
||||
remote_workflows_list = WorkflowSyncService.get_all_remote_workflows(remote)
|
||||
remote_workflows = pd.DataFrame(remote_workflows_list)
|
||||
|
||||
# get the local thumbprints & make sure that 'workflow_spec_id' is a column, not an index
|
||||
local = get_all_spec_state_dataframe().reset_index()
|
||||
|
||||
# merge these on workflow spec id and hash - this will
|
||||
# make two different date columns date_x and date_y
|
||||
different = remote_workflows.merge(local,
|
||||
right_on=['workflow_spec_id','md5_hash'],
|
||||
left_on=['workflow_spec_id','md5_hash'],
|
||||
how = 'outer' ,
|
||||
indicator=True).loc[lambda x : x['_merge']!='both']
|
||||
if len(different)==0:
|
||||
return []
|
||||
# each line has a tag on it - if was in the left or the right,
|
||||
# label it so we know if that was on the remote or local machine
|
||||
different.loc[different['_merge']=='left_only','location'] = 'remote'
|
||||
different.loc[different['_merge']=='right_only','location'] = 'local'
|
||||
|
||||
# this takes the different date_created_x and date-created_y columns and
|
||||
# combines them back into one date_created column
|
||||
index = different['date_created_x'].isnull()
|
||||
different.loc[index,'date_created_x'] = different[index]['date_created_y']
|
||||
different = different[['workflow_spec_id','date_created_x','location']].copy()
|
||||
different.columns=['workflow_spec_id','date_created','location']
|
||||
|
||||
# our different list will have multiple entries for a workflow if there is a version on either side
|
||||
# we want to grab the most recent one, so we sort and grab the most recent one for each workflow
|
||||
changedfiles = different.sort_values('date_created',ascending=False).groupby('workflow_spec_id').first()
|
||||
|
||||
# get an exclusive or list of workflow ids - that is we want lists of files that are
|
||||
# on one machine or the other, but not both
|
||||
remote_spec_ids = remote_workflows[['workflow_spec_id']]
|
||||
local_spec_ids = local[['workflow_spec_id']]
|
||||
left = remote_spec_ids[~remote_spec_ids['workflow_spec_id'].isin(local_spec_ids['workflow_spec_id'])]
|
||||
right = local_spec_ids[~local_spec_ids['workflow_spec_id'].isin(remote_spec_ids['workflow_spec_id'])]
|
||||
|
||||
# flag files as new that are only on the remote box and remove the files that are only on the local box
|
||||
changedfiles['new'] = False
|
||||
changedfiles.loc[changedfiles.index.isin(left['workflow_spec_id']), 'new'] = True
|
||||
output = changedfiles[~changedfiles.index.isin(right['workflow_spec_id'])]
|
||||
|
||||
# return the list as a dict, let swagger convert it to json
|
||||
if as_df:
|
||||
return output
|
||||
else:
|
||||
return output.reset_index().to_dict(orient='records')
|
||||
|
||||
|
||||
def sync_all_changed_workflows(remote):
|
||||
|
||||
workflowsdf = get_changed_workflows(remote,as_df=True)
|
||||
if len(workflowsdf) ==0:
|
||||
return []
|
||||
workflows = workflowsdf.reset_index().to_dict(orient='records')
|
||||
for workflow in workflows:
|
||||
sync_changed_files(remote,workflow['workflow_spec_id'])
|
||||
return [x['workflow_spec_id'] for x in workflows]
|
||||
|
||||
|
||||
def sync_changed_files(remote,workflow_spec_id):
|
||||
# make sure that spec is local before syncing files
|
||||
|
||||
specdict = WorkflowSyncService.get_remote_workflow_spec(remote,workflow_spec_id)
|
||||
|
||||
localspec = session.query(WorkflowSpecModel).filter(WorkflowSpecModel.id == workflow_spec_id).first()
|
||||
if localspec is None:
|
||||
localspec = WorkflowSpecModel()
|
||||
localspec.id = workflow_spec_id
|
||||
if specdict['category'] == None:
|
||||
localspec.category = None
|
||||
else:
|
||||
localcategory = session.query(WorkflowSpecCategoryModel).filter(WorkflowSpecCategoryModel.name
|
||||
== specdict['category']['name']).first()
|
||||
if localcategory == None:
|
||||
#category doesn't exist - lets make it
|
||||
localcategory = WorkflowSpecCategoryModel()
|
||||
localcategory.name = specdict['category']['name']
|
||||
localcategory.display_name = specdict['category']['display_name']
|
||||
localcategory.display_order = specdict['category']['display_order']
|
||||
session.add(localcategory)
|
||||
localspec.category = localcategory
|
||||
|
||||
localspec.display_order = specdict['display_order']
|
||||
localspec.display_name = specdict['display_name']
|
||||
localspec.name = specdict['name']
|
||||
localspec.description = specdict['description']
|
||||
session.add(localspec)
|
||||
|
||||
changedfiles = get_changed_files(remote,workflow_spec_id,as_df=True)
|
||||
if len(changedfiles)==0:
|
||||
return []
|
||||
updatefiles = changedfiles[~((changedfiles['new']==True) & (changedfiles['location']=='local'))]
|
||||
updatefiles = updatefiles.reset_index().to_dict(orient='records')
|
||||
|
||||
deletefiles = changedfiles[((changedfiles['new']==True) & (changedfiles['location']=='local'))]
|
||||
deletefiles = deletefiles.reset_index().to_dict(orient='records')
|
||||
|
||||
for delfile in deletefiles:
|
||||
currentfile = session.query(FileModel).filter(FileModel.workflow_spec_id==workflow_spec_id,
|
||||
FileModel.name == delfile['filename']).first()
|
||||
|
||||
# it is more appropriate to archive the file than delete
|
||||
# due to the fact that we might have workflows that are using the
|
||||
# file data
|
||||
currentfile.archived = True
|
||||
session.add(currentfile)
|
||||
|
||||
for updatefile in updatefiles:
|
||||
currentfile = session.query(FileModel).filter(FileModel.workflow_spec_id==workflow_spec_id,
|
||||
FileModel.name == updatefile['filename']).first()
|
||||
if not currentfile:
|
||||
currentfile = FileModel()
|
||||
currentfile.name = updatefile['filename']
|
||||
currentfile.workflow_spec_id = workflow_spec_id
|
||||
|
||||
currentfile.date_created = updatefile['date_created']
|
||||
currentfile.type = updatefile['type']
|
||||
currentfile.primary = updatefile['primary']
|
||||
currentfile.content_type = updatefile['content_type']
|
||||
currentfile.primary_process_id = updatefile['primary_process_id']
|
||||
session.add(currentfile)
|
||||
content = WorkflowSyncService.get_remote_file_by_hash(remote,updatefile['md5_hash'])
|
||||
FileService.update_file(currentfile,content,updatefile['type'])
|
||||
session.commit()
|
||||
return [x['filename'] for x in updatefiles]
|
||||
|
||||
|
||||
def get_changed_files(remote,workflow_spec_id,as_df=False):
|
||||
"""
|
||||
gets a remote endpoint - gets the files for a workflow_spec on both
|
||||
local and remote and determines what files have been change and returns a list of those
|
||||
files
|
||||
"""
|
||||
remote_file_list = WorkflowSyncService.get_remote_workflow_spec_files(remote,workflow_spec_id)
|
||||
remote_files = pd.DataFrame(remote_file_list)
|
||||
# get the local thumbprints & make sure that 'workflow_spec_id' is a column, not an index
|
||||
local = get_workflow_spec_files_dataframe(workflow_spec_id).reset_index()
|
||||
local['md5_hash'] = local['md5_hash'].astype('str')
|
||||
remote_files['md5_hash'] = remote_files['md5_hash'].astype('str')
|
||||
|
||||
different = remote_files.merge(local,
|
||||
right_on=['filename','md5_hash'],
|
||||
left_on=['filename','md5_hash'],
|
||||
how = 'outer' ,
|
||||
indicator=True).loc[lambda x : x['_merge']!='both']
|
||||
if len(different) == 0:
|
||||
if as_df:
|
||||
return different
|
||||
else:
|
||||
return []
|
||||
# each line has a tag on it - if was in the left or the right,
|
||||
# label it so we know if that was on the remote or local machine
|
||||
different.loc[different['_merge']=='left_only','location'] = 'remote'
|
||||
different.loc[different['_merge']=='right_only','location'] = 'local'
|
||||
|
||||
# this takes the different date_created_x and date-created_y columns and
|
||||
# combines them back into one date_created column
|
||||
dualfields = ['date_created','type','primary','content_type','primary_process_id']
|
||||
for merge in dualfields:
|
||||
index = different[merge+'_x'].isnull()
|
||||
different.loc[index,merge+'_x'] = different[index][merge+'_y']
|
||||
|
||||
fieldlist = [fld+'_x' for fld in dualfields]
|
||||
different = different[ fieldlist + ['md5_hash','filename','location']].copy()
|
||||
|
||||
different.columns=dualfields+['md5_hash','filename','location']
|
||||
# our different list will have multiple entries for a workflow if there is a version on either side
|
||||
# we want to grab the most recent one, so we sort and grab the most recent one for each workflow
|
||||
changedfiles = different.sort_values('date_created',ascending=False).groupby('filename').first()
|
||||
|
||||
# get an exclusive or list of workflow ids - that is we want lists of files that are
|
||||
# on one machine or the other, but not both
|
||||
remote_spec_ids = remote_files[['filename']]
|
||||
local_spec_ids = local[['filename']]
|
||||
left = remote_spec_ids[~remote_spec_ids['filename'].isin(local_spec_ids['filename'])]
|
||||
right = local_spec_ids[~local_spec_ids['filename'].isin(remote_spec_ids['filename'])]
|
||||
changedfiles['new'] = False
|
||||
changedfiles.loc[changedfiles.index.isin(left['filename']), 'new'] = True
|
||||
changedfiles.loc[changedfiles.index.isin(right['filename']),'new'] = True
|
||||
changedfiles = changedfiles.replace({pd.np.nan: None})
|
||||
# return the list as a dict, let swagger convert it to json
|
||||
if as_df:
|
||||
return changedfiles
|
||||
else:
|
||||
return changedfiles.reset_index().to_dict(orient='records')
|
||||
|
||||
|
||||
|
||||
def get_all_spec_state():
|
||||
"""
|
||||
Return a list of all workflow specs along with last updated date and a
|
||||
thumbprint of all of the files that are used for that workflow_spec
|
||||
Convert into a dict list from a dataframe
|
||||
"""
|
||||
df = get_all_spec_state_dataframe()
|
||||
return df.reset_index().to_dict(orient='records')
|
||||
|
||||
|
||||
def get_workflow_spec_files(workflow_spec_id):
|
||||
"""
|
||||
Return a list of all workflow specs along with last updated date and a
|
||||
thumbprint of all of the files that are used for that workflow_spec
|
||||
Convert into a dict list from a dataframe
|
||||
"""
|
||||
df = get_workflow_spec_files_dataframe(workflow_spec_id)
|
||||
return df.reset_index().to_dict(orient='records')
|
||||
|
||||
|
||||
def get_workflow_spec_files_dataframe(workflowid):
|
||||
"""
|
||||
Return a list of all files for a workflow_spec along with last updated date and a
|
||||
hash so we can determine file differences for a changed workflow on a box.
|
||||
Return a dataframe
|
||||
"""
|
||||
x = session.query(FileDataModel).join(FileModel).filter(FileModel.workflow_spec_id==workflowid)
|
||||
# there might be a cleaner way of getting a data frome from some of the
|
||||
# fields in the ORM - but this works OK
|
||||
filelist = []
|
||||
for file in x:
|
||||
filelist.append({'file_model_id':file.file_model_id,
|
||||
'workflow_spec_id': file.file_model.workflow_spec_id,
|
||||
'md5_hash':file.md5_hash,
|
||||
'filename':file.file_model.name,
|
||||
'type':file.file_model.type.name,
|
||||
'primary':file.file_model.primary,
|
||||
'content_type':file.file_model.content_type,
|
||||
'primary_process_id':file.file_model.primary_process_id,
|
||||
'date_created':file.date_created})
|
||||
if len(filelist) == 0:
|
||||
return pd.DataFrame(columns=['file_model_id',
|
||||
'workflow_spec_id',
|
||||
'md5_hash',
|
||||
'filename',
|
||||
'type',
|
||||
'primary',
|
||||
'content_type',
|
||||
'primary_process_id',
|
||||
'date_created'])
|
||||
df = pd.DataFrame(filelist).sort_values('date_created').groupby('file_model_id').last()
|
||||
df['date_created'] = df['date_created'].astype('str')
|
||||
return df
|
||||
|
||||
|
||||
|
||||
def get_all_spec_state_dataframe():
|
||||
"""
|
||||
Return a list of all workflow specs along with last updated date and a
|
||||
thumbprint of all of the files that are used for that workflow_spec
|
||||
Return a dataframe
|
||||
"""
|
||||
x = session.query(FileDataModel).join(FileModel)
|
||||
# there might be a cleaner way of getting a data frome from some of the
|
||||
# fields in the ORM - but this works OK
|
||||
filelist = []
|
||||
for file in x:
|
||||
filelist.append({'file_model_id':file.file_model_id,
|
||||
'workflow_spec_id': file.file_model.workflow_spec_id,
|
||||
'md5_hash':file.md5_hash,
|
||||
'filename':file.file_model.name,
|
||||
'date_created':file.date_created})
|
||||
df = pd.DataFrame(filelist)
|
||||
|
||||
# get a distinct list of file_model_id's with the most recent file_data retained
|
||||
df = df.sort_values('date_created').drop_duplicates(['file_model_id'],keep='last').copy()
|
||||
|
||||
# take that list and then group by workflow_spec and retain the most recently touched file
|
||||
# and make a consolidated hash of the md5_checksums - this acts as a 'thumbprint' for each
|
||||
# workflow spec
|
||||
df = df.groupby('workflow_spec_id').agg({'date_created':'max',
|
||||
'md5_hash':join_uuids}).copy()
|
||||
# get only the columns we are really interested in returning
|
||||
df = df[['date_created','md5_hash']].copy()
|
||||
# convert dates to string
|
||||
df['date_created'] = df['date_created'].astype('str')
|
||||
return df
|
||||
|
@ -1,6 +1,7 @@
|
||||
import enum
|
||||
|
||||
import marshmallow
|
||||
from SpiffWorkflow.navigation import NavItem
|
||||
from marshmallow import INCLUDE
|
||||
from marshmallow_enum import EnumField
|
||||
|
||||
@ -15,22 +16,6 @@ class MultiInstanceType(enum.Enum):
|
||||
sequential = "sequential"
|
||||
|
||||
|
||||
class NavigationItem(object):
|
||||
def __init__(self, id, task_id, name, title, backtracks, level, indent, child_count, state, is_decision,
|
||||
task=None, lane=None):
|
||||
self.id = id
|
||||
self.task_id = task_id
|
||||
self.name = name,
|
||||
self.title = title
|
||||
self.backtracks = backtracks
|
||||
self.level = level
|
||||
self.indent = indent
|
||||
self.child_count = child_count
|
||||
self.state = state
|
||||
self.is_decision = is_decision
|
||||
self.task = task
|
||||
self.lane = lane
|
||||
|
||||
class Task(object):
|
||||
|
||||
##########################################################################
|
||||
@ -158,15 +143,28 @@ class TaskSchema(ma.Schema):
|
||||
|
||||
class NavigationItemSchema(ma.Schema):
|
||||
class Meta:
|
||||
fields = ["id", "task_id", "name", "title", "backtracks", "level", "indent", "child_count", "state",
|
||||
"is_decision", "task", "lane"]
|
||||
fields = ["spec_id", "name", "spec_type", "task_id", "description", "backtracks", "indent",
|
||||
"lane", "state", "children"]
|
||||
unknown = INCLUDE
|
||||
task = marshmallow.fields.Nested(TaskSchema, dump_only=True, required=False, allow_none=True)
|
||||
state = marshmallow.fields.String(required=False, allow_none=True)
|
||||
description = marshmallow.fields.String(required=False, allow_none=True)
|
||||
backtracks = marshmallow.fields.String(required=False, allow_none=True)
|
||||
lane = marshmallow.fields.String(required=False, allow_none=True)
|
||||
title = marshmallow.fields.String(required=False, allow_none=True)
|
||||
task_id = marshmallow.fields.String(required=False, allow_none=True)
|
||||
children = marshmallow.fields.List(marshmallow.fields.Nested(lambda: NavigationItemSchema()))
|
||||
|
||||
@marshmallow.post_load
|
||||
def make_nav(self, data, **kwargs):
|
||||
state = data.pop('state', None)
|
||||
task_id = data.pop('task_id', None)
|
||||
children = data.pop('children', [])
|
||||
spec_type = data.pop('spec_type', None)
|
||||
item = NavItem(**data)
|
||||
item.state = state
|
||||
item.task_id = task_id
|
||||
item.children = children
|
||||
item.spec_type = spec_type
|
||||
return item
|
||||
|
||||
class WorkflowApi(object):
|
||||
def __init__(self, id, status, next_task, navigation,
|
||||
|
@ -69,7 +69,7 @@ class WorkflowStatus(enum.Enum):
|
||||
|
||||
|
||||
class WorkflowSpecDependencyFile(db.Model):
|
||||
"""Connects a workflow to the version of the specification files it depends on to execute"""
|
||||
"""Connects to a workflow to test the version of the specification files it depends on to execute"""
|
||||
file_data_id = db.Column(db.Integer, db.ForeignKey(FileDataModel.id), primary_key=True)
|
||||
workflow_id = db.Column(db.Integer, db.ForeignKey("workflow.id"), primary_key=True)
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import re
|
||||
|
||||
import markdown
|
||||
from jinja2 import Template
|
||||
|
||||
@ -24,13 +26,18 @@ Email Subject ApprvlApprvr1 PIComputingID
|
||||
|
||||
def do_task_validate_only(self, task, *args, **kwargs):
|
||||
self.get_subject(task, args)
|
||||
self.get_users_info(task, args)
|
||||
self.get_email_recipients(task, args)
|
||||
self.get_content(task)
|
||||
|
||||
def do_task(self, task, *args, **kwargs):
|
||||
args = [arg for arg in args if type(arg) == str]
|
||||
subject = self.get_subject(task, args)
|
||||
recipients = self.get_users_info(task, args)
|
||||
args = [arg for arg in args if type(arg) == str or type(arg) == list]
|
||||
|
||||
subject = args[0]
|
||||
recipients = None
|
||||
try:
|
||||
recipients = self.get_email_recipients(task, args)
|
||||
except ApiError:
|
||||
raise
|
||||
content, content_html = self.get_content(task)
|
||||
if recipients:
|
||||
send_mail(
|
||||
@ -41,44 +48,81 @@ Email Subject ApprvlApprvr1 PIComputingID
|
||||
content_html=content_html
|
||||
)
|
||||
|
||||
def get_users_info(self, task, args):
|
||||
if len(args) < 1:
|
||||
raise ApiError(code="missing_argument",
|
||||
message="Email script requires at least one argument. The "
|
||||
"name of the variable in the task data that contains user"
|
||||
"id to process. Multiple arguments are accepted.")
|
||||
emails = []
|
||||
for arg in args:
|
||||
try:
|
||||
uid = task.workflow.script_engine.evaluate_expression(task, arg)
|
||||
except Exception as e:
|
||||
app.logger.error(f'Workflow engines could not parse {arg}', exc_info=True)
|
||||
continue
|
||||
user_info = LdapService.user_info(uid)
|
||||
email = user_info.email_address
|
||||
emails.append(user_info.email_address)
|
||||
if not isinstance(email, str):
|
||||
raise ApiError(code="invalid_argument",
|
||||
message="The Email script requires at least 1 UID argument. The "
|
||||
"name of the variable in the task data that contains subject and"
|
||||
" user ids to process. This must point to an array or a string, but "
|
||||
"it currently points to a %s " % emails.__class__.__name__)
|
||||
def check_valid_email(self, email):
|
||||
# regex from https://emailregex.com/
|
||||
regex = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
|
||||
if (re.search(regex, email)):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_email_recipients(self, task, args):
|
||||
emails = []
|
||||
|
||||
if len(args[1]) < 1:
|
||||
raise ApiError(code="missing_argument",
|
||||
message="Email script requires at least one email address as an argument. "
|
||||
"Multiple email addresses are accepted.")
|
||||
|
||||
if isinstance(args[1], str):
|
||||
if self.check_valid_email(args[1]):
|
||||
emails.append(args[1])
|
||||
else:
|
||||
raise ApiError(code="invalid_argument",
|
||||
message="The email address you provided could not be parsed. "
|
||||
"The value you provided is '%s" % args[1])
|
||||
|
||||
if isinstance(args[1], list):
|
||||
for address in args[1]:
|
||||
if self.check_valid_email(address):
|
||||
emails.append(address)
|
||||
else:
|
||||
raise ApiError(code="invalid_argument",
|
||||
message="The email address you provided could not be parsed. "
|
||||
"The value you provided is '%s" % address)
|
||||
|
||||
if len(emails) > 0:
|
||||
return emails
|
||||
else:
|
||||
raise ApiError(code="invalid_argument",
|
||||
message="Email script requires a valid email address.")
|
||||
|
||||
# def get_users_info(self, task, args):
|
||||
# if len(args) < 1:
|
||||
# raise ApiError(code="missing_argument",
|
||||
# message="Email script requires at least one argument. The "
|
||||
# "name of the variable in the task data that contains user"
|
||||
# "id to process. Multiple arguments are accepted.")
|
||||
# emails = []
|
||||
# for arg in args:
|
||||
# try:
|
||||
# uid = task.workflow.script_engine.evaluate_expression(task, arg)
|
||||
# except Exception as e:
|
||||
# app.logger.error(f'Workflow engines could not parse {arg}', exc_info=True)
|
||||
# continue
|
||||
# user_info = LdapService.user_info(uid)
|
||||
# email = user_info.email_address
|
||||
# emails.append(user_info.email_address)
|
||||
# if not isinstance(email, str):
|
||||
# raise ApiError(code="invalid_argument",
|
||||
# message="The Email script requires at least 1 UID argument. The "
|
||||
# "name of the variable in the task data that contains subject and"
|
||||
# " user ids to process. This must point to an array or a string, but "
|
||||
# "it currently points to a %s " % emails.__class__.__name__)
|
||||
#
|
||||
# return emails
|
||||
|
||||
def get_subject(self, task, args):
|
||||
if len(args) < 1:
|
||||
# subject = ''
|
||||
if len(args[0]) < 1:
|
||||
raise ApiError(code="missing_argument",
|
||||
message="Email script requires at least one subject argument. The "
|
||||
"name of the variable in the task data that contains subject"
|
||||
" to process. Multiple arguments are accepted.")
|
||||
message="No subject was provided for the email message.")
|
||||
|
||||
subject = args[0]
|
||||
if not isinstance(subject, str):
|
||||
if not subject or not isinstance(subject, str):
|
||||
raise ApiError(code="invalid_argument",
|
||||
message="The Email script requires 1 argument. The "
|
||||
"the name of the variable in the task data that contains user"
|
||||
"ids to process. This must point to an array or a string, but "
|
||||
"it currently points to a %s " % subject.__class__.__name__)
|
||||
message="The subject you provided could not be parsed. "
|
||||
"The value is \"%s\" " % subject)
|
||||
|
||||
return subject
|
||||
|
||||
|
15
crc/scripts/failing_script.py
Normal file
15
crc/scripts/failing_script.py
Normal file
@ -0,0 +1,15 @@
|
||||
from crc.scripts.script import Script
|
||||
from crc.services.failing_service import FailingService
|
||||
|
||||
|
||||
class FailingScript(Script):
|
||||
|
||||
def get_description(self):
|
||||
return """It fails"""
|
||||
|
||||
def do_task_validate_only(self, task, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def do_task(self, task, *args, **kwargs):
|
||||
|
||||
FailingService.fail_as_service()
|
11
crc/services/failing_service.py
Normal file
11
crc/services/failing_service.py
Normal file
@ -0,0 +1,11 @@
|
||||
from crc.api.common import ApiError
|
||||
|
||||
|
||||
class FailingService(object):
|
||||
|
||||
@staticmethod
|
||||
def fail_as_service():
|
||||
"""It fails"""
|
||||
|
||||
raise ApiError(code='bad_service',
|
||||
message='This is my failing service')
|
@ -6,7 +6,6 @@ from github import Github, GithubObject, UnknownObjectException
|
||||
from uuid import UUID
|
||||
from lxml import etree
|
||||
|
||||
import flask
|
||||
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
|
||||
from pandas import ExcelFile
|
||||
from sqlalchemy import desc
|
||||
@ -82,7 +81,7 @@ class FileService(object):
|
||||
you get '1.0' rather than '1'
|
||||
fixme: This is stupid stupid slow. Place it in the database and just check if it is up to date."""
|
||||
data_model = FileService.get_reference_file_data(reference_file_name)
|
||||
xls = ExcelFile(data_model.data)
|
||||
xls = ExcelFile(data_model.data, engine='openpyxl')
|
||||
df = xls.parse(xls.sheet_names[0])
|
||||
for c in int_columns:
|
||||
df[c] = df[c].fillna(0)
|
||||
|
@ -1,13 +1,12 @@
|
||||
import copy
|
||||
import json
|
||||
import string
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
import jinja2
|
||||
from SpiffWorkflow import Task as SpiffTask, WorkflowException
|
||||
from SpiffWorkflow import Task as SpiffTask, WorkflowException, NavItem
|
||||
from SpiffWorkflow.bpmn.specs.EndEvent import EndEvent
|
||||
from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask
|
||||
from SpiffWorkflow.bpmn.specs.MultiInstanceTask import MultiInstanceTask
|
||||
@ -15,17 +14,16 @@ from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask
|
||||
from SpiffWorkflow.bpmn.specs.StartEvent import StartEvent
|
||||
from SpiffWorkflow.bpmn.specs.UserTask import UserTask
|
||||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
||||
from SpiffWorkflow.specs import CancelTask, StartTask
|
||||
from SpiffWorkflow.specs import CancelTask, StartTask, MultiChoice
|
||||
from SpiffWorkflow.util.deep_merge import DeepMerge
|
||||
from flask import g
|
||||
from jinja2 import Template
|
||||
|
||||
from crc import db, app
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.api_models import Task, MultiInstanceType, NavigationItem, NavigationItemSchema, WorkflowApi
|
||||
from crc.models.api_models import Task, MultiInstanceType, WorkflowApi
|
||||
from crc.models.file import LookupDataModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.user import UserModel, UserModelSchema
|
||||
from crc.models.workflow import WorkflowModel, WorkflowStatus, WorkflowSpecModel
|
||||
from crc.services.file_service import FileService
|
||||
@ -321,33 +319,9 @@ class WorkflowService(object):
|
||||
"""Returns an API model representing the state of the current workflow, if requested, and
|
||||
possible, next_task is set to the current_task."""
|
||||
|
||||
nav_dict = processor.bpmn_workflow.get_nav_list()
|
||||
navigation = processor.bpmn_workflow.get_deep_nav_list()
|
||||
WorkflowService.update_navigation(navigation, processor)
|
||||
|
||||
# Some basic cleanup of the title for the for the navigation.
|
||||
navigation = []
|
||||
for nav_item in nav_dict:
|
||||
spiff_task = processor.bpmn_workflow.get_task(nav_item['task_id'])
|
||||
if 'description' in nav_item:
|
||||
nav_item['title'] = nav_item.pop('description')
|
||||
# fixme: duplicate code from the workflow_service. Should only do this in one place.
|
||||
if nav_item['title'] is not None and ' ' in nav_item['title']:
|
||||
nav_item['title'] = nav_item['title'].partition(' ')[2]
|
||||
else:
|
||||
nav_item['title'] = ""
|
||||
if spiff_task:
|
||||
nav_item['task'] = WorkflowService.spiff_task_to_api_task(spiff_task, add_docs_and_forms=False)
|
||||
nav_item['title'] = nav_item['task'].title # Prefer the task title.
|
||||
|
||||
user_uids = WorkflowService.get_users_assigned_to_task(processor, spiff_task)
|
||||
if not UserService.in_list(user_uids, allow_admin_impersonate=True):
|
||||
nav_item['state'] = WorkflowService.TASK_STATE_LOCKED
|
||||
|
||||
else:
|
||||
nav_item['task'] = None
|
||||
|
||||
|
||||
navigation.append(NavigationItem(**nav_item))
|
||||
NavigationItemSchema().dump(nav_item)
|
||||
|
||||
spec = db.session.query(WorkflowSpecModel).filter_by(id=processor.workflow_spec_id).first()
|
||||
workflow_api = WorkflowApi(
|
||||
@ -376,6 +350,29 @@ class WorkflowService(object):
|
||||
workflow_api.next_task.state = WorkflowService.TASK_STATE_LOCKED
|
||||
return workflow_api
|
||||
|
||||
@staticmethod
|
||||
def update_navigation(navigation: List[NavItem], processor: WorkflowProcessor):
|
||||
# Recursive function to walk down through children, and clean up descriptions, and statuses
|
||||
for nav_item in navigation:
|
||||
spiff_task = processor.bpmn_workflow.get_task(nav_item.task_id)
|
||||
if spiff_task:
|
||||
# Use existing logic to set the description, and alter the state based on permissions.
|
||||
api_task = WorkflowService.spiff_task_to_api_task(spiff_task, add_docs_and_forms=False)
|
||||
nav_item.description = api_task.title
|
||||
user_uids = WorkflowService.get_users_assigned_to_task(processor, spiff_task)
|
||||
if (isinstance(spiff_task.task_spec, UserTask) or isinstance(spiff_task.task_spec, ManualTask)) \
|
||||
and not UserService.in_list(user_uids, allow_admin_impersonate=True):
|
||||
nav_item.state = WorkflowService.TASK_STATE_LOCKED
|
||||
else:
|
||||
# Strip off the first word in the description, to meet guidlines for BPMN.
|
||||
if nav_item.description:
|
||||
if nav_item.description is not None and ' ' in nav_item.description:
|
||||
nav_item.description = nav_item.description.partition(' ')[2]
|
||||
|
||||
# Recurse here
|
||||
WorkflowService.update_navigation(nav_item.children, processor)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_previously_submitted_data(workflow_id, spiff_task):
|
||||
""" If the user has completed this task previously, find the form data for the last submission."""
|
||||
@ -403,6 +400,7 @@ class WorkflowService(object):
|
||||
return {}
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def spiff_task_to_api_task(spiff_task, add_docs_and_forms=False):
|
||||
task_type = spiff_task.task_spec.__class__.__name__
|
||||
|
52
crc/services/workflow_sync.py
Normal file
52
crc/services/workflow_sync.py
Normal file
@ -0,0 +1,52 @@
|
||||
import json
|
||||
from json import JSONDecodeError
|
||||
from typing import List, Optional
|
||||
|
||||
import requests
|
||||
|
||||
from crc import app
|
||||
from crc.api.common import ApiError
|
||||
|
||||
|
||||
class WorkflowSyncService(object):
|
||||
|
||||
@staticmethod
|
||||
def get_remote_file_by_hash(remote,md5_hash):
|
||||
url = remote+'/v1.0/file/'+md5_hash+'/hash_data'
|
||||
return WorkflowSyncService.__make_request(url,return_contents=True)
|
||||
|
||||
@staticmethod
|
||||
def get_remote_workflow_spec_files(remote,workflow_spec_id):
|
||||
url = remote+'/v1.0/workflow_sync/'+workflow_spec_id+'/files'
|
||||
return WorkflowSyncService.__make_request(url)
|
||||
|
||||
@staticmethod
|
||||
def get_remote_workflow_spec(remote, workflow_spec_id):
|
||||
"""
|
||||
this just gets the details of a workflow spec from the
|
||||
remote side.
|
||||
"""
|
||||
url = remote+'/v1.0/workflow-sync/'+workflow_spec_id+'/spec'
|
||||
return WorkflowSyncService.__make_request(url)
|
||||
|
||||
@staticmethod
|
||||
def get_all_remote_workflows(remote):
|
||||
url = remote + '/v1.0/workflow_sync/all'
|
||||
return WorkflowSyncService.__make_request(url)
|
||||
|
||||
@staticmethod
|
||||
def __make_request(url,return_contents=False):
|
||||
try:
|
||||
response = requests.get(url,headers={'X-CR-API-KEY':app.config['API_TOKEN']})
|
||||
except:
|
||||
raise ApiError("workflow_sync_error",response.text)
|
||||
if response.ok and response.text:
|
||||
if return_contents:
|
||||
return response.content
|
||||
else:
|
||||
return json.loads(response.text)
|
||||
else:
|
||||
raise ApiError("workflow_sync_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s when calling "
|
||||
"url '%s'." %
|
||||
(response.status_code, response.text, url))
|
@ -1,11 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_06pyjz2" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0">
|
||||
<bpmn:process id="Process_01143nb" name="PI's Pr" isExecutable="true">
|
||||
<bpmn:process id="UserTask_ShowInvalidUIDs" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0kcrx5l</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:scriptTask id="ScriptTask_LoadPersonnel" name="Load IRB Personnel">
|
||||
<bpmn:incoming>Flow_0kcrx5l</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_00zanzw</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1dcsioh</bpmn:outgoing>
|
||||
<bpmn:script>current_user = ldap()
|
||||
investigators = study_info('investigators')
|
||||
@ -15,11 +16,11 @@ is_cu_pi = False
|
||||
if pi != None:
|
||||
hasPI = True
|
||||
if pi.get('uid', None) != None:
|
||||
pi_has_uid = True
|
||||
pi_invalid_uid = False
|
||||
if pi['uid'] == current_user['uid']:
|
||||
is_cu_pi = True
|
||||
else:
|
||||
pi_has_uid = False
|
||||
pi_invalid_uid = True
|
||||
else:
|
||||
hasPI = False
|
||||
|
||||
@ -27,9 +28,11 @@ else:
|
||||
dc = investigators.get('DEPT_CH', None)
|
||||
if dc != None:
|
||||
if dc.get('uid', None) != None:
|
||||
dc_has_uid = True
|
||||
dc_invalid_uid = False
|
||||
else:
|
||||
dc_has_uid = False
|
||||
dc_invalid_uid = True
|
||||
else:
|
||||
dc_invalid_uid = False
|
||||
|
||||
# Primary Coordinators
|
||||
pcs = {}
|
||||
@ -39,13 +42,19 @@ for k in investigators.keys():
|
||||
if k in ['SC_I','SC_II','IRBC']:
|
||||
investigator = investigators.get(k)
|
||||
if investigator.get('uid', None) != None:
|
||||
cnt_pcs_uid = cnt_pcs_uid + 1
|
||||
if investigator['uid'] != current_user['uid']:
|
||||
pcs[k] = investigator
|
||||
cnt_pcs_uid = cnt_pcs_uid + 1
|
||||
else:
|
||||
is_cu_pc = True
|
||||
is_cu_pc_role = investigator['label']
|
||||
else:
|
||||
pcs[k] = investigator
|
||||
cnt_pcs = len(pcs.keys())
|
||||
if cnt_pcs != cnt_pcs_uid:
|
||||
pcs_invalid_uid = True
|
||||
else:
|
||||
pcs_invalid_uid = False
|
||||
if cnt_pcs > 0:
|
||||
del(k)
|
||||
del(investigator)
|
||||
@ -58,13 +67,19 @@ for k in investigators.keys():
|
||||
if k == 'AS_C':
|
||||
investigator = investigators.get(k)
|
||||
if investigator.get('uid', None) != None:
|
||||
cnt_acs_uid = cnt_acs_uid + 1
|
||||
if investigator['uid'] != current_user['uid']:
|
||||
acs[k] = investigator
|
||||
cnt_acs_uid = cnt_acs_uid + 1
|
||||
else:
|
||||
is_cu_ac = True
|
||||
is_cu_ac_role = investigator['label']
|
||||
else:
|
||||
acs[k] = investigator
|
||||
cnt_acs = len(acs.keys())
|
||||
if cnt_pcs != cnt_pcs_uid:
|
||||
acs_invalid_uid = True
|
||||
else:
|
||||
acs_invalid_uid = False
|
||||
if cnt_acs > 0:
|
||||
del(k)
|
||||
del(investigator)
|
||||
@ -77,12 +92,18 @@ for k in investigators.keys():
|
||||
if k[:2] == 'SI':
|
||||
investigator = investigators.get(k)
|
||||
if investigator.get('uid', None) != None:
|
||||
cnt_subs_uid = cnt_subs_uid + 1
|
||||
if investigator['uid'] != current_user['uid']:
|
||||
subs[k] = investigator
|
||||
cnt_subs_uid = cnt_subs_uid + 1
|
||||
else:
|
||||
is_cu_subs = True
|
||||
else:
|
||||
subs[k] = investigator
|
||||
cnt_subs = len(subs.keys())
|
||||
if cnt_subs != cnt_subs_uid:
|
||||
subs_invalid_uid = True
|
||||
else:
|
||||
subs_invalid_uid = False
|
||||
if cnt_subs > 0:
|
||||
del(k)
|
||||
del(investigator)
|
||||
@ -95,13 +116,19 @@ for k in investigators.keys():
|
||||
if k in ['SCI','DC']:
|
||||
investigator = investigators.get(k)
|
||||
if investigator.get('uid', None) != None:
|
||||
cnt_aps_uid = cnt_aps_uid + 1
|
||||
if investigator['uid'] != current_user['uid']:
|
||||
aps[k] = investigator
|
||||
cnt_aps_uid = cnt_aps_uid + 1
|
||||
else:
|
||||
is_cu_ap = True
|
||||
is_cu_ap_role = investigator['label']
|
||||
else:
|
||||
aps[k] = investigator
|
||||
cnt_aps = len(aps.keys())
|
||||
if cnt_aps != cnt_aps_uid:
|
||||
aps_invalid_uid = True
|
||||
else:
|
||||
aps_invalid_uid = False
|
||||
if cnt_aps > 0:
|
||||
del(k)
|
||||
del(investigator)
|
||||
@ -132,12 +159,12 @@ Since you are the person entering this information, you already have access and
|
||||
<camunda:property id="rows" value="5" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="pi.access" label="Should the Principal Investigator have full editing access in the system?" type="boolean" defaultValue="true">
|
||||
<camunda:formField id="pi.access" label="Should the Principal Investigator have full editing access in the system?" type="boolean" defaultValue="True">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="is_cu_pi" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="pi.emails" label="Should the Principal Investigator receive automated email notifications?" type="boolean" defaultValue="true">
|
||||
<camunda:formField id="pi.emails" label="Should the Principal Investigator receive automated email notifications?" type="boolean" defaultValue="True">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="is_cu_pi" />
|
||||
</camunda:properties>
|
||||
@ -160,23 +187,23 @@ Since you are the person entering this information, you already have access and
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_0kcrx5l" sourceRef="StartEvent_1" targetRef="ScriptTask_LoadPersonnel" />
|
||||
<bpmn:sequenceFlow id="Flow_1dcsioh" sourceRef="ScriptTask_LoadPersonnel" targetRef="Gateway_CheckForPI" />
|
||||
<bpmn:exclusiveGateway id="Gateway_CheckForPI" name="PI Cnt" default="Flow_147b9li">
|
||||
<bpmn:exclusiveGateway id="Gateway_CheckForPI" name="PI With Valid UID?" default="Flow_147b9li">
|
||||
<bpmn:incoming>Flow_1dcsioh</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_147b9li</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_00prawo</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:sequenceFlow id="Flow_147b9li" name="1 PI from PB" sourceRef="Gateway_CheckForPI" targetRef="ScriptTask_DeterminePI_E0_Department" />
|
||||
<bpmn:sequenceFlow id="Flow_00prawo" name="No PI from PB" sourceRef="Gateway_CheckForPI" targetRef="Activity_1qwzwyi">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">not(hasPI) or (hasPI and not(pi_has_uid))</bpmn:conditionExpression>
|
||||
<bpmn:sequenceFlow id="Flow_147b9li" name="Yes" sourceRef="Gateway_CheckForPI" targetRef="Gateway_CheckUIDs" />
|
||||
<bpmn:sequenceFlow id="Flow_00prawo" name="No" sourceRef="Gateway_CheckForPI" targetRef="Activity_1qwzwyi">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">not(hasPI) or (hasPI and pi_invalid_uid)</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:manualTask id="Activity_1qwzwyi" name="Show No PI">
|
||||
<bpmn:manualTask id="Activity_1qwzwyi" name="Show No PI or Invalid UID">
|
||||
<bpmn:documentation>No PI entered in PB</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_00prawo</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_16qr5jf</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:exclusiveGateway id="Gateway_0jykh6r" name="How many Primary Coordinators?" default="Flow_0xifvai">
|
||||
<bpmn:incoming>Flow_0kpe12r</bpmn:incoming>
|
||||
<bpmn:incoming>SequenceFlow_0cdtt11</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_1ayisx2</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0xifvai</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_1oqem42</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
@ -210,7 +237,8 @@ Otherwise, edit each Coordinator as necessary and select the Save button for eac
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">cnt_pcs == 0</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:scriptTask id="ScriptTask_DeterminePI_E0_Department" name="Determine PI E0 Department">
|
||||
<bpmn:incoming>Flow_147b9li</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_0tfprc8</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_0tsdclr</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1grahhv</bpmn:outgoing>
|
||||
<bpmn:script>LDAP_dept = pi.department
|
||||
length_LDAP_dept = len(LDAP_dept)
|
||||
@ -274,36 +302,12 @@ else:
|
||||
<bpmn:incoming>Flow_0w4d2bz</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1oo0ijr</bpmn:outgoing>
|
||||
</bpmn:businessRuleTask>
|
||||
<bpmn:userTask id="UserTask_109otvi" name="Update Chair Info" camunda:formKey="RO_Chair_Info">
|
||||
<bpmn:documentation>***Name & Degree:*** {{ RO_Chair_Name_Degree }}
|
||||
***School:*** {{ RO_School }}
|
||||
***Department:*** {{ RO_Department }}
|
||||
***Title:*** {{ RO_Chair_Title }}
|
||||
***Email:*** {{ RO_Chair_CID }}
|
||||
|
||||
|
||||
{% if RO_Chair_CID != dc.uid %}
|
||||
*Does not match the Department Chair specified in Protocol Builder, {{ dc.display_name }}*
|
||||
{% endif %}</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="RO_ChairAccess" label="Should the Department Chair have full editing access in the system?" type="boolean" defaultValue="false" />
|
||||
<camunda:formField id="RO_ChairEmails" label="Should the Department Chair receive automated email notifications?" type="boolean" defaultValue="false" />
|
||||
</camunda:formData>
|
||||
<camunda:properties>
|
||||
<camunda:property name="display_name" value=""Responsible Organization's Chair Info"" />
|
||||
</camunda:properties>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0vi6thu</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_0cdtt11</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0cdtt11" sourceRef="UserTask_109otvi" targetRef="Gateway_0jykh6r" />
|
||||
<bpmn:exclusiveGateway id="Gateway_PI_is_DeptChair" name="PI is Dept Chair?" default="Flow_0vi6thu">
|
||||
<bpmn:incoming>Flow_070j5fg</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0vi6thu</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_00yhlrq</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:sequenceFlow id="Flow_0vi6thu" name="No" sourceRef="Gateway_PI_is_DeptChair" targetRef="UserTask_109otvi" />
|
||||
<bpmn:sequenceFlow id="Flow_0vi6thu" name="No" sourceRef="Gateway_PI_is_DeptChair" targetRef="Activity_1sffono" />
|
||||
<bpmn:sequenceFlow id="Flow_00yhlrq" name="Yes" sourceRef="Gateway_PI_is_DeptChair" targetRef="Activity_ShowPI_is_DeptChair">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">RO_Chair_CID == pi.uid</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
@ -360,6 +364,7 @@ Otherwise, edit each Sub-Investigator as necessary and select the Save button fo
|
||||
<bpmn:outgoing>Flow_1kg5jot</bpmn:outgoing>
|
||||
<bpmn:script>pi.E0.schoolName = PI_E0_schoolName
|
||||
pi.E0.deptName = PI_E0_deptName
|
||||
pi.experience = user_data_get("pi_experience","")
|
||||
ro = {}
|
||||
ro['chair'] = {}</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
@ -605,17 +610,17 @@ ro.schoolAbbrv = RO_StudySchool.value</bpmn:script>
|
||||
<bpmn:outgoing>Flow_0vff9k5</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:sequenceFlow id="Flow_0vff9k5" sourceRef="Gateway_0zd7syo" targetRef="BusinessRuleTask_Determine_RO_Chair" />
|
||||
<bpmn:exclusiveGateway id="Gateway_13k761k" name="How many Additional Personnel? " default="Flow_0kp47dz">
|
||||
<bpmn:exclusiveGateway id="Gateway_13k761k" name="How many Additional Personnel? " default="Flow_0q56tn8">
|
||||
<bpmn:incoming>Flow_0ofpgml</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_0jxzqw1</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0q56tn8</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_0kp47dz</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:sequenceFlow id="Flow_0q56tn8" sourceRef="Gateway_13k761k" targetRef="Activity_1sra1vn">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">cnt_aps > 0</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_0q56tn8" sourceRef="Gateway_13k761k" targetRef="Activity_1sra1vn" />
|
||||
<bpmn:sequenceFlow id="Flow_10zn0h1" sourceRef="Activity_1sra1vn" targetRef="EndEvent_1qor16n" />
|
||||
<bpmn:sequenceFlow id="Flow_0kp47dz" sourceRef="Gateway_13k761k" targetRef="EndEvent_1qor16n" />
|
||||
<bpmn:sequenceFlow id="Flow_0kp47dz" sourceRef="Gateway_13k761k" targetRef="EndEvent_1qor16n">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">cnt_aps == 0</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:userTask id="Activity_1sra1vn" name="Update Additional Personnel Info" camunda:formKey="AP_AccessEmails">
|
||||
<bpmn:documentation>The following Additional Personnel were entered in Protocol Builder:
|
||||
{%+ for key, value in aps.items() %}{{value.display_name}} ({{key}}){% if loop.index is lt cnt_aps %}, {% endif %}{% endfor %}
|
||||
@ -646,400 +651,545 @@ Otherwise, edit each Additional Personnel as necessary and select the Save butto
|
||||
<bpmn:outgoing>Flow_10zn0h1</bpmn:outgoing>
|
||||
<bpmn:multiInstanceLoopCharacteristics camunda:collection="aps" camunda:elementVariable="ap" />
|
||||
</bpmn:userTask>
|
||||
<bpmn:exclusiveGateway id="Gateway_CheckUIDs" name="Invalid UIDs?" default="Flow_0tfprc8">
|
||||
<bpmn:incoming>Flow_147b9li</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0tfprc8</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_0nz62mu</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:sequenceFlow id="Flow_0tfprc8" name="No" sourceRef="Gateway_CheckUIDs" targetRef="ScriptTask_DeterminePI_E0_Department" />
|
||||
<bpmn:sequenceFlow id="Flow_0nz62mu" name="Yes" sourceRef="Gateway_CheckUIDs" targetRef="Activity_19z6vct">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">dc_invalid_uid or pcs_invalid_uid or acs_invalid_uid or subs_invalid_uid or aps_invalid_uid</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:userTask id="Activity_19z6vct" name="Show Invalid UIDs" camunda:formKey="ShowInvalidUIDs">
|
||||
<bpmn:documentation>Select No if all displayed invalid Computing IDs do not need system access and/or receive emails. If they do, correct in Protocol Builder first and then select Yes.
|
||||
|
||||
|
||||
{% if dc_invalid_uid %}
|
||||
Department Chair
|
||||
{{ dc.error }}
|
||||
{% endif %}
|
||||
{% if pcs_invalid_uid %}
|
||||
Primary Coordinators
|
||||
{% for k, pc in pcs.items() %}
|
||||
{% if pc.get('uid', None) == None: %}
|
||||
{{ pc.error }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if acs_invalid_uid %}
|
||||
Additional Coordinators
|
||||
{% for k, ac in acs.items() %}
|
||||
{% if ac.get('uid', None) == None: %}
|
||||
{{ ac.error }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if subs_invalid_uid %}
|
||||
Sub-Investigators
|
||||
{% for k, sub in subs.items() %}
|
||||
{% if sub.get('uid', None) == None: %}
|
||||
{{ sub.error }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if aps_invalid_uid %}
|
||||
Additional Personnnel
|
||||
{% for k, ap in aps.items() %}
|
||||
{% if ap.get('uid', None) == None: %}
|
||||
{{ ap.error }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="FixInvalidUIDs" label="Do you want to fix?" type="boolean">
|
||||
<camunda:properties>
|
||||
<camunda:property id="description" value="Select Yes if you have corrected in Protocol Builder, No if you would like to proceed without correcting." />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0nz62mu</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_16bkbuc</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:exclusiveGateway id="Gateway_FixInvalidUIDs" name="Fix Invalid UIDs?" default="Flow_00zanzw">
|
||||
<bpmn:incoming>Flow_16bkbuc</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_00zanzw</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_0tsdclr</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:sequenceFlow id="Flow_16bkbuc" sourceRef="Activity_19z6vct" targetRef="Gateway_FixInvalidUIDs" />
|
||||
<bpmn:sequenceFlow id="Flow_00zanzw" name="Yes" sourceRef="Gateway_FixInvalidUIDs" targetRef="ScriptTask_LoadPersonnel" />
|
||||
<bpmn:sequenceFlow id="Flow_0tsdclr" name="No" sourceRef="Gateway_FixInvalidUIDs" targetRef="ScriptTask_DeterminePI_E0_Department">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">not(FixInvalidUIDs)</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:userTask id="Activity_1sffono" name="Update Chair Info" camunda:formKey="RO_Chair_Info">
|
||||
<bpmn:documentation>***Name & Degree:*** {{ RO_Chair_Name_Degree }}
|
||||
***School:*** {{ RO_School }}
|
||||
***Department:*** {{ RO_Department }}
|
||||
***Title:*** {{ RO_Chair_Title }}
|
||||
***Email:*** {{ RO_Chair_CID }}
|
||||
|
||||
|
||||
{% if RO_Chair_CID != dc.uid %}
|
||||
*Does not match the Department Chair specified in Protocol Builder, {{ dc.display_name }}*
|
||||
{% endif %}</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="RO_ChairAccess" label="Should the Department Chair have full editing access in the system?" type="boolean" defaultValue="false" />
|
||||
<camunda:formField id="RO_ChairEmails" label="Should the Department Chair receive automated email notifications?" type="boolean" defaultValue="false" />
|
||||
</camunda:formData>
|
||||
<camunda:properties>
|
||||
<camunda:property name="display_name" value=""Responsible Organization's Chair Info"" />
|
||||
</camunda:properties>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0vi6thu</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1ayisx2</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_1ayisx2" sourceRef="Activity_1sffono" targetRef="Gateway_0jykh6r" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_01143nb">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="UserTask_ShowInvalidUIDs">
|
||||
<bpmndi:BPMNEdge id="Flow_1ayisx2_di" bpmnElement="Flow_1ayisx2">
|
||||
<di:waypoint x="2810" y="290" />
|
||||
<di:waypoint x="2875" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0tsdclr_di" bpmnElement="Flow_0tsdclr">
|
||||
<di:waypoint x="715" y="540" />
|
||||
<di:waypoint x="860" y="540" />
|
||||
<di:waypoint x="860" y="330" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="781" y="522" width="15" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_00zanzw_di" bpmnElement="Flow_00zanzw">
|
||||
<di:waypoint x="690" y="565" />
|
||||
<di:waypoint x="690" y="610" />
|
||||
<di:waypoint x="360" y="610" />
|
||||
<di:waypoint x="360" y="330" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="516" y="592" width="18" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_16bkbuc_di" bpmnElement="Flow_16bkbuc">
|
||||
<di:waypoint x="690" y="460" />
|
||||
<di:waypoint x="690" y="515" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0nz62mu_di" bpmnElement="Flow_0nz62mu">
|
||||
<di:waypoint x="690" y="315" />
|
||||
<di:waypoint x="690" y="380" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="696" y="345" width="18" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0tfprc8_di" bpmnElement="Flow_0tfprc8">
|
||||
<di:waypoint x="715" y="290" />
|
||||
<di:waypoint x="810" y="290" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="756" y="272" width="15" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0kp47dz_di" bpmnElement="Flow_0kp47dz">
|
||||
<di:waypoint x="3800" y="265" />
|
||||
<di:waypoint x="3800" y="200" />
|
||||
<di:waypoint x="4150" y="200" />
|
||||
<di:waypoint x="4150" y="272" />
|
||||
<di:waypoint x="3960" y="265" />
|
||||
<di:waypoint x="3960" y="200" />
|
||||
<di:waypoint x="4310" y="200" />
|
||||
<di:waypoint x="4310" y="272" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_10zn0h1_di" bpmnElement="Flow_10zn0h1">
|
||||
<di:waypoint x="4030" y="290" />
|
||||
<di:waypoint x="4132" y="290" />
|
||||
<di:waypoint x="4190" y="290" />
|
||||
<di:waypoint x="4292" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0q56tn8_di" bpmnElement="Flow_0q56tn8">
|
||||
<di:waypoint x="3825" y="290" />
|
||||
<di:waypoint x="3930" y="290" />
|
||||
<di:waypoint x="3985" y="290" />
|
||||
<di:waypoint x="4090" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0vff9k5_di" bpmnElement="Flow_0vff9k5">
|
||||
<di:waypoint x="2040" y="375" />
|
||||
<di:waypoint x="2040" y="330" />
|
||||
<di:waypoint x="2200" y="375" />
|
||||
<di:waypoint x="2200" y="330" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0iuzu7j_di" bpmnElement="Flow_0iuzu7j">
|
||||
<di:waypoint x="2015" y="830" />
|
||||
<di:waypoint x="1900" y="830" />
|
||||
<di:waypoint x="1900" y="760" />
|
||||
<di:waypoint x="2175" y="830" />
|
||||
<di:waypoint x="2060" y="830" />
|
||||
<di:waypoint x="2060" y="760" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1905" y="783" width="49" height="14" />
|
||||
<dc:Bounds x="2065" y="783" width="49" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0giqf35_di" bpmnElement="Flow_0giqf35">
|
||||
<di:waypoint x="2065" y="830" />
|
||||
<di:waypoint x="2180" y="830" />
|
||||
<di:waypoint x="2180" y="760" />
|
||||
<di:waypoint x="2225" y="830" />
|
||||
<di:waypoint x="2340" y="830" />
|
||||
<di:waypoint x="2340" y="760" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2189" y="783" width="22" height="14" />
|
||||
<dc:Bounds x="2349" y="783" width="22" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0mdjaid_di" bpmnElement="Flow_0mdjaid">
|
||||
<di:waypoint x="2040" y="900" />
|
||||
<di:waypoint x="2040" y="855" />
|
||||
<di:waypoint x="2200" y="900" />
|
||||
<di:waypoint x="2200" y="855" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1vyg8ir_di" bpmnElement="Flow_1vyg8ir">
|
||||
<di:waypoint x="2180" y="680" />
|
||||
<di:waypoint x="2180" y="620" />
|
||||
<di:waypoint x="2065" y="620" />
|
||||
<di:waypoint x="2340" y="680" />
|
||||
<di:waypoint x="2340" y="620" />
|
||||
<di:waypoint x="2225" y="620" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0zc01f9_di" bpmnElement="Flow_0zc01f9">
|
||||
<di:waypoint x="2040" y="680" />
|
||||
<di:waypoint x="2040" y="645" />
|
||||
<di:waypoint x="2200" y="680" />
|
||||
<di:waypoint x="2200" y="645" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1vv63qa_di" bpmnElement="Flow_1vv63qa">
|
||||
<di:waypoint x="2040" y="460" />
|
||||
<di:waypoint x="2040" y="425" />
|
||||
<di:waypoint x="2200" y="460" />
|
||||
<di:waypoint x="2200" y="425" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1azfvtx_di" bpmnElement="Flow_1azfvtx">
|
||||
<di:waypoint x="2040" y="805" />
|
||||
<di:waypoint x="2040" y="760" />
|
||||
<di:waypoint x="2200" y="805" />
|
||||
<di:waypoint x="2200" y="760" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2047" y="783" width="45" height="14" />
|
||||
<dc:Bounds x="2207" y="783" width="45" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0w4d2bz_di" bpmnElement="Flow_0w4d2bz">
|
||||
<di:waypoint x="1775" y="290" />
|
||||
<di:waypoint x="1990" y="290" />
|
||||
<di:waypoint x="1935" y="290" />
|
||||
<di:waypoint x="2150" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1mplloa_di" bpmnElement="Flow_1mplloa">
|
||||
<di:waypoint x="1460" y="290" />
|
||||
<di:waypoint x="1540" y="290" />
|
||||
<di:waypoint x="1620" y="290" />
|
||||
<di:waypoint x="1700" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1va8c15_di" bpmnElement="Flow_1va8c15">
|
||||
<di:waypoint x="1640" y="290" />
|
||||
<di:waypoint x="1725" y="290" />
|
||||
<di:waypoint x="1800" y="290" />
|
||||
<di:waypoint x="1885" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0m9peiz_di" bpmnElement="Flow_0m9peiz">
|
||||
<di:waypoint x="2040" y="595" />
|
||||
<di:waypoint x="2040" y="540" />
|
||||
<di:waypoint x="2200" y="595" />
|
||||
<di:waypoint x="2200" y="540" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0fw4rck_di" bpmnElement="Flow_0fw4rck">
|
||||
<di:waypoint x="2065" y="830" />
|
||||
<di:waypoint x="2270" y="830" />
|
||||
<di:waypoint x="2270" y="400" />
|
||||
<di:waypoint x="2065" y="400" />
|
||||
<di:waypoint x="2225" y="830" />
|
||||
<di:waypoint x="2430" y="830" />
|
||||
<di:waypoint x="2430" y="400" />
|
||||
<di:waypoint x="2225" y="400" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2278" y="603" width="15" height="14" />
|
||||
<dc:Bounds x="2438" y="603" width="15" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0whqr3p_di" bpmnElement="Flow_0whqr3p">
|
||||
<di:waypoint x="1900" y="680" />
|
||||
<di:waypoint x="1900" y="620" />
|
||||
<di:waypoint x="2015" y="620" />
|
||||
<di:waypoint x="2060" y="680" />
|
||||
<di:waypoint x="2060" y="620" />
|
||||
<di:waypoint x="2175" y="620" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1yz8k2a_di" bpmnElement="Flow_1yz8k2a">
|
||||
<di:waypoint x="2040" y="1050" />
|
||||
<di:waypoint x="2040" y="980" />
|
||||
<di:waypoint x="2200" y="1050" />
|
||||
<di:waypoint x="2200" y="980" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1fj9iz0_di" bpmnElement="Flow_1fj9iz0">
|
||||
<di:waypoint x="1800" y="1090" />
|
||||
<di:waypoint x="1990" y="1090" />
|
||||
<di:waypoint x="1960" y="1090" />
|
||||
<di:waypoint x="2150" y="1090" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0ycdxbl_di" bpmnElement="Flow_0ycdxbl">
|
||||
<di:waypoint x="1750" y="855" />
|
||||
<di:waypoint x="1750" y="1050" />
|
||||
<di:waypoint x="1910" y="855" />
|
||||
<di:waypoint x="1910" y="1050" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1722" y="933" width="15" height="14" />
|
||||
<dc:Bounds x="1882" y="933" width="15" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_13la8l3_di" bpmnElement="Flow_13la8l3">
|
||||
<di:waypoint x="1775" y="830" />
|
||||
<di:waypoint x="2015" y="830" />
|
||||
<di:waypoint x="1935" y="830" />
|
||||
<di:waypoint x="2175" y="830" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1811" y="813" width="18" height="14" />
|
||||
<dc:Bounds x="1971" y="813" width="18" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1yd7kbi_di" bpmnElement="Flow_1yd7kbi">
|
||||
<di:waypoint x="1750" y="315" />
|
||||
<di:waypoint x="1750" y="805" />
|
||||
<di:waypoint x="1910" y="315" />
|
||||
<di:waypoint x="1910" y="805" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1722" y="691" width="15" height="14" />
|
||||
<dc:Bounds x="1882" y="691" width="15" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0dt3pjw_di" bpmnElement="Flow_0dt3pjw">
|
||||
<di:waypoint x="3100" y="265" />
|
||||
<di:waypoint x="3100" y="180" />
|
||||
<di:waypoint x="3480" y="180" />
|
||||
<di:waypoint x="3480" y="265" />
|
||||
<di:waypoint x="3260" y="265" />
|
||||
<di:waypoint x="3260" y="180" />
|
||||
<di:waypoint x="3640" y="180" />
|
||||
<di:waypoint x="3640" y="265" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3277" y="162" width="27" height="14" />
|
||||
<dc:Bounds x="3437" y="162" width="27" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_12ss6u8_di" bpmnElement="Flow_12ss6u8">
|
||||
<di:waypoint x="3340" y="290" />
|
||||
<di:waypoint x="3455" y="290" />
|
||||
<di:waypoint x="3500" y="290" />
|
||||
<di:waypoint x="3615" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1gtl2o3_di" bpmnElement="Flow_1gtl2o3">
|
||||
<di:waypoint x="3125" y="290" />
|
||||
<di:waypoint x="3240" y="290" />
|
||||
<di:waypoint x="3285" y="290" />
|
||||
<di:waypoint x="3400" y="290" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3159" y="272" width="48" height="14" />
|
||||
<dc:Bounds x="3319" y="272" width="48" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_070j5fg_di" bpmnElement="Flow_070j5fg">
|
||||
<di:waypoint x="2270" y="290" />
|
||||
<di:waypoint x="2365" y="290" />
|
||||
<di:waypoint x="2430" y="290" />
|
||||
<di:waypoint x="2525" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1kg5jot_di" bpmnElement="Flow_1kg5jot">
|
||||
<di:waypoint x="1280" y="290" />
|
||||
<di:waypoint x="1360" y="290" />
|
||||
<di:waypoint x="1440" y="290" />
|
||||
<di:waypoint x="1520" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_16qr5jf_di" bpmnElement="Flow_16qr5jf">
|
||||
<di:waypoint x="580" y="410" />
|
||||
<di:waypoint x="642" y="410" />
|
||||
<di:waypoint x="740" y="150" />
|
||||
<di:waypoint x="822" y="150" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0jxzqw1_di" bpmnElement="Flow_0jxzqw1">
|
||||
<di:waypoint x="3480" y="315" />
|
||||
<di:waypoint x="3480" y="390" />
|
||||
<di:waypoint x="3800" y="390" />
|
||||
<di:waypoint x="3800" y="315" />
|
||||
<di:waypoint x="3640" y="315" />
|
||||
<di:waypoint x="3640" y="390" />
|
||||
<di:waypoint x="3960" y="390" />
|
||||
<di:waypoint x="3960" y="315" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3627" y="372" width="27" height="14" />
|
||||
<dc:Bounds x="3787" y="372" width="27" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0ofpgml_di" bpmnElement="Flow_0ofpgml">
|
||||
<di:waypoint x="3710" y="290" />
|
||||
<di:waypoint x="3775" y="290" />
|
||||
<di:waypoint x="3870" y="290" />
|
||||
<di:waypoint x="3935" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_05rqrlf_di" bpmnElement="Flow_05rqrlf">
|
||||
<di:waypoint x="3505" y="290" />
|
||||
<di:waypoint x="3610" y="290" />
|
||||
<di:waypoint x="3665" y="290" />
|
||||
<di:waypoint x="3770" y="290" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3534" y="272" width="48" height="14" />
|
||||
<dc:Bounds x="3694" y="272" width="48" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0kpe12r_di" bpmnElement="Flow_0kpe12r">
|
||||
<di:waypoint x="2440" y="120" />
|
||||
<di:waypoint x="2740" y="120" />
|
||||
<di:waypoint x="2740" y="260" />
|
||||
<di:waypoint x="2600" y="120" />
|
||||
<di:waypoint x="2900" y="120" />
|
||||
<di:waypoint x="2900" y="260" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_00yhlrq_di" bpmnElement="Flow_00yhlrq">
|
||||
<di:waypoint x="2390" y="265" />
|
||||
<di:waypoint x="2390" y="160" />
|
||||
<di:waypoint x="2550" y="265" />
|
||||
<di:waypoint x="2550" y="160" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2401" y="178" width="18" height="14" />
|
||||
<dc:Bounds x="2561" y="178" width="18" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0vi6thu_di" bpmnElement="Flow_0vi6thu">
|
||||
<di:waypoint x="2415" y="290" />
|
||||
<di:waypoint x="2540" y="290" />
|
||||
<di:waypoint x="2575" y="290" />
|
||||
<di:waypoint x="2710" y="290" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2471" y="272" width="15" height="14" />
|
||||
<dc:Bounds x="2620" y="272" width="15" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0cdtt11_di" bpmnElement="SequenceFlow_0cdtt11">
|
||||
<di:waypoint x="2640" y="290" />
|
||||
<di:waypoint x="2715" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1oo0ijr_di" bpmnElement="Flow_1oo0ijr">
|
||||
<di:waypoint x="2090" y="290" />
|
||||
<di:waypoint x="2170" y="290" />
|
||||
<di:waypoint x="2250" y="290" />
|
||||
<di:waypoint x="2330" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1eaikyp_di" bpmnElement="Flow_1eaikyp">
|
||||
<di:waypoint x="920" y="290" />
|
||||
<di:waypoint x="1010" y="290" />
|
||||
<di:waypoint x="1070" y="290" />
|
||||
<di:waypoint x="1160" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1wz38hl_di" bpmnElement="Flow_1wz38hl">
|
||||
<di:waypoint x="1110" y="290" />
|
||||
<di:waypoint x="1180" y="290" />
|
||||
<di:waypoint x="1260" y="290" />
|
||||
<di:waypoint x="1340" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1grahhv_di" bpmnElement="Flow_1grahhv">
|
||||
<di:waypoint x="750" y="290" />
|
||||
<di:waypoint x="820" y="290" />
|
||||
<di:waypoint x="910" y="290" />
|
||||
<di:waypoint x="970" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1oqem42_di" bpmnElement="Flow_1oqem42">
|
||||
<di:waypoint x="2740" y="315" />
|
||||
<di:waypoint x="2740" y="400" />
|
||||
<di:waypoint x="3100" y="400" />
|
||||
<di:waypoint x="3100" y="315" />
|
||||
<di:waypoint x="2900" y="315" />
|
||||
<di:waypoint x="2900" y="400" />
|
||||
<di:waypoint x="3260" y="400" />
|
||||
<di:waypoint x="3260" y="315" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2916" y="383" width="27" height="14" />
|
||||
<dc:Bounds x="3076" y="383" width="27" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1n0k4pd_di" bpmnElement="Flow_1n0k4pd">
|
||||
<di:waypoint x="2980" y="290" />
|
||||
<di:waypoint x="3075" y="290" />
|
||||
<di:waypoint x="3140" y="290" />
|
||||
<di:waypoint x="3235" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0xifvai_di" bpmnElement="Flow_0xifvai">
|
||||
<di:waypoint x="2765" y="290" />
|
||||
<di:waypoint x="2880" y="290" />
|
||||
<di:waypoint x="2925" y="290" />
|
||||
<di:waypoint x="3040" y="290" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2793" y="273" width="48" height="14" />
|
||||
<dc:Bounds x="2953" y="273" width="48" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_00prawo_di" bpmnElement="Flow_00prawo">
|
||||
<di:waypoint x="350" y="315" />
|
||||
<di:waypoint x="350" y="410" />
|
||||
<di:waypoint x="480" y="410" />
|
||||
<di:waypoint x="510" y="265" />
|
||||
<di:waypoint x="510" y="150" />
|
||||
<di:waypoint x="640" y="150" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="359" y="353" width="71" height="14" />
|
||||
<dc:Bounds x="483" y="204" width="15" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_147b9li_di" bpmnElement="Flow_147b9li">
|
||||
<di:waypoint x="375" y="290" />
|
||||
<di:waypoint x="650" y="290" />
|
||||
<di:waypoint x="535" y="290" />
|
||||
<di:waypoint x="665" y="290" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="402" y="273" width="63" height="14" />
|
||||
<dc:Bounds x="583" y="273" width="18" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1dcsioh_di" bpmnElement="Flow_1dcsioh">
|
||||
<di:waypoint x="250" y="290" />
|
||||
<di:waypoint x="325" y="290" />
|
||||
<di:waypoint x="410" y="290" />
|
||||
<di:waypoint x="485" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0kcrx5l_di" bpmnElement="Flow_0kcrx5l">
|
||||
<di:waypoint x="28" y="290" />
|
||||
<di:waypoint x="150" y="290" />
|
||||
<di:waypoint x="188" y="290" />
|
||||
<di:waypoint x="310" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="-8" y="272" width="36" height="36" />
|
||||
<dc:Bounds x="152" y="272" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ScriptTask_0h49cmf_di" bpmnElement="ScriptTask_LoadPersonnel">
|
||||
<dc:Bounds x="150" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="310" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_1qor16n_di" bpmnElement="EndEvent_1qor16n">
|
||||
<dc:Bounds x="4132" y="272" width="36" height="36" />
|
||||
<dc:Bounds x="4292" y="272" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="4140" y="318" width="20" height="14" />
|
||||
<dc:Bounds x="4300" y="318" width="20" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0d622qi_di" bpmnElement="Activity_EditPI">
|
||||
<dc:Bounds x="1360" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="1520" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_0qzf1r3_di" bpmnElement="Gateway_CheckForPI" isMarkerVisible="true">
|
||||
<dc:Bounds x="325" y="265" width="50" height="50" />
|
||||
<dc:Bounds x="485" y="265" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="334" y="241" width="31" height="14" />
|
||||
<dc:Bounds x="478" y="325" width="63" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0neg931_di" bpmnElement="Activity_1qwzwyi">
|
||||
<dc:Bounds x="480" y="370" width="100" height="80" />
|
||||
<dc:Bounds x="640" y="110" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_0jykh6r_di" bpmnElement="Gateway_0jykh6r" isMarkerVisible="true">
|
||||
<dc:Bounds x="2715" y="265" width="50" height="50" />
|
||||
<dc:Bounds x="2875" y="265" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2745" y="309" width="70" height="40" />
|
||||
<dc:Bounds x="2905" y="309" width="70" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1nz85vv_di" bpmnElement="TaskPMI_UpdateCoordinatorInfo">
|
||||
<dc:Bounds x="2880" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="3040" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1z05bvn_di" bpmnElement="ScriptTask_DeterminePI_E0_Department">
|
||||
<dc:Bounds x="650" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0uz6yhu_di" bpmnElement="BusinessRule_PI_Dept">
|
||||
<dc:Bounds x="1010" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1sn7wxh_di" bpmnElement="BusinessRule_PI_School">
|
||||
<dc:Bounds x="820" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="810" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_1a7hck9_di" bpmnElement="UserTask_SelectChair">
|
||||
<dc:Bounds x="1850" y="680" width="100" height="80" />
|
||||
<dc:Bounds x="2010" y="680" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1sk9596_di" bpmnElement="BusinessRuleTask_Determine_RO_Chair">
|
||||
<dc:Bounds x="1990" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_109otvi_di" bpmnElement="UserTask_109otvi">
|
||||
<dc:Bounds x="2540" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="2150" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1xio5hy_di" bpmnElement="Gateway_PI_is_DeptChair" isMarkerVisible="true">
|
||||
<dc:Bounds x="2365" y="265" width="50" height="50" />
|
||||
<dc:Bounds x="2525" y="265" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2348" y="322" width="84" height="14" />
|
||||
<dc:Bounds x="2508" y="322" width="84" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0i869dj_di" bpmnElement="Activity_ShowPI_is_DeptChair">
|
||||
<dc:Bounds x="2340" y="80" width="100" height="80" />
|
||||
<dc:Bounds x="2500" y="80" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1oxt6h1_di" bpmnElement="Gateway_1oxt6h1" isMarkerVisible="true">
|
||||
<dc:Bounds x="3455" y="265" width="50" height="50" />
|
||||
<dc:Bounds x="3615" y="265" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3500" y="315" width="79" height="27" />
|
||||
<dc:Bounds x="3660" y="315" width="79" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0oyqfs3_di" bpmnElement="Activity_0yd4wuz">
|
||||
<dc:Bounds x="3610" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="3770" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_0npjf2p_di" bpmnElement="Event_0npjf2p">
|
||||
<dc:Bounds x="642" y="392" width="36" height="36" />
|
||||
<dc:Bounds x="822" y="132" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_02led02_di" bpmnElement="ScriptTask_Update_PIData">
|
||||
<dc:Bounds x="1180" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="1340" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1mt9o4o_di" bpmnElement="ScriptTask_UpdateRO_Data">
|
||||
<dc:Bounds x="2170" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="2330" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_0zrcknh_di" bpmnElement="Gateway_0zrcknh" isMarkerVisible="true">
|
||||
<dc:Bounds x="3075" y="265" width="50" height="50" />
|
||||
<dc:Bounds x="3235" y="265" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3115" y="309" width="70" height="40" />
|
||||
<dc:Bounds x="3275" y="309" width="70" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1gqvpu9_di" bpmnElement="Activity_1yjg742">
|
||||
<dc:Bounds x="3240" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="3400" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_10ngpfu_di" bpmnElement="Gateway_10ngpfu" isMarkerVisible="true">
|
||||
<dc:Bounds x="1725" y="265" width="50" height="50" />
|
||||
<dc:Bounds x="1885" y="265" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1706" y="170" width="88" height="80" />
|
||||
<dc:Bounds x="1866" y="170" width="88" height="80" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_141zszd_di" bpmnElement="Gateway_141zszd" isMarkerVisible="true">
|
||||
<dc:Bounds x="1725" y="805" width="50" height="50" />
|
||||
<dc:Bounds x="1885" y="805" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1644" y="823" width="72" height="14" />
|
||||
<dc:Bounds x="1804" y="823" width="72" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0vjz7wg_di" bpmnElement="Activity_1h5mjwh">
|
||||
<dc:Bounds x="1700" y="1050" width="100" height="80" />
|
||||
<dc:Bounds x="1860" y="1050" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0evmxd3_di" bpmnElement="Activity_141w33n">
|
||||
<dc:Bounds x="1990" y="1050" width="100" height="80" />
|
||||
<dc:Bounds x="2150" y="1050" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1dfciuq_di" bpmnElement="Gateway_1dfciuq" isMarkerVisible="true">
|
||||
<dc:Bounds x="2015" y="805" width="50" height="50" />
|
||||
<dc:Bounds x="2175" y="805" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2055" y="846" width="70" height="27" />
|
||||
<dc:Bounds x="2215" y="846" width="70" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1gzewp9_di" bpmnElement="Gateway_1gzewp9" isMarkerVisible="true">
|
||||
<dc:Bounds x="2015" y="595" width="50" height="50" />
|
||||
<dc:Bounds x="2175" y="595" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0zjabzm_di" bpmnElement="BusinessRuleTask_Determine_RO">
|
||||
<dc:Bounds x="1990" y="460" width="100" height="80" />
|
||||
<dc:Bounds x="2150" y="460" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_11muu5l_di" bpmnElement="Activity_11muu5l">
|
||||
<dc:Bounds x="1990" y="680" width="100" height="80" />
|
||||
<dc:Bounds x="2150" y="680" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_18f2rpz_di" bpmnElement="ScriptTask_SetRO_SchoolAndDept">
|
||||
<dc:Bounds x="1700" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1bvro77_di" bpmnElement="Activity_1bvro77">
|
||||
<dc:Bounds x="2130" y="680" width="100" height="80" />
|
||||
<dc:Bounds x="2290" y="680" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1yen2d0_di" bpmnElement="Activity_1yen2d0">
|
||||
<dc:Bounds x="1990" y="900" width="100" height="80" />
|
||||
<dc:Bounds x="2150" y="900" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_0zd7syo_di" bpmnElement="Gateway_0zd7syo" isMarkerVisible="true">
|
||||
<dc:Bounds x="2015" y="375" width="50" height="50" />
|
||||
<dc:Bounds x="2175" y="375" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_13k761k_di" bpmnElement="Gateway_13k761k" isMarkerVisible="true">
|
||||
<dc:Bounds x="3775" y="265" width="50" height="50" />
|
||||
<dc:Bounds x="3935" y="265" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3822" y="309" width="56" height="40" />
|
||||
<dc:Bounds x="3982" y="309" width="56" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1m6r78y_di" bpmnElement="Activity_1sra1vn">
|
||||
<dc:Bounds x="3930" y="250" width="100" height="80" />
|
||||
<dc:Bounds x="4090" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_18f2rpz_di" bpmnElement="ScriptTask_SetRO_SchoolAndDept">
|
||||
<dc:Bounds x="1540" y="250" width="100" height="80" />
|
||||
<bpmndi:BPMNShape id="Gateway_1gq2m4q_di" bpmnElement="Gateway_CheckUIDs" isMarkerVisible="true">
|
||||
<dc:Bounds x="665" y="265" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="657" y="241" width="66" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1l5u9mj_di" bpmnElement="Activity_19z6vct">
|
||||
<dc:Bounds x="640" y="380" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_01e55pl_di" bpmnElement="Gateway_FixInvalidUIDs" isMarkerVisible="true">
|
||||
<dc:Bounds x="665" y="515" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="570" y="530" width="84" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1sffono_di" bpmnElement="Activity_1sffono">
|
||||
<dc:Bounds x="2710" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1sn7wxh_di" bpmnElement="BusinessRule_PI_School">
|
||||
<dc:Bounds x="970" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0uz6yhu_di" bpmnElement="BusinessRule_PI_Dept">
|
||||
<dc:Bounds x="1160" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
|
@ -1,75 +1,76 @@
|
||||
alabaster==0.7.12
|
||||
alembic==1.4.2
|
||||
amqp==2.5.2
|
||||
alembic==1.4.3
|
||||
aniso8601==8.0.0
|
||||
attrs==19.3.0
|
||||
babel==2.8.0
|
||||
bcrypt==3.1.7
|
||||
beautifulsoup4==4.9.1
|
||||
billiard==3.6.3.0
|
||||
attrs==20.3.0
|
||||
babel==2.9.0
|
||||
bcrypt==3.2.0
|
||||
beautifulsoup4==4.9.3
|
||||
blinker==1.4
|
||||
celery==4.4.2
|
||||
certifi==2020.4.5.1
|
||||
cffi==1.14.0
|
||||
certifi==2020.11.8
|
||||
cffi==1.14.4
|
||||
chardet==3.0.4
|
||||
click==7.1.2
|
||||
clickclick==1.2.2
|
||||
clickclick==20.10.2
|
||||
commonmark==0.9.1
|
||||
configparser==5.0.0
|
||||
connexion==2.7.0
|
||||
coverage==5.1
|
||||
coverage==5.3
|
||||
deprecated==1.2.10
|
||||
docutils==0.16
|
||||
docxtpl==0.9.2
|
||||
docxtpl==0.11.2
|
||||
et-xmlfile==1.0.1
|
||||
flask==1.1.2
|
||||
flask-admin==1.5.7
|
||||
flask-bcrypt==0.7.1
|
||||
flask-cors==3.0.8
|
||||
flask-marshmallow==0.12.0
|
||||
flask-cors==3.0.9
|
||||
flask-mail==0.9.1
|
||||
flask-marshmallow==0.14.0
|
||||
flask-migrate==2.5.3
|
||||
flask-restful==0.3.8
|
||||
flask-sqlalchemy==2.4.1
|
||||
flask-sso==0.4.0
|
||||
future==0.18.2
|
||||
httpretty==1.0.2
|
||||
idna==2.9
|
||||
flask-sqlalchemy==2.4.4
|
||||
gunicorn==20.0.4
|
||||
httpretty==1.0.3
|
||||
idna==2.10
|
||||
imagesize==1.2.0
|
||||
importlib-metadata==1.6.0
|
||||
inflection==0.4.0
|
||||
inflection==0.5.1
|
||||
itsdangerous==1.1.0
|
||||
jdcal==1.4.1
|
||||
jinja2==2.11.2
|
||||
jsonschema==3.2.0
|
||||
kombu==4.6.8
|
||||
ldap3==2.7
|
||||
lxml==4.5.1
|
||||
mako==1.1.2
|
||||
ldap3==2.8.1
|
||||
lxml==4.6.2
|
||||
mako==1.1.3
|
||||
markdown==3.3.3
|
||||
markupsafe==1.1.1
|
||||
marshmallow==3.6.0
|
||||
marshmallow==3.9.1
|
||||
marshmallow-enum==1.5.1
|
||||
marshmallow-sqlalchemy==0.23.0
|
||||
numpy==1.18.4
|
||||
openapi-spec-validator==0.2.8
|
||||
openpyxl==3.0.3
|
||||
marshmallow-sqlalchemy==0.24.1
|
||||
numpy==1.19.4
|
||||
openapi-spec-validator==0.2.9
|
||||
openpyxl==3.0.5
|
||||
packaging==20.4
|
||||
pandas==1.0.3
|
||||
psycopg2-binary==2.8.5
|
||||
pandas==1.1.4
|
||||
psycopg2-binary==2.8.6
|
||||
pyasn1==0.4.8
|
||||
pycparser==2.20
|
||||
pygments==2.6.1
|
||||
pygithub==1.53
|
||||
pygments==2.7.2
|
||||
pyjwt==1.7.1
|
||||
pyparsing==2.4.7
|
||||
pyrsistent==0.16.0
|
||||
pyrsistent==0.17.3
|
||||
python-box==5.2.0
|
||||
python-dateutil==2.8.1
|
||||
python-docx==0.8.10
|
||||
python-editor==1.0.4
|
||||
pytz==2020.1
|
||||
python-levenshtein==0.12.0
|
||||
pytz==2020.4
|
||||
pyyaml==5.3.1
|
||||
recommonmark==0.6.0
|
||||
requests==2.23.0
|
||||
six==1.14.0
|
||||
requests==2.25.0
|
||||
sentry-sdk==0.14.4
|
||||
six==1.15.0
|
||||
snowballstemmer==2.0.0
|
||||
soupsieve==2.0.1
|
||||
sphinx==3.0.3
|
||||
sphinx==3.3.1
|
||||
sphinxcontrib-applehelp==1.0.2
|
||||
sphinxcontrib-devhelp==1.0.2
|
||||
sphinxcontrib-htmlhelp==1.0.3
|
||||
@ -77,14 +78,14 @@ sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==1.0.3
|
||||
sphinxcontrib-serializinghtml==1.1.4
|
||||
spiffworkflow
|
||||
sqlalchemy==1.3.17
|
||||
swagger-ui-bundle==0.0.6
|
||||
urllib3==1.25.9
|
||||
vine==1.3.0
|
||||
waitress==1.4.3
|
||||
sqlalchemy==1.3.20
|
||||
swagger-ui-bundle==0.0.8
|
||||
urllib3==1.26.2
|
||||
waitress==1.4.4
|
||||
webob==1.8.6
|
||||
webtest==2.0.35
|
||||
werkzeug==1.0.1
|
||||
wrapt==1.12.1
|
||||
wtforms==2.3.3
|
||||
xlrd==1.2.0
|
||||
xlsxwriter==1.2.8
|
||||
zipp==3.1.0
|
||||
xlsxwriter==1.3.7
|
||||
|
@ -66,6 +66,7 @@ class ExampleDataLoader:
|
||||
display_order=6
|
||||
),
|
||||
]
|
||||
db.session.execute("select setval('workflow_spec_category_id_seq',7);")
|
||||
db.session.add_all(categories)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -204,6 +204,14 @@ class BaseTest(unittest.TestCase):
|
||||
data = myfile.read()
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def workflow_sync_response(file_name):
|
||||
filepath = os.path.join(app.root_path, '..', 'tests', 'data', 'workflow_sync_responses', file_name)
|
||||
with open(filepath, 'rb') as myfile:
|
||||
data = myfile.read()
|
||||
return data
|
||||
|
||||
|
||||
def assert_success(self, rv, msg=""):
|
||||
try:
|
||||
data = json.loads(rv.get_data(as_text=True))
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0y2dq4f" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0y2dq4f" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||
<bpmn:process id="Process_0tad5ma" name="Set Recipients" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_1synsig</bpmn:outgoing>
|
||||
@ -20,7 +20,7 @@ Email content to be delivered to {{ ApprvlApprvr1 }}
|
||||
---</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_08n2npe</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1xlrgne</bpmn:outgoing>
|
||||
<bpmn:script>email("Camunda Email Subject",'ApprvlApprvr1','PIComputingID')</bpmn:script>
|
||||
<bpmn:script>email("Camunda Email Subject",ApprvlApprvr1,PIComputingID)</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1synsig" sourceRef="StartEvent_1" targetRef="Activity_1l9vih3" />
|
||||
<bpmn:sequenceFlow id="Flow_1xlrgne" sourceRef="Activity_0s5v97n" targetRef="Event_0izrcj4" />
|
||||
|
72
tests/data/email_script/email_script.bpmn
Normal file
72
tests/data/email_script/email_script.bpmn
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_bd39673" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0">
|
||||
<bpmn:process id="Process_fe6205f" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0scd96e</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_0scd96e" sourceRef="StartEvent_1" targetRef="Activity_EmailForm" />
|
||||
<bpmn:userTask id="Activity_EmailForm" name="Email Form" camunda:formKey="email_form">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="email_address" label="Enter Email" type="string" defaultValue="dan@sartography.com">
|
||||
<camunda:validation>
|
||||
<camunda:constraint name="required" config="true" />
|
||||
</camunda:validation>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0scd96e</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0c60gne</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_0c60gne" sourceRef="Activity_EmailForm" targetRef="Activity_SendEmail" />
|
||||
<bpmn:endEvent id="Event_EndEvent">
|
||||
<bpmn:incoming>Flow_19fqvhc</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_19fqvhc" sourceRef="Activity_SendEmail" targetRef="Event_EndEvent" />
|
||||
<bpmn:scriptTask id="Activity_SendEmail" name="Send Email">
|
||||
<bpmn:documentation>Dear Person,
|
||||
|
||||
|
||||
Thank you for using this email example.
|
||||
I hope this makes sense.
|
||||
|
||||
|
||||
Yours faithfully,
|
||||
|
||||
|
||||
Dan</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0c60gne</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_19fqvhc</bpmn:outgoing>
|
||||
<bpmn:script>subject = 'My Email Subject'
|
||||
email(subject, email_address)</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_fe6205f">
|
||||
<bpmndi:BPMNEdge id="Flow_19fqvhc_di" bpmnElement="Flow_19fqvhc">
|
||||
<di:waypoint x="530" y="117" />
|
||||
<di:waypoint x="592" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0c60gne_di" bpmnElement="Flow_0c60gne">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="430" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0scd96e_di" bpmnElement="Flow_0scd96e">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0wqsfcj_di" bpmnElement="Activity_EmailForm">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1wh1xsj_di" bpmnElement="Event_EndEvent">
|
||||
<dc:Bounds x="592" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1ajacra_di" bpmnElement="Activity_SendEmail">
|
||||
<dc:Bounds x="430" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
52
tests/data/failing_workflow/failing_workflow.bpmn
Normal file
52
tests/data/failing_workflow/failing_workflow.bpmn
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_886a64d" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0">
|
||||
<bpmn:process id="Process_FailingWorkflow" name="Failing Workflow" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0cszvz2</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_0cszvz2" sourceRef="StartEvent_1" targetRef="Activity_CallFailingScript" />
|
||||
<bpmn:scriptTask id="Activity_CallFailingScript" name="Call Failing Script">
|
||||
<bpmn:incoming>Flow_0cszvz2</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1l02umo</bpmn:outgoing>
|
||||
<bpmn:script>failing_script()</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1l02umo" sourceRef="Activity_CallFailingScript" targetRef="Activity_PlaceHolder" />
|
||||
<bpmn:scriptTask id="Activity_PlaceHolder" name="Place Holder">
|
||||
<bpmn:incoming>Flow_1l02umo</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_08zq7mf</bpmn:outgoing>
|
||||
<bpmn:script>print('I am a placeholder.')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="Event_0k0yvmv">
|
||||
<bpmn:incoming>Flow_08zq7mf</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_08zq7mf" sourceRef="Activity_PlaceHolder" targetRef="Event_0k0yvmv" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_FailingWorkflow">
|
||||
<bpmndi:BPMNEdge id="Flow_0cszvz2_di" bpmnElement="Flow_0cszvz2">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1l02umo_di" bpmnElement="Flow_1l02umo">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="430" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_08zq7mf_di" bpmnElement="Flow_08zq7mf">
|
||||
<di:waypoint x="530" y="117" />
|
||||
<di:waypoint x="592" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_045fev7_di" bpmnElement="Activity_CallFailingScript">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0f3nusg_di" bpmnElement="Activity_PlaceHolder">
|
||||
<dc:Bounds x="430" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_0k0yvmv_di" bpmnElement="Event_0k0yvmv">
|
||||
<dc:Bounds x="592" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
200
tests/data/random_fact/random_fact2.bpmn
Normal file
200
tests/data/random_fact/random_fact2.bpmn
Normal file
@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1gjhqt9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
|
||||
<bpmn:process id="Process_SecondFact" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_0c7wlth</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:userTask id="Task_User_Select_Type" name="Set Type" camunda:formKey="Get A Random Fun Fact">
|
||||
<bpmn:documentation># h1 Heading 8-)
|
||||
## h2 Heading
|
||||
### h3 Heading
|
||||
#### h4 Heading
|
||||
##### h5 Heading
|
||||
###### h6 Heading
|
||||
|
||||
|
||||
## Horizontal Rules
|
||||
|
||||
___
|
||||
|
||||
---
|
||||
|
||||
***
|
||||
|
||||
|
||||
## Typographic replacements
|
||||
|
||||
"double quotes" and 'single quotes'
|
||||
|
||||
|
||||
## Emphasis
|
||||
|
||||
**This is bold text**
|
||||
|
||||
__This is bold text__
|
||||
|
||||
*This is italic text*
|
||||
|
||||
_This is italic text_
|
||||
|
||||
~~Strikethrough~~
|
||||
|
||||
|
||||
## Blockquotes
|
||||
|
||||
|
||||
> Blockquotes can also be nested...
|
||||
>> ...by using additional greater-than signs right next to each other...
|
||||
> > > ...or with spaces between arrows.
|
||||
|
||||
|
||||
## Lists
|
||||
|
||||
Unordered
|
||||
|
||||
+ Create a list by starting a line with `+`, `-`, or `*`
|
||||
+ Sub-lists are made by indenting 2 spaces:
|
||||
- Marker character change forces new list start:
|
||||
* Ac tristique libero volutpat at
|
||||
+ Facilisis in pretium nisl aliquet
|
||||
- Nulla volutpat aliquam velit
|
||||
+ Very easy!
|
||||
|
||||
Ordered
|
||||
|
||||
1. Lorem ipsum dolor sit amet
|
||||
2. Consectetur adipiscing elit
|
||||
3. Integer molestie lorem at massa
|
||||
|
||||
|
||||
1. You can use sequential numbers...
|
||||
1. ...or keep all the numbers as `1.`
|
||||
|
||||
Start numbering with offset:
|
||||
|
||||
57. foo
|
||||
1. bar
|
||||
|
||||
## Tables
|
||||
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| data | path to data files to supply the data that will be passed into templates. |
|
||||
| engine | engine to be used for processing templates. Handlebars is the default. |
|
||||
| ext | extension to be used for dest files. |
|
||||
|
||||
Right aligned columns
|
||||
|
||||
| Option | Description |
|
||||
| ------:| -----------:|
|
||||
| data | path to data files to supply the data that will be passed into templates. |
|
||||
| engine | engine to be used for processing templates. Handlebars is the default. |
|
||||
| ext | extension to be used for dest files. |
|
||||
|
||||
|
||||
## Links
|
||||
|
||||
[link text](http://dev.nodeca.com)
|
||||
|
||||
[link with title](http://nodeca.github.io/pica/demo/ "title text!")
|
||||
|
||||
Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
|
||||
|
||||
|
||||
## Images
|
||||
|
||||

|
||||
</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="type" label="Type" type="enum" defaultValue="cat">
|
||||
<camunda:validation>
|
||||
<camunda:constraint name="required" config="true" />
|
||||
</camunda:validation>
|
||||
<camunda:value id="norris" name="Chuck Norris" />
|
||||
<camunda:value id="cat" name="Cat Fact" />
|
||||
<camunda:value id="buzzword" name="Business Buzzword" />
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
<camunda:properties>
|
||||
<camunda:property name="type" value="string" />
|
||||
</camunda:properties>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_0c7wlth</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_0641sh6</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:scriptTask id="Task_Get_Fact_From_API" name="Display Fact">
|
||||
<bpmn:documentation />
|
||||
<bpmn:extensionElements>
|
||||
<camunda:inputOutput>
|
||||
<camunda:inputParameter name="Fact.type" />
|
||||
</camunda:inputOutput>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_0641sh6</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_0t29gjo</bpmn:outgoing>
|
||||
<bpmn:script>FactService = fact_service()</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="EndEvent_0u1cgrf">
|
||||
<bpmn:documentation># Great Job!
|
||||
You have completed the random fact generator.
|
||||
You chose to receive a random fact of the type: "{{type}}"
|
||||
|
||||
Your random fact is:
|
||||
{{details}}</bpmn:documentation>
|
||||
<bpmn:incoming>SequenceFlow_0t29gjo</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0c7wlth" sourceRef="StartEvent_1" targetRef="Task_User_Select_Type" />
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0641sh6" sourceRef="Task_User_Select_Type" targetRef="Task_Get_Fact_From_API" />
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0t29gjo" sourceRef="Task_Get_Fact_From_API" targetRef="EndEvent_0u1cgrf" />
|
||||
<bpmn:textAnnotation id="TextAnnotation_09fq7kh">
|
||||
<bpmn:text>User sets the Fact.type to cat, norris, or buzzword</bpmn:text>
|
||||
</bpmn:textAnnotation>
|
||||
<bpmn:association id="Association_1cfasjp" sourceRef="Task_User_Select_Type" targetRef="TextAnnotation_09fq7kh" />
|
||||
<bpmn:textAnnotation id="TextAnnotation_1234e5n">
|
||||
<bpmn:text>Makes an API call to get a fact of the required type.</bpmn:text>
|
||||
</bpmn:textAnnotation>
|
||||
<bpmn:association id="Association_1qirnyy" sourceRef="Task_Get_Fact_From_API" targetRef="TextAnnotation_1234e5n" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1ds61df">
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0t29gjo_di" bpmnElement="SequenceFlow_0t29gjo">
|
||||
<di:waypoint x="570" y="250" />
|
||||
<di:waypoint x="692" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0641sh6_di" bpmnElement="SequenceFlow_0641sh6">
|
||||
<di:waypoint x="370" y="250" />
|
||||
<di:waypoint x="470" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0c7wlth_di" bpmnElement="SequenceFlow_0c7wlth">
|
||||
<di:waypoint x="188" y="250" />
|
||||
<di:waypoint x="270" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="152" y="232" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_186s7tw_di" bpmnElement="Task_User_Select_Type">
|
||||
<dc:Bounds x="270" y="210" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_0u1cgrf_di" bpmnElement="EndEvent_0u1cgrf">
|
||||
<dc:Bounds x="692" y="232" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_09fq7kh_di" bpmnElement="TextAnnotation_09fq7kh">
|
||||
<dc:Bounds x="330" y="116" width="99.99202297383536" height="68.28334396936822" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_1234e5n_di" bpmnElement="TextAnnotation_1234e5n">
|
||||
<dc:Bounds x="570" y="120" width="100" height="68" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ScriptTask_10keafb_di" bpmnElement="Task_Get_Fact_From_API">
|
||||
<dc:Bounds x="470" y="210" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Association_1cfasjp_di" bpmnElement="Association_1cfasjp">
|
||||
<di:waypoint x="344" y="210" />
|
||||
<di:waypoint x="359" y="184" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Association_1qirnyy_di" bpmnElement="Association_1qirnyy">
|
||||
<di:waypoint x="561" y="210" />
|
||||
<di:waypoint x="584" y="188" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
104
tests/data/workflow_sync_responses/random_fact2.bpmn
Normal file
104
tests/data/workflow_sync_responses/random_fact2.bpmn
Normal file
@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1gjhqt9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
|
||||
<bpmn:process id="Process_SecondFact" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_0c7wlth</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:userTask id="Task_User_Select_Type" name="Set Type" camunda:formKey="Get A Random Fun Fact">
|
||||
<bpmn:documentation># h1 Heading 8-)
|
||||
NEW_FILE_ADDED
|
||||
|
||||
</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="type" label="Type" type="enum" defaultValue="cat">
|
||||
<camunda:validation>
|
||||
<camunda:constraint name="required" config="true" />
|
||||
</camunda:validation>
|
||||
<camunda:value id="norris" name="Chuck Norris" />
|
||||
<camunda:value id="cat" name="Cat Fact" />
|
||||
<camunda:value id="buzzword" name="Business Buzzword" />
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
<camunda:properties>
|
||||
<camunda:property name="type" value="string" />
|
||||
</camunda:properties>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_0c7wlth</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_0641sh6</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:scriptTask id="Task_Get_Fact_From_API" name="Display Fact">
|
||||
<bpmn:documentation />
|
||||
<bpmn:extensionElements>
|
||||
<camunda:inputOutput>
|
||||
<camunda:inputParameter name="Fact.type" />
|
||||
</camunda:inputOutput>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_0641sh6</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_0t29gjo</bpmn:outgoing>
|
||||
<bpmn:script>FactService = fact_service()</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="EndEvent_0u1cgrf">
|
||||
<bpmn:documentation># Great Job!
|
||||
You have completed the random fact generator.
|
||||
You chose to receive a random fact of the type: "{{type}}"
|
||||
|
||||
Your random fact is:
|
||||
{{details}}</bpmn:documentation>
|
||||
<bpmn:incoming>SequenceFlow_0t29gjo</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0c7wlth" sourceRef="StartEvent_1" targetRef="Task_User_Select_Type" />
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0641sh6" sourceRef="Task_User_Select_Type" targetRef="Task_Get_Fact_From_API" />
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0t29gjo" sourceRef="Task_Get_Fact_From_API" targetRef="EndEvent_0u1cgrf" />
|
||||
<bpmn:textAnnotation id="TextAnnotation_09fq7kh">
|
||||
<bpmn:text>User sets the Fact.type to cat, norris, or buzzword</bpmn:text>
|
||||
</bpmn:textAnnotation>
|
||||
<bpmn:association id="Association_1cfasjp" sourceRef="Task_User_Select_Type" targetRef="TextAnnotation_09fq7kh" />
|
||||
<bpmn:textAnnotation id="TextAnnotation_1234e5n">
|
||||
<bpmn:text>Makes an API call to get a fact of the required type.</bpmn:text>
|
||||
</bpmn:textAnnotation>
|
||||
<bpmn:association id="Association_1qirnyy" sourceRef="Task_Get_Fact_From_API" targetRef="TextAnnotation_1234e5n" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1ds61df">
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0t29gjo_di" bpmnElement="SequenceFlow_0t29gjo">
|
||||
<di:waypoint x="570" y="250" />
|
||||
<di:waypoint x="692" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0641sh6_di" bpmnElement="SequenceFlow_0641sh6">
|
||||
<di:waypoint x="370" y="250" />
|
||||
<di:waypoint x="470" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0c7wlth_di" bpmnElement="SequenceFlow_0c7wlth">
|
||||
<di:waypoint x="188" y="250" />
|
||||
<di:waypoint x="270" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="152" y="232" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_186s7tw_di" bpmnElement="Task_User_Select_Type">
|
||||
<dc:Bounds x="270" y="210" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_0u1cgrf_di" bpmnElement="EndEvent_0u1cgrf">
|
||||
<dc:Bounds x="692" y="232" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_09fq7kh_di" bpmnElement="TextAnnotation_09fq7kh">
|
||||
<dc:Bounds x="330" y="116" width="99.99202297383536" height="68.28334396936822" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_1234e5n_di" bpmnElement="TextAnnotation_1234e5n">
|
||||
<dc:Bounds x="570" y="120" width="100" height="68" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ScriptTask_10keafb_di" bpmnElement="Task_Get_Fact_From_API">
|
||||
<dc:Bounds x="470" y="210" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Association_1cfasjp_di" bpmnElement="Association_1cfasjp">
|
||||
<di:waypoint x="344" y="210" />
|
||||
<di:waypoint x="359" y="184" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Association_1qirnyy_di" bpmnElement="Association_1qirnyy">
|
||||
<di:waypoint x="561" y="210" />
|
||||
<di:waypoint x="584" y="188" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -1,6 +1,6 @@
|
||||
from tests.base_test import BaseTest
|
||||
from crc import mail
|
||||
from crc.models.email import EmailModel
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestEmailScript(BaseTest):
|
||||
@ -9,8 +9,8 @@ class TestEmailScript(BaseTest):
|
||||
workflow = self.create_workflow('email')
|
||||
|
||||
task_data = {
|
||||
'PIComputingID': 'dhf8r',
|
||||
'ApprvlApprvr1': 'lb3dp'
|
||||
'PIComputingID': 'dhf8r@virginia.edu',
|
||||
'ApprvlApprvr1': 'lb3dp@virginia.edu'
|
||||
}
|
||||
task = self.get_workflow_api(workflow).next_task
|
||||
|
||||
|
@ -46,7 +46,7 @@ class TestFilesApi(BaseTest):
|
||||
content_type="application/json", headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
self.assertEqual(2, len(json_data))
|
||||
self.assertEqual(3, len(json_data))
|
||||
|
||||
|
||||
def test_create_file(self):
|
||||
|
@ -165,7 +165,7 @@ class TestStudyApi(BaseTest):
|
||||
self.assertEqual(study_event.comment, update_comment)
|
||||
self.assertEqual(study_event.user_uid, self.test_uid)
|
||||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_investigators
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
|
||||
|
68
tests/test_email_script.py
Normal file
68
tests/test_email_script.py
Normal file
@ -0,0 +1,68 @@
|
||||
from tests.base_test import BaseTest
|
||||
from crc import mail
|
||||
|
||||
|
||||
# class TestEmailDirectly(BaseTest):
|
||||
#
|
||||
# def test_email_directly(self):
|
||||
# recipients = ['michaelc@cullerton.com']
|
||||
# sender = 'michaelc@cullerton.com'
|
||||
# with mail.record_messages() as outbox:
|
||||
# mail.send_message(subject='testing',
|
||||
# body='test',
|
||||
# recipients=recipients,
|
||||
# sender=sender)
|
||||
# assert len(outbox) == 1
|
||||
# assert outbox[0].subject == "testing"
|
||||
|
||||
|
||||
class TestEmailScript(BaseTest):
|
||||
|
||||
def test_email_script(self):
|
||||
with mail.record_messages() as outbox:
|
||||
|
||||
workflow = self.create_workflow('email_script')
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.complete_form(workflow, first_task, {'email_address': 'test@example.com'})
|
||||
|
||||
self.assertEqual(1, len(outbox))
|
||||
self.assertEqual('My Email Subject', outbox[0].subject)
|
||||
self.assertEqual(['test@example.com'], outbox[0].recipients)
|
||||
|
||||
def test_email_script_multiple(self):
|
||||
with mail.record_messages() as outbox:
|
||||
|
||||
workflow = self.create_workflow('email_script')
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.complete_form(workflow, first_task, {'email_address': ['test@example.com', 'test2@example.com']})
|
||||
|
||||
self.assertEqual(1, len(outbox))
|
||||
self.assertEqual("My Email Subject", outbox[0].subject)
|
||||
self.assertEqual(2, len(outbox[0].recipients))
|
||||
self.assertEqual('test@example.com', outbox[0].recipients[0])
|
||||
self.assertEqual('test2@example.com', outbox[0].recipients[1])
|
||||
|
||||
def test_bad_email_address_1(self):
|
||||
workflow = self.create_workflow('email_script')
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
with self.assertRaises(AssertionError):
|
||||
self.complete_form(workflow, first_task, {'email_address': 'test@example'})
|
||||
|
||||
|
||||
def test_bad_email_address_2(self):
|
||||
workflow = self.create_workflow('email_script')
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
with self.assertRaises(AssertionError):
|
||||
self.complete_form(workflow, first_task, {'email_address': 'test'})
|
@ -68,7 +68,7 @@ class TestTasksApi(BaseTest):
|
||||
# get the first form in the two form workflow.
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual('two_forms', workflow_api.workflow_spec_id)
|
||||
self.assertEqual(2, len(workflow_api.navigation))
|
||||
self.assertEqual(5, len(workflow_api.navigation))
|
||||
self.assertIsNotNone(workflow_api.next_task.form)
|
||||
self.assertEqual("UserTask", workflow_api.next_task.type)
|
||||
self.assertEqual("StepOne", workflow_api.next_task.name)
|
||||
@ -113,14 +113,20 @@ class TestTasksApi(BaseTest):
|
||||
|
||||
self.assertIsNotNone(workflow_api.navigation)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual(5, len(nav))
|
||||
self.assertEqual("Do You Have Bananas", nav[0]['title'])
|
||||
self.assertEqual("Bananas?", nav[1]['title'])
|
||||
self.assertEqual("FUTURE", nav[1]['state'])
|
||||
self.assertEqual("yes", nav[2]['title'])
|
||||
self.assertEqual("NOOP", nav[2]['state'])
|
||||
self.assertEqual("no", nav[3]['title'])
|
||||
self.assertEqual("NOOP", nav[3]['state'])
|
||||
self.assertEqual(4, len(nav))
|
||||
self.assertEqual("Do You Have Bananas", nav[1].description)
|
||||
self.assertEqual("Bananas?", nav[2].description)
|
||||
self.assertEqual("LIKELY", nav[2].state)
|
||||
|
||||
self.assertEqual("yes", nav[2].children[0].description)
|
||||
self.assertEqual("LIKELY", nav[2].children[0].state)
|
||||
self.assertEqual("of Bananas", nav[2].children[0].children[0].description)
|
||||
self.assertEqual("EndEvent", nav[2].children[0].children[1].spec_type)
|
||||
|
||||
self.assertEqual("no", nav[2].children[1].description)
|
||||
self.assertEqual("MAYBE", nav[2].children[1].state)
|
||||
self.assertEqual("no bananas", nav[2].children[1].children[0].description)
|
||||
self.assertEqual("EndEvent", nav[2].children[1].children[1].spec_type)
|
||||
|
||||
def test_navigation_with_exclusive_gateway(self):
|
||||
workflow = self.create_workflow('exclusive_gateway_2')
|
||||
@ -130,13 +136,16 @@ class TestTasksApi(BaseTest):
|
||||
self.assertIsNotNone(workflow_api.navigation)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual(7, len(nav))
|
||||
self.assertEqual("Task 1", nav[0]['title'])
|
||||
self.assertEqual("Which Branch?", nav[1]['title'])
|
||||
self.assertEqual("a", nav[2]['title'])
|
||||
self.assertEqual("Task 2a", nav[3]['title'])
|
||||
self.assertEqual("b", nav[4]['title'])
|
||||
self.assertEqual("Task 2b", nav[5]['title'])
|
||||
self.assertEqual("Task 3", nav[6]['title'])
|
||||
self.assertEqual("Task 1", nav[1].description)
|
||||
self.assertEqual("Which Branch?", nav[2].description)
|
||||
self.assertEqual("a", nav[2].children[0].description)
|
||||
self.assertEqual("Task 2a", nav[2].children[0].children[0].description)
|
||||
self.assertEqual("b", nav[2].children[1].description)
|
||||
self.assertEqual("Task 2b", nav[2].children[1].children[0].description)
|
||||
self.assertEqual(None, nav[3].description)
|
||||
self.assertEqual("Task 3", nav[4].description)
|
||||
self.assertEqual("EndEvent", nav[5].spec_type)
|
||||
|
||||
|
||||
def test_document_added_to_workflow_shows_up_in_file_list(self):
|
||||
self.create_reference_document()
|
||||
@ -267,7 +276,7 @@ class TestTasksApi(BaseTest):
|
||||
# get the first form in the two form workflow.
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
navigation = self.get_workflow_api(workflow).navigation
|
||||
self.assertEqual(4, len(navigation)) # Start task, form_task, multi_task, end task
|
||||
self.assertEqual(5, len(navigation)) # Start task, form_task, multi_task, end task
|
||||
self.assertEqual("UserTask", workflow.next_task.type)
|
||||
self.assertEqual(MultiInstanceType.sequential.value, workflow.next_task.multi_instance_type)
|
||||
self.assertEqual(5, workflow.next_task.multi_instance_count)
|
||||
@ -385,7 +394,7 @@ class TestTasksApi(BaseTest):
|
||||
navigation = workflow_api.navigation
|
||||
task = workflow_api.next_task
|
||||
|
||||
self.assertEqual(2, len(navigation))
|
||||
self.assertEqual(5, len(navigation))
|
||||
self.assertEqual("UserTask", task.type)
|
||||
self.assertEqual("Activity_A", task.name)
|
||||
self.assertEqual("My Sub Process", task.process_name)
|
||||
@ -452,8 +461,8 @@ class TestTasksApi(BaseTest):
|
||||
workflow = self.create_workflow('multi_instance_parallel')
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual(8, len(workflow_api.navigation))
|
||||
ready_items = [nav for nav in workflow_api.navigation if nav['state'] == "READY"]
|
||||
self.assertEqual(9, len(workflow_api.navigation))
|
||||
ready_items = [nav for nav in workflow_api.navigation if nav.state == "READY"]
|
||||
self.assertEqual(5, len(ready_items))
|
||||
|
||||
self.assertEqual("UserTask", workflow_api.next_task.type)
|
||||
@ -461,8 +470,8 @@ class TestTasksApi(BaseTest):
|
||||
self.assertEqual("Primary Investigator", workflow_api.next_task.title)
|
||||
|
||||
for i in random.sample(range(5), 5):
|
||||
task = TaskSchema().load(ready_items[i]['task'])
|
||||
rv = self.app.put('/v1.0/workflow/%i/task/%s/set_token' % (workflow.id, task.id),
|
||||
task_id = ready_items[i].task_id
|
||||
rv = self.app.put('/v1.0/workflow/%i/task/%s/set_token' % (workflow.id, task_id),
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json")
|
||||
self.assert_success(rv)
|
||||
@ -470,7 +479,7 @@ class TestTasksApi(BaseTest):
|
||||
workflow = WorkflowApiSchema().load(json_data)
|
||||
data = workflow.next_task.data
|
||||
data['investigator']['email'] = "dhf8r@virginia.edu"
|
||||
self.complete_form(workflow, task, data)
|
||||
self.complete_form(workflow, workflow.next_task, data)
|
||||
#tasks = self.get_workflow_api(workflow).user_tasks
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
|
28
tests/test_user_in_logs.py
Normal file
28
tests/test_user_in_logs.py
Normal file
@ -0,0 +1,28 @@
|
||||
from tests.base_test import BaseTest
|
||||
from crc import db
|
||||
from crc.models.user import UserModel
|
||||
import json
|
||||
|
||||
|
||||
class TestUserID(BaseTest):
|
||||
|
||||
def test_user_id_in_request(self):
|
||||
"""This assures the uid is in response via ApiError"""
|
||||
|
||||
workflow = self.create_workflow('failing_workflow')
|
||||
user_uid = workflow.study.user_uid
|
||||
user = db.session.query(UserModel).filter_by(uid=user_uid).first()
|
||||
rv = self.app.get(f'/v1.0/workflow/{workflow.id}'
|
||||
f'?soft_reset={str(False)}'
|
||||
f'&hard_reset={str(False)}'
|
||||
f'&do_engine_steps={str(True)}',
|
||||
headers=self.logged_in_headers(user),
|
||||
content_type="application/json")
|
||||
data = json.loads(rv.data)
|
||||
self.assertEqual(data['task_user'], user_uid)
|
||||
|
||||
def test_user_id_in_sentry(self):
|
||||
"""This assures the uid is in Sentry.
|
||||
We use this to send errors to Slack."""
|
||||
# Currently have no clue how to do this :(
|
||||
pass
|
@ -1,6 +1,8 @@
|
||||
import json
|
||||
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
from crc.models.api_models import NavigationItemSchema
|
||||
from crc.models.workflow import WorkflowStatus
|
||||
from crc import db
|
||||
from crc.api.common import ApiError
|
||||
@ -62,8 +64,8 @@ class TestTasksApi(BaseTest):
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual(5, len(nav))
|
||||
self.assertEqual("supervisor", nav[1]['lane'])
|
||||
self.assertEqual(4, len(nav))
|
||||
self.assertEqual("supervisor", nav[2].lane)
|
||||
|
||||
def test_get_outstanding_tasks_awaiting_current_user(self):
|
||||
submitter = self.create_user(uid='lje5u')
|
||||
@ -121,12 +123,10 @@ class TestTasksApi(BaseTest):
|
||||
# Navigation as Submitter with ready task.
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual(5, len(nav))
|
||||
self.assertEqual('READY', nav[0]['state']) # First item is ready, no progress yet.
|
||||
self.assertEqual('LOCKED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('LOCKED', nav[2]['state']) # third item is a gateway, and belongs to no one, and is locked.
|
||||
self.assertEqual('NOOP', nav[3]['state']) # Approved Path, has no operation
|
||||
self.assertEqual('NOOP', nav[4]['state']) # Rejected Path, has no operation.
|
||||
self.assertEqual(4, len(nav))
|
||||
self.assertEqual('READY', nav[1].state) # First item is ready, no progress yet.
|
||||
self.assertEqual('LOCKED', nav[2].state) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('LIKELY', nav[3].state) # Third item is a gateway, which contains things that are also locked.
|
||||
self.assertEqual('READY', workflow_api.next_task.state)
|
||||
|
||||
# Navigation as Submitter after handoff to supervisor
|
||||
@ -134,10 +134,9 @@ class TestTasksApi(BaseTest):
|
||||
data['supervisor'] = supervisor.uid
|
||||
workflow_api = self.complete_form(workflow, workflow_api.next_task, data, user_uid=submitter.uid)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual('COMPLETED', nav[0]['state']) # First item is ready, no progress yet.
|
||||
self.assertEqual('LOCKED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('LOCKED', nav[2]['state']) # third item is a gateway, and belongs to no one, and is locked.
|
||||
self.assertEqual('LOCKED', workflow_api.next_task.state)
|
||||
self.assertEqual('COMPLETED', nav[1].state) # First item is ready, no progress yet.
|
||||
self.assertEqual('LOCKED', nav[2].state) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('LIKELY', nav[3].state) # third item is a gateway, and belongs to no one
|
||||
# In the event the next task is locked, we should say something sensible here.
|
||||
# It is possible to look at the role of the task, and say The next task "TASK TITLE" will
|
||||
# be handled by 'dhf8r', who is full-filling the role of supervisor. the Task Data
|
||||
@ -149,10 +148,9 @@ class TestTasksApi(BaseTest):
|
||||
# Navigation as Supervisor
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=supervisor.uid)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual(5, len(nav))
|
||||
self.assertEqual('LOCKED', nav[0]['state']) # First item belongs to the submitter, and is locked.
|
||||
self.assertEqual('READY', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('LOCKED', nav[2]['state']) # third item is a gateway, and belongs to no one, and is locked.
|
||||
self.assertEqual('LOCKED', nav[1].state) # First item belongs to the submitter, and is locked.
|
||||
self.assertEqual('READY', nav[2].state) # Second item is ready, as we are now the supervisor.
|
||||
self.assertEqual('LIKELY', nav[3].state) # Feedback is locked.
|
||||
self.assertEqual('READY', workflow_api.next_task.state)
|
||||
|
||||
data = workflow_api.next_task.data
|
||||
@ -161,28 +159,33 @@ class TestTasksApi(BaseTest):
|
||||
|
||||
# Navigation as Supervisor, after completing task.
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual(5, len(nav))
|
||||
self.assertEqual('LOCKED', nav[0]['state']) # First item belongs to the submitter, and is locked.
|
||||
self.assertEqual('COMPLETED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('COMPLETED', nav[2]['state']) # third item is a gateway, and is now complete.
|
||||
self.assertEqual('LOCKED', nav[1].state) # First item belongs to the submitter, and is locked.
|
||||
self.assertEqual('COMPLETED', nav[2].state) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('READY', nav[3].state) # Gateway is ready, and should be unfolded
|
||||
self.assertEqual(None, nav[3].children[0].state) # sequence flow for approved is none - we aren't going this way.
|
||||
self.assertEqual('READY', nav[3].children[1].state) # sequence flow for denied is ready
|
||||
self.assertEqual('LOCKED', nav[3].children[1].children[0].state) # Feedback is locked, it belongs to submitter
|
||||
self.assertEqual('LOCKED', nav[3].children[1].children[0].state) # Approval is locked, it belongs to the submitter
|
||||
self.assertEqual('LOCKED', workflow_api.next_task.state)
|
||||
|
||||
# Navigation as Submitter, coming back in to a rejected workflow to view the rejection message.
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual(5, len(nav))
|
||||
self.assertEqual('COMPLETED', nav[0]['state']) # First item belongs to the submitter, and is locked.
|
||||
self.assertEqual('LOCKED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('LOCKED', nav[2]['state']) # third item is a gateway belonging to the supervisor, and is locked.
|
||||
self.assertEqual('READY', workflow_api.next_task.state)
|
||||
self.assertEqual(4, len(nav))
|
||||
self.assertEqual('COMPLETED', nav[1].state) # First item belongs to the submitter, and is locked.
|
||||
self.assertEqual('LOCKED', nav[2].state) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('READY', nav[3].state)
|
||||
self.assertEqual(None, nav[3].children[0].state) # sequence flow for approved is none - we aren't going this way.
|
||||
self.assertEqual('READY', nav[3].children[1].state) # sequence flow for denied is ready
|
||||
self.assertEqual('READY', nav[3].children[1].children[0].state) # Feedback is locked, it belongs to submitter
|
||||
self.assertEqual('READY', nav[3].children[1].children[0].state) # Approval is locked, it belongs to the submitter
|
||||
|
||||
# Navigation as Submitter, re-completing the original request a second time, and sending it for review.
|
||||
workflow_api = self.complete_form(workflow, workflow_api.next_task, data, user_uid=submitter.uid)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEqual(5, len(nav))
|
||||
self.assertEqual('READY', nav[0]['state']) # When you loop back the task is again in the ready state.
|
||||
self.assertEqual('LOCKED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('LOCKED', nav[2]['state']) # third item is a gateway belonging to the supervisor, and is locked.
|
||||
self.assertEqual('READY', nav[1].state) # When you loop back the task is again in the ready state.
|
||||
self.assertEqual('LOCKED', nav[2].state) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEqual('COMPLETED', nav[3].state) # Feedback is completed
|
||||
self.assertEqual('READY', workflow_api.next_task.state)
|
||||
|
||||
data["favorite_color"] = "blue"
|
||||
|
156
tests/test_workflow_sync.py
Normal file
156
tests/test_workflow_sync.py
Normal file
@ -0,0 +1,156 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from crc import db
|
||||
from tests.base_test import BaseTest
|
||||
from crc.api.workflow_sync import get_all_spec_state, \
|
||||
get_changed_workflows, \
|
||||
get_workflow_spec_files, \
|
||||
get_changed_files, \
|
||||
get_workflow_specification, \
|
||||
sync_changed_files
|
||||
from crc.models.workflow import WorkflowSpecModel
|
||||
from datetime import datetime
|
||||
from crc.services.file_service import FileService
|
||||
|
||||
|
||||
|
||||
class TestWorkflowSync(BaseTest):
|
||||
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_all_remote_workflows')
|
||||
def test_get_no_changes(self, mock_get):
|
||||
self.load_example_data()
|
||||
othersys = get_all_spec_state()
|
||||
mock_get.return_value = othersys
|
||||
response = get_changed_workflows('localhost:0000') # not actually used due to mock
|
||||
self.assertIsNotNone(response)
|
||||
self.assertEqual(response,[])
|
||||
|
||||
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_all_remote_workflows')
|
||||
def test_remote_workflow_change(self, mock_get):
|
||||
self.load_example_data()
|
||||
othersys = get_all_spec_state()
|
||||
othersys[1]['date_created'] = str(datetime.now())
|
||||
othersys[1]['md5_hash'] = '12345'
|
||||
mock_get.return_value = othersys
|
||||
response = get_changed_workflows('localhost:0000') #endpoint is not used due to mock
|
||||
self.assertIsNotNone(response)
|
||||
self.assertEqual(len(response),1)
|
||||
self.assertEqual(response[0]['workflow_spec_id'], 'random_fact')
|
||||
self.assertEqual(response[0]['location'], 'remote')
|
||||
self.assertEqual(response[0]['new'], False)
|
||||
|
||||
|
||||
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_all_remote_workflows')
|
||||
def test_remote_workflow_has_new(self, mock_get):
|
||||
self.load_example_data()
|
||||
othersys = get_all_spec_state()
|
||||
othersys.append({'workflow_spec_id':'my_new_workflow',
|
||||
'date_created':str(datetime.now()),
|
||||
'md5_hash': '12345'})
|
||||
mock_get.return_value = othersys
|
||||
response = get_changed_workflows('localhost:0000') #endpoint is not used due to mock
|
||||
self.assertIsNotNone(response)
|
||||
self.assertEqual(len(response),1)
|
||||
self.assertEqual(response[0]['workflow_spec_id'],'my_new_workflow')
|
||||
self.assertEqual(response[0]['location'], 'remote')
|
||||
self.assertEqual(response[0]['new'], True)
|
||||
|
||||
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_all_remote_workflows')
|
||||
def test_local_workflow_has_new(self, mock_get):
|
||||
self.load_example_data()
|
||||
|
||||
othersys = get_all_spec_state()
|
||||
mock_get.return_value = othersys
|
||||
wf_spec = WorkflowSpecModel()
|
||||
wf_spec.id = 'abcdefg'
|
||||
wf_spec.display_name = 'New Workflow - Yum!!'
|
||||
wf_spec.name = 'my_new_workflow'
|
||||
wf_spec.description = 'yep - its a new workflow'
|
||||
wf_spec.category_id = 0
|
||||
wf_spec.display_order = 0
|
||||
db.session.add(wf_spec)
|
||||
db.session.commit()
|
||||
FileService.add_workflow_spec_file(wf_spec,'dummyfile.txt','text',b'this is a test')
|
||||
# after setting up the test - I realized that this doesn't return anything for
|
||||
# a workflow that is new locally - it just returns nothing
|
||||
response = get_changed_workflows('localhost:0000') #endpoint is not used due to mock
|
||||
self.assertIsNotNone(response)
|
||||
self.assertEqual(response,[])
|
||||
|
||||
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec_files')
|
||||
def test_file_differences(self, mock_get):
|
||||
self.load_example_data()
|
||||
othersys = get_workflow_spec_files('random_fact')
|
||||
othersys[1]['date_created'] = str(datetime.now())
|
||||
othersys[1]['md5_hash'] = '12345'
|
||||
mock_get.return_value = othersys
|
||||
response = get_changed_files('localhost:0000','random_fact',as_df=False) #endpoint is not used due to mock
|
||||
self.assertIsNotNone(response)
|
||||
self.assertEqual(len(response),1)
|
||||
self.assertEqual(response[0]['filename'], 'random_fact2.bpmn')
|
||||
self.assertEqual(response[0]['location'], 'remote')
|
||||
self.assertEqual(response[0]['new'], False)
|
||||
|
||||
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec_files')
|
||||
def test_file_differences(self, mock_get):
|
||||
self.load_example_data()
|
||||
othersys = get_workflow_spec_files('random_fact')
|
||||
othersys[1]['date_created'] = str(datetime.now())
|
||||
othersys[1]['md5_hash'] = '12345'
|
||||
mock_get.return_value = othersys
|
||||
response = get_changed_files('localhost:0000','random_fact',as_df=False) #endpoint is not used due to mock
|
||||
self.assertIsNotNone(response)
|
||||
self.assertEqual(len(response),1)
|
||||
self.assertEqual(response[0]['filename'], 'random_fact2.bpmn')
|
||||
self.assertEqual(response[0]['location'], 'remote')
|
||||
self.assertEqual(response[0]['new'], False)
|
||||
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_file_by_hash')
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec_files')
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec')
|
||||
def test_file_differences(self, workflow_mock, spec_files_mock, file_data_mock):
|
||||
self.load_example_data()
|
||||
remote_workflow = get_workflow_specification('random_fact')
|
||||
self.assertEqual(remote_workflow['display_name'],'Random Fact')
|
||||
remote_workflow['description'] = 'This Workflow came from Remote'
|
||||
remote_workflow['display_name'] = 'Remote Workflow'
|
||||
workflow_mock.return_value = remote_workflow
|
||||
othersys = get_workflow_spec_files('random_fact')
|
||||
othersys[1]['date_created'] = str(datetime.now())
|
||||
othersys[1]['md5_hash'] = '12345'
|
||||
spec_files_mock.return_value = othersys
|
||||
file_data_mock.return_value = self.workflow_sync_response('random_fact2.bpmn')
|
||||
response = sync_changed_files('localhost:0000','random_fact') # endpoint not used due to mock
|
||||
self.assertIsNotNone(response)
|
||||
self.assertEqual(len(response),1)
|
||||
self.assertEqual(response[0], 'random_fact2.bpmn')
|
||||
files = FileService.get_spec_data_files('random_fact')
|
||||
md5sums = [str(f.md5_hash) for f in files]
|
||||
self.assertEqual('21bb6f9e-0af7-0ab2-0fc7-ec0f94787e58' in md5sums, True)
|
||||
new_local_workflow = get_workflow_specification('random_fact')
|
||||
self.assertEqual(new_local_workflow['display_name'],'Remote Workflow')
|
||||
|
||||
|
||||
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec_files')
|
||||
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec')
|
||||
def test_file_deleted(self, workflow_mock, spec_files_mock):
|
||||
self.load_example_data()
|
||||
remote_workflow = get_workflow_specification('random_fact')
|
||||
workflow_mock.return_value = remote_workflow
|
||||
othersys = get_workflow_spec_files('random_fact')
|
||||
del(othersys[1])
|
||||
spec_files_mock.return_value = othersys
|
||||
response = sync_changed_files('localhost:0000','random_fact') # endpoint not used due to mock
|
||||
self.assertIsNotNone(response)
|
||||
# when we delete a local file, we do not return that it was deleted - just
|
||||
# a list of updated files. We may want to change this in the future.
|
||||
self.assertEqual(len(response),0)
|
||||
files = FileService.get_spec_data_files('random_fact')
|
||||
self.assertEqual(len(files),1)
|
||||
|
@ -52,11 +52,11 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
|
||||
task_list = processor.get_ready_user_tasks()
|
||||
processor.complete_task(task_list[0])
|
||||
processor.do_engine_steps()
|
||||
nav_list = processor.bpmn_workflow.get_nav_list()
|
||||
nav_list = processor.bpmn_workflow.get_flat_nav_list()
|
||||
processor.save()
|
||||
# reload after save
|
||||
processor = WorkflowProcessor(workflow_spec_model)
|
||||
nav_list2 = processor.bpmn_workflow.get_nav_list()
|
||||
nav_list2 = processor.bpmn_workflow.get_flat_nav_list()
|
||||
self.assertEqual(nav_list,nav_list2)
|
||||
|
||||
@patch('crc.services.study_service.StudyService.get_investigators')
|
||||
@ -158,7 +158,7 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
|
||||
self.assertEqual(3, len(next_user_tasks))
|
||||
# There should be six tasks in the navigation: start event, the script task, end event, and three tasks
|
||||
# for the three executions of hte multi-instance.
|
||||
self.assertEqual(6, len(processor.bpmn_workflow.get_nav_list()))
|
||||
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
|
||||
|
||||
# We can complete the tasks out of order.
|
||||
task = next_user_tasks[2]
|
||||
@ -171,12 +171,12 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
|
||||
|
||||
# Assure navigation picks up the label of the current element variable.
|
||||
nav = WorkflowService.processor_to_workflow_api(processor, task).navigation
|
||||
self.assertEqual("Primary Investigator", nav[2].title)
|
||||
self.assertEqual("Primary Investigator", nav[2].description)
|
||||
|
||||
task.update_data({"investigator": {"email": "dhf8r@virginia.edu"}})
|
||||
processor.complete_task(task)
|
||||
processor.do_engine_steps()
|
||||
self.assertEqual(6, len(processor.bpmn_workflow.get_nav_list()))
|
||||
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
|
||||
|
||||
task = next_user_tasks[0]
|
||||
api_task = WorkflowService.spiff_task_to_api_task(task)
|
||||
@ -184,7 +184,7 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
|
||||
task.update_data({"investigator":{"email":"asd3v@virginia.edu"}})
|
||||
processor.complete_task(task)
|
||||
processor.do_engine_steps()
|
||||
self.assertEqual(6, len(processor.bpmn_workflow.get_nav_list()))
|
||||
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
|
||||
|
||||
task = next_user_tasks[1]
|
||||
api_task = WorkflowService.spiff_task_to_api_task(task)
|
||||
@ -192,7 +192,7 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
|
||||
task.update_data({"investigator":{"email":"asdf32@virginia.edu"}})
|
||||
processor.complete_task(task)
|
||||
processor.do_engine_steps()
|
||||
self.assertEqual(6, len(processor.bpmn_workflow.get_nav_list()))
|
||||
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
|
||||
|
||||
# Completing the tasks out of order, still provides the correct information.
|
||||
expected = self.mock_investigator_response
|
||||
@ -203,4 +203,4 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
|
||||
task.data['StudyInfo']['investigators'])
|
||||
|
||||
self.assertEqual(WorkflowStatus.complete, processor.get_status())
|
||||
self.assertEqual(6, len(processor.bpmn_workflow.get_nav_list()))
|
||||
self.assertEqual(7, len(processor.bpmn_workflow.get_flat_nav_list()))
|
||||
|
Loading…
x
Reference in New Issue
Block a user