mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-21 12:18:08 +00:00
commit
beec0b591d
44
Pipfile.lock
generated
44
Pipfile.lock
generated
@ -35,7 +35,6 @@
|
||||
"sha256:24dbaff8ce4f30566bb88976b398e8c4e77637171af3af6f1b9650f48890e60b",
|
||||
"sha256:bb68f8d2bced8f93ccfd07d96c689b716b3227720add971be980accfc2952139"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"aniso8601": {
|
||||
@ -50,7 +49,6 @@
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==19.3.0"
|
||||
},
|
||||
"babel": {
|
||||
@ -58,7 +56,6 @@
|
||||
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
|
||||
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"bcrypt": {
|
||||
@ -82,7 +79,6 @@
|
||||
"sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7",
|
||||
"sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.1.7"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
@ -111,7 +107,6 @@
|
||||
"sha256:ef17d7dffde7fc73ecab3a3b6389d93d3213bac53fa7f28e68e33647ad50b916",
|
||||
"sha256:fd77e4248bb1b7af5f7922dd8e81156f540306e3a5c4b1c24167c1f5f06025da"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.4.6"
|
||||
},
|
||||
"certifi": {
|
||||
@ -166,7 +161,6 @@
|
||||
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
|
||||
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"clickclick": {
|
||||
@ -188,7 +182,6 @@
|
||||
"sha256:2ca44140ee259b5e3d8aaf47c79c36a7ab0d5e94d70bd4105c03ede7a20ea5a1",
|
||||
"sha256:cffc044844040c7ce04e9acd1838b5f2e5fa3170182f6fda4d2ea8b0099dbadd"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.0"
|
||||
},
|
||||
"connexion": {
|
||||
@ -247,7 +240,6 @@
|
||||
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
||||
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.16"
|
||||
},
|
||||
"docxtpl": {
|
||||
@ -337,7 +329,6 @@
|
||||
"hashes": [
|
||||
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"gunicorn": {
|
||||
@ -360,7 +351,6 @@
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"imagesize": {
|
||||
@ -368,7 +358,6 @@
|
||||
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
|
||||
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
@ -384,7 +373,6 @@
|
||||
"sha256:88b101b2668a1d81d6d72d4c2018e53bc6c7fc544c987849da1c7f77545c3bc9",
|
||||
"sha256:f576e85132d34f5bf7df5183c2c6f94cfb32e528f53065345cf71329ba0b8924"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"itsdangerous": {
|
||||
@ -392,7 +380,6 @@
|
||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
|
||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jdcal": {
|
||||
@ -407,7 +394,6 @@
|
||||
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
|
||||
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.11.2"
|
||||
},
|
||||
"jsonschema": {
|
||||
@ -422,7 +408,6 @@
|
||||
"sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a",
|
||||
"sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.6.11"
|
||||
},
|
||||
"ldap3": {
|
||||
@ -475,7 +460,6 @@
|
||||
"sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27",
|
||||
"sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.3"
|
||||
},
|
||||
"markdown": {
|
||||
@ -522,7 +506,6 @@
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"marshmallow": {
|
||||
@ -578,7 +561,6 @@
|
||||
"sha256:df1889701e2dfd8ba4dc9b1a010f0a60950077fb5242bb92c8b5c7f1a6f2668a",
|
||||
"sha256:fa1fe75b4a9e18b66ae7f0b122543c42debcf800aaafa0212aaff3ad273c2596"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.19.0"
|
||||
},
|
||||
"openapi-spec-validator": {
|
||||
@ -602,7 +584,6 @@
|
||||
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
|
||||
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.4"
|
||||
},
|
||||
"pandas": {
|
||||
@ -686,7 +667,6 @@
|
||||
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
||||
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.20"
|
||||
},
|
||||
"pygments": {
|
||||
@ -694,7 +674,6 @@
|
||||
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
|
||||
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.6.1"
|
||||
},
|
||||
"pyjwt": {
|
||||
@ -710,7 +689,6 @@
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pyrsistent": {
|
||||
@ -853,7 +831,6 @@
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
@ -868,7 +845,6 @@
|
||||
"sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55",
|
||||
"sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"sphinx": {
|
||||
@ -884,7 +860,6 @@
|
||||
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
|
||||
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-devhelp": {
|
||||
@ -892,7 +867,6 @@
|
||||
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
|
||||
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-htmlhelp": {
|
||||
@ -900,7 +874,6 @@
|
||||
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
|
||||
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"sphinxcontrib-jsmath": {
|
||||
@ -908,7 +881,6 @@
|
||||
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
|
||||
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"sphinxcontrib-qthelp": {
|
||||
@ -916,7 +888,6 @@
|
||||
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
|
||||
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"sphinxcontrib-serializinghtml": {
|
||||
@ -924,7 +895,6 @@
|
||||
"sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
|
||||
"sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.1.4"
|
||||
},
|
||||
"spiffworkflow": {
|
||||
@ -963,7 +933,6 @@
|
||||
"sha256:f57be5673e12763dd400fea568608700a63ce1c6bd5bdbc3cc3a2c5fdb045274",
|
||||
"sha256:fc728ece3d5c772c196fd338a99798e7efac7a04f9cb6416299a3638ee9a94cd"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.3.18"
|
||||
},
|
||||
"swagger-ui-bundle": {
|
||||
@ -980,7 +949,6 @@
|
||||
"sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
|
||||
"sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.25.9"
|
||||
},
|
||||
"vine": {
|
||||
@ -988,7 +956,6 @@
|
||||
"sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87",
|
||||
"sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"waitress": {
|
||||
@ -996,7 +963,6 @@
|
||||
"sha256:1bb436508a7487ac6cb097ae7a7fe5413aefca610550baf58f0940e51ecfb261",
|
||||
"sha256:3d633e78149eb83b60a07dfabb35579c29aac2d24bb803c18b26fb2ab1a584db"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==1.4.4"
|
||||
},
|
||||
"webob": {
|
||||
@ -1004,7 +970,6 @@
|
||||
"sha256:a3c89a8e9ba0aeb17382836cdb73c516d0ecf6630ec40ec28288f3ed459ce87b",
|
||||
"sha256:aa3a917ed752ba3e0b242234b2a373f9c4e2a75d35291dcbe977649bd21fd108"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.8.6"
|
||||
},
|
||||
"webtest": {
|
||||
@ -1051,7 +1016,6 @@
|
||||
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
|
||||
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.1.0"
|
||||
}
|
||||
},
|
||||
@ -1061,7 +1025,6 @@
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==19.3.0"
|
||||
},
|
||||
"coverage": {
|
||||
@ -1117,7 +1080,6 @@
|
||||
"sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5",
|
||||
"sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==8.4.0"
|
||||
},
|
||||
"packaging": {
|
||||
@ -1125,7 +1087,6 @@
|
||||
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
|
||||
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.4"
|
||||
},
|
||||
"pbr": {
|
||||
@ -1141,7 +1102,6 @@
|
||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"py": {
|
||||
@ -1149,7 +1109,6 @@
|
||||
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
|
||||
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.9.0"
|
||||
},
|
||||
"pyparsing": {
|
||||
@ -1157,7 +1116,6 @@
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pytest": {
|
||||
@ -1173,7 +1131,6 @@
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"wcwidth": {
|
||||
@ -1188,7 +1145,6 @@
|
||||
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
|
||||
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.1.0"
|
||||
}
|
||||
}
|
||||
|
51
crc/api.yml
51
crc/api.yml
@ -502,7 +502,6 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/File"
|
||||
# /v1.0/workflow/0
|
||||
/reference_file:
|
||||
get:
|
||||
operationId: crc.api.file.get_reference_files
|
||||
@ -565,6 +564,26 @@ paths:
|
||||
type: string
|
||||
format: binary
|
||||
example: '<?xml version="1.0" encoding="UTF-8"?><bpmn:definitions></bpmn:definitions>'
|
||||
/task_events:
|
||||
parameters:
|
||||
- name: action
|
||||
in: query
|
||||
required: false
|
||||
description: The type of action the event documents, options include "ASSIGNMENT" for tasks that are waiting on you, "COMPLETE" for things have completed.
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
operationId: crc.api.workflow.get_task_events
|
||||
summary: Returns a list of task events related to the current user. Can be filtered by type.
|
||||
tags:
|
||||
- Workflows and Tasks
|
||||
responses:
|
||||
'200':
|
||||
description: Returns details about tasks that are waiting on the current user.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TaskEvent"
|
||||
# /v1.0/workflow/0
|
||||
/workflow/{workflow_id}:
|
||||
parameters:
|
||||
@ -1192,6 +1211,36 @@ components:
|
||||
value: "model.my_boolean_field_id && model.my_enum_field_value !== 'something'"
|
||||
- id: "hide_expression"
|
||||
value: "model.my_enum_field_value === 'something'"
|
||||
TaskEvent:
|
||||
properties:
|
||||
workflow:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
study:
|
||||
$ref: "#/components/schemas/Study"
|
||||
workflow_sec:
|
||||
$ref: "#/components/schemas/WorkflowSpec"
|
||||
spec_version:
|
||||
type: string
|
||||
action:
|
||||
type: string
|
||||
task_id:
|
||||
type: string
|
||||
task_type:
|
||||
type: string
|
||||
task_lane:
|
||||
type: string
|
||||
form_data:
|
||||
type: object
|
||||
mi_type:
|
||||
type: string
|
||||
mi_count:
|
||||
type: integer
|
||||
mi_index:
|
||||
type: integer
|
||||
process_name:
|
||||
type: string
|
||||
date:
|
||||
type: string
|
||||
Form:
|
||||
properties:
|
||||
key:
|
||||
|
@ -12,7 +12,7 @@ from crc import db, app
|
||||
from crc.api.user import verify_token, verify_token_admin
|
||||
from crc.models.approval import ApprovalModel
|
||||
from crc.models.file import FileModel
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.user import UserModel
|
||||
from crc.models.workflow import WorkflowModel
|
||||
|
@ -6,7 +6,8 @@ from crc import session, app
|
||||
from crc.api.common import ApiError, ApiErrorSchema
|
||||
from crc.models.api_models import WorkflowApi, WorkflowApiSchema, NavigationItem, NavigationItemSchema
|
||||
from crc.models.file import FileModel, LookupDataSchema
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.study import StudyModel, WorkflowMetadata
|
||||
from crc.models.task_event import TaskEventModel, TaskEventModelSchema, TaskEvent, TaskEventSchema
|
||||
from crc.models.workflow import WorkflowModel, WorkflowSpecModelSchema, WorkflowSpecModel, WorkflowSpecCategoryModel, \
|
||||
WorkflowSpecCategoryModelSchema
|
||||
from crc.services.file_service import FileService
|
||||
@ -87,7 +88,7 @@ def delete_workflow_specification(spec_id):
|
||||
|
||||
session.query(TaskEventModel).filter(TaskEventModel.workflow_spec_id == spec_id).delete()
|
||||
|
||||
# Delete all stats and workflow models related to this specification
|
||||
# Delete all events and workflow models related to this specification
|
||||
for workflow in session.query(WorkflowModel).filter_by(workflow_spec_id=spec_id):
|
||||
StudyService.delete_workflow(workflow)
|
||||
session.query(WorkflowSpecModel).filter_by(id=spec_id).delete()
|
||||
@ -98,19 +99,38 @@ def get_workflow(workflow_id, soft_reset=False, hard_reset=False):
|
||||
workflow_model: WorkflowModel = session.query(WorkflowModel).filter_by(id=workflow_id).first()
|
||||
processor = WorkflowProcessor(workflow_model, soft_reset=soft_reset, hard_reset=hard_reset)
|
||||
workflow_api_model = WorkflowService.processor_to_workflow_api(processor)
|
||||
WorkflowService.update_task_assignments(processor)
|
||||
return WorkflowApiSchema().dump(workflow_api_model)
|
||||
|
||||
|
||||
def get_task_events(action):
|
||||
"""Provides a way to see a history of what has happened, or get a list of tasks that need your attention."""
|
||||
query = session.query(TaskEventModel).filter(TaskEventModel.user_uid == g.user.uid)
|
||||
if action:
|
||||
query = query.filter(TaskEventModel.action == action)
|
||||
events = query.all()
|
||||
|
||||
# Turn the database records into something a little richer for the UI to use.
|
||||
task_events = []
|
||||
for event in events:
|
||||
study = session.query(StudyModel).filter(StudyModel.id == event.study_id).first()
|
||||
workflow = session.query(WorkflowModel).filter(WorkflowModel.id == event.workflow_id).first()
|
||||
workflow_meta = WorkflowMetadata.from_workflow(workflow)
|
||||
task_events.append(TaskEvent(event, study, workflow_meta))
|
||||
return TaskEventSchema(many=True).dump(task_events)
|
||||
|
||||
|
||||
def delete_workflow(workflow_id):
|
||||
StudyService.delete_workflow(workflow_id)
|
||||
|
||||
|
||||
def set_current_task(workflow_id, task_id):
|
||||
workflow_model = session.query(WorkflowModel).filter_by(id=workflow_id).first()
|
||||
user_uid = __get_user_uid(workflow_model.study.user_uid)
|
||||
processor = WorkflowProcessor(workflow_model)
|
||||
task_id = uuid.UUID(task_id)
|
||||
spiff_task = processor.bpmn_workflow.get_task(task_id)
|
||||
_verify_user_and_role(processor, spiff_task)
|
||||
user_uid = g.user.uid
|
||||
if spiff_task.state != spiff_task.COMPLETED and spiff_task.state != spiff_task.READY:
|
||||
raise ApiError("invalid_state", "You may not move the token to a task who's state is not "
|
||||
"currently set to COMPLETE or READY.")
|
||||
@ -120,41 +140,42 @@ def set_current_task(workflow_id, task_id):
|
||||
spiff_task.reset_token(reset_data=True) # Don't try to copy the existing data back into this task.
|
||||
|
||||
processor.save()
|
||||
WorkflowService.log_task_action(user_uid, workflow_model, spiff_task,
|
||||
WorkflowService.TASK_ACTION_TOKEN_RESET,
|
||||
version=processor.get_version_string())
|
||||
WorkflowService.log_task_action(user_uid, processor, spiff_task, WorkflowService.TASK_ACTION_TOKEN_RESET)
|
||||
WorkflowService.update_task_assignments(processor)
|
||||
|
||||
workflow_api_model = WorkflowService.processor_to_workflow_api(processor, spiff_task)
|
||||
return WorkflowApiSchema().dump(workflow_api_model)
|
||||
|
||||
|
||||
def update_task(workflow_id, task_id, body, terminate_loop=None):
|
||||
workflow_model = session.query(WorkflowModel).filter_by(id=workflow_id).first()
|
||||
|
||||
if workflow_model is None:
|
||||
raise ApiError("invalid_workflow_id", "The given workflow id is not valid.", status_code=404)
|
||||
|
||||
elif workflow_model.study is None:
|
||||
raise ApiError("invalid_study", "There is no study associated with the given workflow.", status_code=404)
|
||||
|
||||
user_uid = __get_user_uid(workflow_model.study.user_uid)
|
||||
processor = WorkflowProcessor(workflow_model)
|
||||
task_id = uuid.UUID(task_id)
|
||||
spiff_task = processor.bpmn_workflow.get_task(task_id)
|
||||
_verify_user_and_role(processor, spiff_task)
|
||||
if not spiff_task:
|
||||
raise ApiError("empty_task", "Processor failed to obtain task.", status_code=404)
|
||||
if spiff_task.state != spiff_task.READY:
|
||||
raise ApiError("invalid_state", "You may not update a task unless it is in the READY state. "
|
||||
"Consider calling a token reset to make this task Ready.")
|
||||
|
||||
if terminate_loop:
|
||||
spiff_task.terminate_loop()
|
||||
|
||||
spiff_task.update_data(body)
|
||||
processor.complete_task(spiff_task)
|
||||
processor.do_engine_steps()
|
||||
processor.save()
|
||||
|
||||
WorkflowService.log_task_action(user_uid, workflow_model, spiff_task, WorkflowService.TASK_ACTION_COMPLETE,
|
||||
version=processor.get_version_string())
|
||||
# Log the action, and any pending task assignments in the event of lanes in the workflow.
|
||||
WorkflowService.log_task_action(g.user.uid, processor, spiff_task, WorkflowService.TASK_ACTION_COMPLETE)
|
||||
WorkflowService.update_task_assignments(processor)
|
||||
|
||||
workflow_api_model = WorkflowService.processor_to_workflow_api(processor)
|
||||
return WorkflowApiSchema().dump(workflow_api_model)
|
||||
|
||||
@ -210,13 +231,21 @@ def lookup(workflow_id, field_id, query=None, value=None, limit=10):
|
||||
return LookupDataSchema(many=True).dump(lookup_data)
|
||||
|
||||
|
||||
def __get_user_uid(user_uid):
|
||||
if 'user' in g:
|
||||
if g.user.uid not in app.config['ADMIN_UIDS'] and user_uid != g.user.uid:
|
||||
raise ApiError("permission_denied", "You are not authorized to edit the task data for this workflow.",
|
||||
status_code=403)
|
||||
else:
|
||||
return g.user.uid
|
||||
def _verify_user_and_role(processor, spiff_task):
|
||||
"""Assures the currently logged in user can access the given workflow and task, or
|
||||
raises an error.
|
||||
Allow administrators to modify tasks, otherwise assure that the current user
|
||||
is allowed to edit or update the task. Will raise the appropriate error if user
|
||||
is not authorized. """
|
||||
|
||||
else:
|
||||
if 'user' not in g:
|
||||
raise ApiError("logged_out", "You are no longer logged in.", status_code=401)
|
||||
|
||||
if g.user.uid in app.config['ADMIN_UIDS']:
|
||||
return g.user.uid
|
||||
|
||||
allowed_users = WorkflowService.get_users_assigned_to_task(processor, spiff_task)
|
||||
if g.user.uid not in allowed_users:
|
||||
raise ApiError.from_task("permission_denied",
|
||||
f"This task must be completed by '{allowed_users}', "
|
||||
f"but you are {g.user.uid}", spiff_task)
|
||||
|
@ -29,6 +29,7 @@ class NavigationItem(object):
|
||||
self.state = state
|
||||
self.is_decision = is_decision
|
||||
self.task = task
|
||||
self.lane = lane
|
||||
|
||||
class Task(object):
|
||||
|
||||
@ -63,8 +64,9 @@ class Task(object):
|
||||
|
||||
##########################################################################
|
||||
|
||||
def __init__(self, id, name, title, type, state, form, documentation, data,
|
||||
multi_instance_type, multi_instance_count, multi_instance_index, process_name, properties):
|
||||
def __init__(self, id, name, title, type, state, lane, form, documentation, data,
|
||||
multi_instance_type, multi_instance_count, multi_instance_index,
|
||||
process_name, properties):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.title = title
|
||||
@ -73,6 +75,7 @@ class Task(object):
|
||||
self.form = form
|
||||
self.documentation = documentation
|
||||
self.data = data
|
||||
self.lane = lane
|
||||
self.multi_instance_type = multi_instance_type # Some tasks have a repeat behavior.
|
||||
self.multi_instance_count = multi_instance_count # This is the number of times the task could repeat.
|
||||
self.multi_instance_index = multi_instance_index # And the index of the currently repeating task.
|
||||
@ -111,7 +114,7 @@ class FormSchema(ma.Schema):
|
||||
|
||||
class TaskSchema(ma.Schema):
|
||||
class Meta:
|
||||
fields = ["id", "name", "title", "type", "state", "form", "documentation", "data", "multi_instance_type",
|
||||
fields = ["id", "name", "title", "type", "state", "lane", "form", "documentation", "data", "multi_instance_type",
|
||||
"multi_instance_count", "multi_instance_index", "process_name", "properties"]
|
||||
|
||||
multi_instance_type = EnumField(MultiInstanceType)
|
||||
@ -119,6 +122,7 @@ class TaskSchema(ma.Schema):
|
||||
form = marshmallow.fields.Nested(FormSchema, required=False, allow_none=True)
|
||||
title = marshmallow.fields.String(required=False, allow_none=True)
|
||||
process_name = marshmallow.fields.String(required=False, allow_none=True)
|
||||
lane = marshmallow.fields.String(required=False, allow_none=True)
|
||||
|
||||
@marshmallow.post_load
|
||||
def make_task(self, data, **kwargs):
|
||||
@ -128,10 +132,11 @@ 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"]
|
||||
"is_decision", "task", "lane"]
|
||||
unknown = INCLUDE
|
||||
task = marshmallow.fields.Nested(TaskSchema, dump_only=True, 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)
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
||||
|
||||
from crc import db
|
||||
|
||||
|
||||
class TaskEventModel(db.Model):
|
||||
__tablename__ = 'task_event'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
study_id = db.Column(db.Integer, db.ForeignKey('study.id'), nullable=False)
|
||||
user_uid = db.Column(db.String, db.ForeignKey('user.uid'), nullable=False)
|
||||
workflow_id = db.Column(db.Integer, db.ForeignKey('workflow.id'), nullable=False)
|
||||
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'))
|
||||
spec_version = db.Column(db.String)
|
||||
action = db.Column(db.String)
|
||||
task_id = db.Column(db.String)
|
||||
task_name = db.Column(db.String)
|
||||
task_title = db.Column(db.String)
|
||||
task_type = db.Column(db.String)
|
||||
task_state = db.Column(db.String)
|
||||
form_data = db.Column(db.JSON) # And form data submitted when the task was completed.
|
||||
mi_type = db.Column(db.String)
|
||||
mi_count = db.Column(db.Integer)
|
||||
mi_index = db.Column(db.Integer)
|
||||
process_name = db.Column(db.String)
|
||||
date = db.Column(db.DateTime)
|
||||
|
||||
|
||||
class TaskEventModelSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = TaskEventModel
|
||||
load_instance = True
|
||||
include_relationships = True
|
||||
include_fk = True # Includes foreign keys
|
64
crc/models/task_event.py
Normal file
64
crc/models/task_event.py
Normal file
@ -0,0 +1,64 @@
|
||||
from marshmallow import INCLUDE, fields
|
||||
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
||||
|
||||
from crc import db, ma
|
||||
from crc.models.study import StudyModel, StudySchema, WorkflowMetadataSchema, WorkflowMetadata
|
||||
from crc.models.workflow import WorkflowModel
|
||||
|
||||
|
||||
class TaskEventModel(db.Model):
|
||||
__tablename__ = 'task_event'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
study_id = db.Column(db.Integer, db.ForeignKey('study.id'), nullable=False)
|
||||
user_uid = db.Column(db.String, nullable=False) # In some cases the unique user id may not exist in the db yet.
|
||||
workflow_id = db.Column(db.Integer, db.ForeignKey('workflow.id'), nullable=False)
|
||||
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'))
|
||||
spec_version = db.Column(db.String)
|
||||
action = db.Column(db.String)
|
||||
task_id = db.Column(db.String)
|
||||
task_name = db.Column(db.String)
|
||||
task_title = db.Column(db.String)
|
||||
task_type = db.Column(db.String)
|
||||
task_state = db.Column(db.String)
|
||||
task_lane = db.Column(db.String)
|
||||
form_data = db.Column(db.JSON) # And form data submitted when the task was completed.
|
||||
mi_type = db.Column(db.String)
|
||||
mi_count = db.Column(db.Integer)
|
||||
mi_index = db.Column(db.Integer)
|
||||
process_name = db.Column(db.String)
|
||||
date = db.Column(db.DateTime)
|
||||
|
||||
|
||||
class TaskEventModelSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = TaskEventModel
|
||||
load_instance = True
|
||||
include_relationships = True
|
||||
include_fk = True # Includes foreign keys
|
||||
|
||||
|
||||
class TaskEvent(object):
|
||||
def __init__(self, model: TaskEventModel, study: StudyModel, workflow: WorkflowMetadata):
|
||||
self.id = model.id
|
||||
self.study = study
|
||||
self.workflow = workflow
|
||||
self.user_uid = model.user_uid
|
||||
self.action = model.action
|
||||
self.task_id = model.task_id
|
||||
self.task_title = model.task_title
|
||||
self.task_name = model.task_name
|
||||
self.task_type = model.task_type
|
||||
self.task_state = model.task_state
|
||||
self.task_lane = model.task_lane
|
||||
|
||||
|
||||
class TaskEventSchema(ma.Schema):
|
||||
|
||||
study = fields.Nested(StudySchema, dump_only=True)
|
||||
workflow = fields.Nested(WorkflowMetadataSchema, dump_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TaskEvent
|
||||
additional = ["id", "user_uid", "action", "task_id", "task_title",
|
||||
"task_name", "task_type", "task_state", "task_lane"]
|
||||
unknown = INCLUDE
|
@ -13,7 +13,7 @@ from crc.api.common import ApiError
|
||||
from crc.models.file import FileModel, FileModelSchema, File
|
||||
from crc.models.ldap import LdapSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.study import StudyModel, Study, Category, WorkflowMetadata
|
||||
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \
|
||||
WorkflowStatus
|
||||
|
@ -1,6 +1,7 @@
|
||||
import copy
|
||||
import json
|
||||
import string
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
import random
|
||||
|
||||
@ -15,13 +16,14 @@ from SpiffWorkflow.bpmn.specs.UserTask import UserTask
|
||||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
||||
from SpiffWorkflow.specs import CancelTask, StartTask
|
||||
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.file import LookupDataModel
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.user import UserModel
|
||||
from crc.models.workflow import WorkflowModel, WorkflowStatus, WorkflowSpecModel
|
||||
@ -32,10 +34,13 @@ from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
||||
|
||||
class WorkflowService(object):
|
||||
TASK_ACTION_COMPLETE = "Complete"
|
||||
TASK_ACTION_TOKEN_RESET = "Backwards Move"
|
||||
TASK_ACTION_HARD_RESET = "Restart (Hard)"
|
||||
TASK_ACTION_SOFT_RESET = "Restart (Soft)"
|
||||
TASK_ACTION_COMPLETE = "COMPLETE"
|
||||
TASK_ACTION_TOKEN_RESET = "TOKEN_RESET"
|
||||
TASK_ACTION_HARD_RESET = "HARD_RESET"
|
||||
TASK_ACTION_SOFT_RESET = "SOFT_RESET"
|
||||
TASK_ACTION_ASSIGNMENT = "ASSIGNMENT" # Whenever the lane changes between tasks we assign the task to specifc user.
|
||||
|
||||
TASK_STATE_LOCKED = "LOCKED" # When the task belongs to a different user.
|
||||
|
||||
"""Provides tools for processing workflows and tasks. This
|
||||
should at some point, be the only way to work with Workflows, and
|
||||
@ -94,11 +99,16 @@ class WorkflowService(object):
|
||||
processor.bpmn_workflow.do_engine_steps()
|
||||
tasks = processor.bpmn_workflow.get_tasks(SpiffTask.READY)
|
||||
for task in tasks:
|
||||
if task.task_spec.lane is not None and task.task_spec.lane not in task.data:
|
||||
raise ApiError.from_task("invalid_role",
|
||||
f"This task is in a lane called '{task.task_spec.lane}', The "
|
||||
f" current task data must have information mapping this role to "
|
||||
f" a unique user id.", task)
|
||||
task_api = WorkflowService.spiff_task_to_api_task(
|
||||
task,
|
||||
add_docs_and_forms=True) # Assure we try to process the documentation, and raise those errors.
|
||||
WorkflowService.populate_form_with_random_data(task, task_api, required_only)
|
||||
task.complete()
|
||||
processor.complete_task(task)
|
||||
except WorkflowException as we:
|
||||
WorkflowService.delete_test_data()
|
||||
raise ApiError.from_workflow_exception("workflow_validation_exception", str(we), we)
|
||||
@ -133,20 +143,37 @@ class WorkflowService(object):
|
||||
|
||||
@staticmethod
|
||||
def get_random_data_for_field(field, task):
|
||||
if field.type == "enum":
|
||||
has_ldap_lookup = field.has_property(Task.PROP_LDAP_LOOKUP)
|
||||
has_file_lookup = field.has_property(Task.PROP_OPTIONS_FILE_NAME)
|
||||
has_data_lookup = field.has_property(Task.PROP_OPTIONS_DATA_NAME)
|
||||
has_lookup = has_ldap_lookup or has_file_lookup or has_data_lookup
|
||||
|
||||
if field.type == "enum" and not has_lookup:
|
||||
# If it's a normal enum field with no lookup,
|
||||
# return a random option.
|
||||
if len(field.options) > 0:
|
||||
random_choice = random.choice(field.options)
|
||||
if isinstance(random_choice, dict):
|
||||
return random.choice(field.options)['id']
|
||||
choice = random.choice(field.options)
|
||||
return {
|
||||
'value': choice['id'],
|
||||
'label': choice['name']
|
||||
}
|
||||
else:
|
||||
# fixme: why it is sometimes an EnumFormFieldOption, and other times not?
|
||||
return random_choice.id ## Assume it is an EnumFormFieldOption
|
||||
# Assume it is an EnumFormFieldOption
|
||||
return {
|
||||
'value': random_choice.id,
|
||||
'label': random_choice.name
|
||||
}
|
||||
else:
|
||||
raise ApiError.from_task("invalid_enum", "You specified an enumeration field (%s),"
|
||||
" with no options" % field.id, task)
|
||||
elif field.type == "autocomplete":
|
||||
elif field.type == "autocomplete" or field.type == "enum":
|
||||
# If it has a lookup, get the lookup model from the spreadsheet or task data, then return a random option
|
||||
# from the lookup model
|
||||
lookup_model = LookupService.get_lookup_model(task, field)
|
||||
if field.has_property(Task.PROP_LDAP_LOOKUP): # All ldap records get the same person.
|
||||
if has_ldap_lookup: # All ldap records get the same person.
|
||||
return {
|
||||
"label": "dhf8r",
|
||||
"value": "Dan Funk",
|
||||
@ -162,9 +189,7 @@ class WorkflowService(object):
|
||||
elif lookup_model:
|
||||
data = db.session.query(LookupDataModel).filter(
|
||||
LookupDataModel.lookup_file_model == lookup_model).limit(10).all()
|
||||
options = []
|
||||
for d in data:
|
||||
options.append({"id": d.value, "label": d.label})
|
||||
options = [{"value": d.value, "label": d.label, "data": d.data} for d in data]
|
||||
return random.choice(options)
|
||||
else:
|
||||
raise ApiError.from_task("unknown_lookup_option", "The settings for this auto complete field "
|
||||
@ -197,13 +222,15 @@ class WorkflowService(object):
|
||||
possible, next_task is set to the current_task."""
|
||||
|
||||
nav_dict = processor.bpmn_workflow.get_nav_list()
|
||||
|
||||
# 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 ' ' in nav_item['title']:
|
||||
if nav_item['title'] is not None and ' ' in nav_item['title']:
|
||||
nav_item['title'] = nav_item['title'].partition(' ')[2]
|
||||
else:
|
||||
nav_item['title'] = ""
|
||||
@ -211,11 +238,13 @@ class WorkflowService(object):
|
||||
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 'user' not in g or not g.user or g.user.uid not in user_uids:
|
||||
nav_item['state'] = WorkflowService.TASK_STATE_LOCKED
|
||||
|
||||
else:
|
||||
nav_item['task'] = None
|
||||
|
||||
if not 'is_decision' in nav_item:
|
||||
nav_item['is_decision'] = False
|
||||
|
||||
navigation.append(NavigationItem(**nav_item))
|
||||
NavigationItemSchema().dump(nav_item)
|
||||
@ -241,7 +270,10 @@ class WorkflowService(object):
|
||||
previous_form_data = WorkflowService.get_previously_submitted_data(processor.workflow_model.id, next_task)
|
||||
DeepMerge.merge(next_task.data, previous_form_data)
|
||||
workflow_api.next_task = WorkflowService.spiff_task_to_api_task(next_task, add_docs_and_forms=True)
|
||||
|
||||
# Update the state of the task to locked if the current user does not own the task.
|
||||
user_uids = WorkflowService.get_users_assigned_to_task(processor, next_task)
|
||||
if 'user' not in g or not g.user or g.user.uid not in user_uids:
|
||||
workflow_api.next_task.state = WorkflowService.TASK_STATE_LOCKED
|
||||
return workflow_api
|
||||
|
||||
@staticmethod
|
||||
@ -299,11 +331,17 @@ class WorkflowService(object):
|
||||
for key, val in spiff_task.task_spec.extensions.items():
|
||||
props[key] = val
|
||||
|
||||
if hasattr(spiff_task.task_spec, 'lane'):
|
||||
lane = spiff_task.task_spec.lane
|
||||
else:
|
||||
lane = None
|
||||
|
||||
task = Task(spiff_task.id,
|
||||
spiff_task.task_spec.name,
|
||||
spiff_task.task_spec.description,
|
||||
task_type,
|
||||
spiff_task.get_state_name(),
|
||||
lane,
|
||||
None,
|
||||
"",
|
||||
{},
|
||||
@ -424,21 +462,50 @@ class WorkflowService(object):
|
||||
return options
|
||||
|
||||
@staticmethod
|
||||
def log_task_action(user_uid, workflow_model, spiff_task, action, version):
|
||||
def update_task_assignments(processor):
|
||||
"""For every upcoming user task, log a task action
|
||||
that connects the assigned user(s) to that task. All
|
||||
existing assignment actions for this workflow are removed from the database,
|
||||
so that only the current valid actions are available. update_task_assignments
|
||||
should be called whenever progress is made on a workflow."""
|
||||
db.session.query(TaskEventModel). \
|
||||
filter(TaskEventModel.workflow_id == processor.workflow_model.id). \
|
||||
filter(TaskEventModel.action == WorkflowService.TASK_ACTION_ASSIGNMENT).delete()
|
||||
|
||||
for task in processor.get_current_user_tasks():
|
||||
user_ids = WorkflowService.get_users_assigned_to_task(processor, task)
|
||||
for user_id in user_ids:
|
||||
WorkflowService.log_task_action(user_id, processor, task, WorkflowService.TASK_ACTION_ASSIGNMENT)
|
||||
|
||||
@staticmethod
|
||||
def get_users_assigned_to_task(processor, spiff_task):
|
||||
if not hasattr(spiff_task.task_spec, 'lane') or spiff_task.task_spec.lane is None:
|
||||
return [processor.workflow_model.study.user_uid]
|
||||
# todo: return a list of all users that can edit the study by default
|
||||
if spiff_task.task_spec.lane not in spiff_task.data:
|
||||
return [] # No users are assignable to the task at this moment
|
||||
lane_users = spiff_task.data[spiff_task.task_spec.lane]
|
||||
if not isinstance(lane_users, list):
|
||||
lane_users = [lane_users]
|
||||
return lane_users
|
||||
|
||||
@staticmethod
|
||||
def log_task_action(user_uid, processor, spiff_task, action):
|
||||
task = WorkflowService.spiff_task_to_api_task(spiff_task)
|
||||
form_data = WorkflowService.extract_form_data(spiff_task.data, spiff_task)
|
||||
task_event = TaskEventModel(
|
||||
study_id=workflow_model.study_id,
|
||||
study_id=processor.workflow_model.study_id,
|
||||
user_uid=user_uid,
|
||||
workflow_id=workflow_model.id,
|
||||
workflow_spec_id=workflow_model.workflow_spec_id,
|
||||
spec_version=version,
|
||||
workflow_id=processor.workflow_model.id,
|
||||
workflow_spec_id=processor.workflow_model.workflow_spec_id,
|
||||
spec_version=processor.get_version_string(),
|
||||
action=action,
|
||||
task_id=task.id,
|
||||
task_name=task.name,
|
||||
task_title=task.title,
|
||||
task_type=str(task.type),
|
||||
task_state=task.state,
|
||||
task_lane=task.lane,
|
||||
form_data=form_data,
|
||||
mi_type=task.multi_instance_type.value, # Some tasks have a repeat behavior.
|
||||
mi_count=task.multi_instance_count, # This is the number of times the task could repeat.
|
||||
|
@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" id="Definitions_0o0ff2r" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||
<decision id="decision_ind_check" name="IND Check">
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="IS_IND">
|
||||
<inputExpression id="inputExpression_1" typeRef="integer">
|
||||
<text>StudyInfo.details.IS_IND</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<input id="InputClause_1yk6kx1" label="IND_1 Number?">
|
||||
<inputExpression id="LiteralExpression_00xhtjw" typeRef="string">
|
||||
<text>StudyInfo.details.IND_1</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<input id="InputClause_069sith" label="IND_2 Number?">
|
||||
<inputExpression id="LiteralExpression_1h9kd8o" typeRef="string">
|
||||
<text>StudyInfo.details.IND_2</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<input id="InputClause_0d0vpur" label="IND_3 Number?">
|
||||
<inputExpression id="LiteralExpression_0zbsg01" typeRef="string">
|
||||
<text>StudyInfo.details.IND_3</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="Add Supplemental Data" name="ind_supplement" typeRef="boolean" />
|
||||
<rule id="DecisionRule_0h0od2e">
|
||||
<inputEntry id="UnaryTests_09ctq71">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1cub5pk">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0aubvru">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0rjeqez">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1we3duh">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_199dgpt">
|
||||
<inputEntry id="UnaryTests_1ec0msc">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0h3sj7g">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1ji4kgh">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_10gxrx9">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1fhlpya">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0teanii">
|
||||
<inputEntry id="UnaryTests_0akfjdp">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1c88e2t">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0zfrdlt">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_07drghr">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1i7dtia">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0m9aydp">
|
||||
<inputEntry id="UnaryTests_003n37j">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1fcaod2">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0hmnsvb">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0y6xian">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1wuhxz7">
|
||||
<text>false</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
</decisionTable>
|
||||
</decision>
|
||||
</definitions>
|
@ -1,127 +0,0 @@
|
||||
<?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_1e7871f" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||
<bpmn:process id="Process_04jm0bm" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_1dhb8f4</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1dhb8f4" sourceRef="StartEvent_1" targetRef="ScriptTask_1fn00ox" />
|
||||
<bpmn:endEvent id="EndEvent_1h89sl4">
|
||||
<bpmn:incoming>SequenceFlow_1yhv1qz</bpmn:incoming>
|
||||
<bpmn:incoming>SequenceFlow_1enco3g</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:scriptTask id="ScriptTask_1fn00ox" name="Load IRB Details">
|
||||
<bpmn:incoming>SequenceFlow_1dhb8f4</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1uzcl1f</bpmn:outgoing>
|
||||
<bpmn:script>StudyInfo details</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1uzcl1f" sourceRef="ScriptTask_1fn00ox" targetRef="Task_SupplementIDE" />
|
||||
<bpmn:exclusiveGateway id="ExclusiveGateway_1fib89p">
|
||||
<bpmn:incoming>SequenceFlow_1lazou8</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1yb1vma</bpmn:outgoing>
|
||||
<bpmn:outgoing>SequenceFlow_011l5xt</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1yb1vma" name="Yes" sourceRef="ExclusiveGateway_1fib89p" targetRef="UserTask_0a2dfa8">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">ind_supplement == True</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_011l5xt" name="No" sourceRef="ExclusiveGateway_1fib89p" targetRef="Task_NoIDE">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">ind_supplement == False</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:manualTask id="Task_NoIDE" name="IND But No Numbers">
|
||||
<bpmn:documentation>The use of an Investigational New Drug (IND) was indicated in Protocol Builder, but no IND number was entered. Please enter up to three numbers in the Supplemental section of Protocol Builder so supplemental information can be entered here.</bpmn:documentation>
|
||||
<bpmn:incoming>SequenceFlow_011l5xt</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1yhv1qz</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1lazou8" sourceRef="Task_SupplementIDE" targetRef="ExclusiveGateway_1fib89p" />
|
||||
<bpmn:businessRuleTask id="Task_SupplementIDE" name="Supplement IND?" camunda:decisionRef="decision_ind_check">
|
||||
<bpmn:incoming>SequenceFlow_1uzcl1f</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1lazou8</bpmn:outgoing>
|
||||
</bpmn:businessRuleTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1yhv1qz" sourceRef="Task_NoIDE" targetRef="EndEvent_1h89sl4" />
|
||||
<bpmn:userTask id="UserTask_0a2dfa8" name="Edit IND Info" camunda:formKey="FormKey_Details">
|
||||
<bpmn:documentation>IND No.: {{StudyInfo.details.IND_1}}</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="HolderType" label="IND Holder Type" type="enum">
|
||||
<camunda:value id="Industry" name="Industry" />
|
||||
<camunda:value id="UVaPI" name="UVa PI" />
|
||||
<camunda:value id="OtherPI" name="Other PI" />
|
||||
<camunda:value id="UVaCenter" name="UVaCenter" />
|
||||
<camunda:value id="OtherCollUniv" name="Other Colleges and Universities" />
|
||||
<camunda:value id="Exempt" name="IND Exempt" />
|
||||
<camunda:value id="NA" name="NA" />
|
||||
</camunda:formField>
|
||||
<camunda:formField id="HolderNameNotInList" label="IND Holder Name if not in above list" type="string" />
|
||||
<camunda:formField id="DrugBiologicName" label="Drug/Biologic Name" type="string" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_1yb1vma</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1enco3g</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1enco3g" sourceRef="UserTask_0a2dfa8" targetRef="EndEvent_1h89sl4" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_04jm0bm">
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1enco3g_di" bpmnElement="SequenceFlow_1enco3g">
|
||||
<di:waypoint x="810" y="117" />
|
||||
<di:waypoint x="932" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1yhv1qz_di" bpmnElement="SequenceFlow_1yhv1qz">
|
||||
<di:waypoint x="810" y="230" />
|
||||
<di:waypoint x="871" y="230" />
|
||||
<di:waypoint x="871" y="117" />
|
||||
<di:waypoint x="932" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1lazou8_di" bpmnElement="SequenceFlow_1lazou8">
|
||||
<di:waypoint x="510" y="117" />
|
||||
<di:waypoint x="585" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_011l5xt_di" bpmnElement="SequenceFlow_011l5xt">
|
||||
<di:waypoint x="610" y="142" />
|
||||
<di:waypoint x="610" y="230" />
|
||||
<di:waypoint x="710" y="230" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="618" y="183" width="15" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1yb1vma_di" bpmnElement="SequenceFlow_1yb1vma">
|
||||
<di:waypoint x="635" y="117" />
|
||||
<di:waypoint x="710" y="117" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="659" y="99" width="18" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1uzcl1f_di" bpmnElement="SequenceFlow_1uzcl1f">
|
||||
<di:waypoint x="360" y="117" />
|
||||
<di:waypoint x="410" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1dhb8f4_di" bpmnElement="SequenceFlow_1dhb8f4">
|
||||
<di:waypoint x="188" y="117" />
|
||||
<di:waypoint x="260" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="152" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_1h89sl4_di" bpmnElement="EndEvent_1h89sl4">
|
||||
<dc:Bounds x="932" y="99" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="414" y="202" width="74" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ScriptTask_1fn00ox_di" bpmnElement="ScriptTask_1fn00ox">
|
||||
<dc:Bounds x="260" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ExclusiveGateway_1fib89p_di" bpmnElement="ExclusiveGateway_1fib89p" isMarkerVisible="true">
|
||||
<dc:Bounds x="585" y="92" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ManualTask_1f7z9wm_di" bpmnElement="Task_NoIDE">
|
||||
<dc:Bounds x="710" y="190" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="BusinessRuleTask_1cszgkx_di" bpmnElement="Task_SupplementIDE">
|
||||
<dc:Bounds x="410" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_0a2dfa8_di" bpmnElement="UserTask_0a2dfa8">
|
||||
<dc:Bounds x="710" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
BIN
crc/static/bpmn/ind_update/SponsorList.xls
Normal file
BIN
crc/static/bpmn/ind_update/SponsorList.xls
Normal file
Binary file not shown.
220
crc/static/bpmn/ind_update/decision_ind_check.dmn
Normal file
220
crc/static/bpmn/ind_update/decision_ind_check.dmn
Normal file
@ -0,0 +1,220 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" id="Definitions_0o0ff2r" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||
<decision id="decision_ind_check" name="IND Check">
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="IS_IND">
|
||||
<inputExpression id="inputExpression_1" typeRef="integer">
|
||||
<text>StudyInfo.details.IS_IND</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<input id="InputClause_1yk6kx1" label="IND_1 Number?">
|
||||
<inputExpression id="LiteralExpression_00xhtjw" typeRef="string">
|
||||
<text>StudyInfo.details.IND_1</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<input id="InputClause_069sith" label="IND_2 Number?">
|
||||
<inputExpression id="LiteralExpression_1h9kd8o" typeRef="string">
|
||||
<text>StudyInfo.details.IND_2</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<input id="InputClause_0d0vpur" label="IND_3 Number?">
|
||||
<inputExpression id="LiteralExpression_0zbsg01" typeRef="string">
|
||||
<text>StudyInfo.details.IND_3</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="Add Supplemental Data" name="ind_supplement" typeRef="boolean" />
|
||||
<output id="OutputClause_0cfn42v" label="IND Count Entered" name="ind_cnt" typeRef="string" />
|
||||
<output id="OutputClause_0xcdkqm" label="IND Message" name="ind_message" typeRef="string" />
|
||||
<output id="OutputClause_08qk83g" label="IND 1 Field Value" name="IND1_Number" typeRef="string" />
|
||||
<rule id="DecisionRule_0teanii">
|
||||
<description>3 IND #s</description>
|
||||
<inputEntry id="UnaryTests_0akfjdp">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1c88e2t">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0zfrdlt">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_07drghr">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1i7dtia">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_0kulwlr">
|
||||
<text>"three"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1tw8tzn">
|
||||
<text>"Three IND #s entered"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1fiijih">
|
||||
<text></text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_199dgpt">
|
||||
<description>2 IND #s</description>
|
||||
<inputEntry id="UnaryTests_1ec0msc">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0h3sj7g">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1ji4kgh">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_10gxrx9">
|
||||
<text>""</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1fhlpya">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1h5mox1">
|
||||
<text>"two"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1nvcjhv">
|
||||
<text>"Two IND #s entered"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1rwd1ja">
|
||||
<text></text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0z0tcm0">
|
||||
<description>3 IND#s, missing #2</description>
|
||||
<inputEntry id="UnaryTests_1kf86r3">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0jm1wzq">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_14itgac">
|
||||
<text>""</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1prht5p">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_0pooubu">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_0nioovi">
|
||||
<text>"two"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1fa5e2o">
|
||||
<text>"Two IND #s entered"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1qul3vr">
|
||||
<text></text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0bwkqh7">
|
||||
<description>3 IND#s, missing #1</description>
|
||||
<inputEntry id="UnaryTests_13ig4fh">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_11kb6cw">
|
||||
<text>""</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0sfwtwo">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0xxmh5j">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_14otjle">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_13qodmm">
|
||||
<text>"two"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_0xhjgjn">
|
||||
<text>"Two IND #s entered"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_13g0u0n">
|
||||
<text></text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0h0od2e">
|
||||
<description>1 IND #</description>
|
||||
<inputEntry id="UnaryTests_09ctq71">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1cub5pk">
|
||||
<text>not('')</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0aubvru">
|
||||
<text>""</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0rjeqez">
|
||||
<text>""</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1we3duh">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1jv0san">
|
||||
<text>"one"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_19cvvhd">
|
||||
<text>"One IND # entered"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_15ikz7u">
|
||||
<text>StudyInfo.details.IND_1</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_1nitohs">
|
||||
<description>No</description>
|
||||
<inputEntry id="UnaryTests_19oot48">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0i2qyga">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_09wye05">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1g4y2ti">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_0c2mi3l">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1e2kzvw">
|
||||
<text>"na"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_0wj4zzb">
|
||||
<text>"No IND Numbers Entered in PB"</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_049iioi">
|
||||
<text>""</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0m9aydp">
|
||||
<description>No IND, PB Q#56 answered as No, should not be needed, but here as stopgap in case memu check failed</description>
|
||||
<inputEntry id="UnaryTests_003n37j">
|
||||
<text>0</text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_1fcaod2">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0hmnsvb">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<inputEntry id="UnaryTests_0y6xian">
|
||||
<text></text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1wuhxz7">
|
||||
<text>false</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1dznftw">
|
||||
<text></text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1lbt5oy">
|
||||
<text></text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_0tkt63s">
|
||||
<text></text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
</decisionTable>
|
||||
</decision>
|
||||
</definitions>
|
276
crc/static/bpmn/ind_update/ind_update.bpmn
Normal file
276
crc/static/bpmn/ind_update/ind_update.bpmn
Normal file
@ -0,0 +1,276 @@
|
||||
<?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_1e7871f" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.0.0">
|
||||
<bpmn:process id="Process_04jm0bm" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_1dhb8f4</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1dhb8f4" sourceRef="StartEvent_1" targetRef="ScriptTask_LoadIRBDetails" />
|
||||
<bpmn:endEvent id="EndEvent_1h89sl4">
|
||||
<bpmn:incoming>Flow_0jqdolk</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_OneOnly</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:scriptTask id="ScriptTask_LoadIRBDetails" name="Load IRB Details">
|
||||
<bpmn:incoming>SequenceFlow_1dhb8f4</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1uzcl1f</bpmn:outgoing>
|
||||
<bpmn:script>StudyInfo details</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1uzcl1f" sourceRef="ScriptTask_LoadIRBDetails" targetRef="Task_SupplementIDE" />
|
||||
<bpmn:businessRuleTask id="Task_SupplementIDE" name="Current IND Status" camunda:decisionRef="decision_ind_check">
|
||||
<bpmn:incoming>SequenceFlow_1uzcl1f</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1cwibmt</bpmn:outgoing>
|
||||
</bpmn:businessRuleTask>
|
||||
<bpmn:userTask id="IND_n1_info" name="Edit IND #1 Info" camunda:formKey="IND1_Info">
|
||||
<bpmn:documentation>IND No.: {{ StudyInfo.details.IND_1 }}</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="IND1_Number" label="IND1 number:" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="description" value="Enter IND number, if available." />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND1_HolderType" label="IND Holder Type" type="enum">
|
||||
<camunda:value id="Industry" name="Industry" />
|
||||
<camunda:value id="UVaPI" name="UVa PI" />
|
||||
<camunda:value id="OtherPI" name="Other PI" />
|
||||
<camunda:value id="UVaCenter" name="UVaCenter" />
|
||||
<camunda:value id="OtherCollUniv" name="Other Colleges and Universities" />
|
||||
<camunda:value id="Exempt" name="IND Exempt" />
|
||||
<camunda:value id="NA" name="NA" />
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND1_HolderName" label="Holder Name" type="autocomplete">
|
||||
<camunda:properties>
|
||||
<camunda:property id="spreadsheet.name" value="SponsorList.xls" />
|
||||
<camunda:property id="spreadsheet.value.column" value="CUSTOMER_NUMBER" />
|
||||
<camunda:property id="spreadsheet.label.column" value="CUSTOMER_NAME" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND1_HolderNameNotInList" label="IND Holder Name if not in above list" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="model.IND1_HolderName && model.IND1_HolderName.value !== "0"" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND1_DrugBiologicName" label="Drug/Biologic Name" type="string" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1bn0jp7</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_10rb7gb</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:userTask id="IND_n2_info" name="Edit IND #2 Info" camunda:formKey="IND2_Info">
|
||||
<bpmn:documentation>IND No.:</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="IND2_Status" label="Do you have a second Investigational New Drug?" type="enum">
|
||||
<camunda:value id="Yes" name="Yes" />
|
||||
<camunda:value id="YesBut" name="Yes, but number is not available at this time" />
|
||||
<camunda:value id="No" name="No" />
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND2_Number" label="IND2 Number:" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="value_expression" value="model.StudyInfo.details.IND_2" />
|
||||
<camunda:property id="hide_expression" value="!model.IND2_Status || !model.IND2_Status.value || model.IND2_Status.value === 'No'" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND2_HolderType" label="IND Holder Type" type="enum">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="!model.IND2_Status || !model.IND2_Status.value || model.IND2_Status.value === 'No'" />
|
||||
</camunda:properties>
|
||||
<camunda:value id="Industry" name="Industry" />
|
||||
<camunda:value id="UVaPI" name="UVa PI" />
|
||||
<camunda:value id="OtherPI" name="Other PI" />
|
||||
<camunda:value id="UVaCenter" name="UVaCenter" />
|
||||
<camunda:value id="OtherCollUniv" name="Other Colleges and Universities" />
|
||||
<camunda:value id="Exempt" name="IND Exempt" />
|
||||
<camunda:value id="NA" name="NA" />
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND2_HolderName" label="Holder Name" type="autocomplete">
|
||||
<camunda:properties>
|
||||
<camunda:property id="spreadsheet.name" value="SponsorList.xls" />
|
||||
<camunda:property id="spreadsheet.value.column" value="CUSTOMER_NUMBER" />
|
||||
<camunda:property id="spreadsheet.label.column" value="CUSTOMER_NAME" />
|
||||
<camunda:property id="hide_expression" value="!model.IND2_Status || !model.IND2_Status.value || model.IND2_Status.value === 'No'" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND2_HolderNameNotInList" label="IND Holder Name if not in above list" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="!model.IND2_Status || !model.IND2_Status.value || model.IND2_Status.value === 'No' || model.IND2_HolderName.value !== "0"" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND2_DrugBiologicName" label="Drug/Biologic Name" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="!model.IND2_Status || !model.IND2_Status.value || model.IND2_Status.value === 'No'" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_TwoOrThree</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1p563xr</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:userTask id="IND_n3_info" name="Edit IND #3 Info" camunda:formKey="IND3_Info">
|
||||
<bpmn:documentation>IND No.:</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="IND3_Status" label="Do you have a third Investigational New Drug?" type="enum" defaultValue="No">
|
||||
<camunda:value id="Yes" name="Yes" />
|
||||
<camunda:value id="YesBut" name="Yes, but number is not available at this time." />
|
||||
<camunda:value id="No" name="No" />
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND3_Number" label="IND3 Number:" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="value_expression" value="model.StudyInfo.details.IND_3" />
|
||||
<camunda:property id="hide_expression" value="!model.IND3_Status || !model.IND3_Status.value || model.IND3_Status.value === 'No'" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND3_HolderType" label="IND Holder Type" type="enum">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="!model.IND3_Status || !model.IND3_Status.value || model.IND3_Status.value === 'No'" />
|
||||
</camunda:properties>
|
||||
<camunda:value id="Industry" name="Industry" />
|
||||
<camunda:value id="UVaPI" name="UVa PI" />
|
||||
<camunda:value id="OtherPI" name="Other PI" />
|
||||
<camunda:value id="UVaCenter" name="UVaCenter" />
|
||||
<camunda:value id="OtherCollUniv" name="Other Colleges and Universities" />
|
||||
<camunda:value id="Exempt" name="IND Exempt" />
|
||||
<camunda:value id="NA" name="NA" />
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND3_HolderName" label="Holder Name" type="autocomplete">
|
||||
<camunda:properties>
|
||||
<camunda:property id="spreadsheet.name" value="SponsorList.xls" />
|
||||
<camunda:property id="spreadsheet.value.column" value="CUSTOMER_NUMBER" />
|
||||
<camunda:property id="spreadsheet.label.column" value="CUSTOMER_NAME" />
|
||||
<camunda:property id="hide_expression" value="!model.IND3_Status || !model.IND3_Status.value || model.IND3_Status.value === 'No'" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND3_HolderNameNotInList" label="IND Holder Name if not in above list" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="!model.IND3_Status || !model.IND3_Status.value || model.IND3_Status.value === 'No' || model.IND3_HolderName.value !== "0"" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
<camunda:formField id="IND3_DrugBiologicName" label="Drug/Biologic Name" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="hide_expression" value="!model.IND3_Status || !model.IND3_Status.value || model.IND3_Status.value === 'No'" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1p563xr</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0jqdolk</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1cwibmt" sourceRef="Task_SupplementIDE" targetRef="Activity_0yf2ypo" />
|
||||
<bpmn:userTask id="Activity_0yf2ypo" name="Provide IND Count" camunda:formKey="IND_Count">
|
||||
<bpmn:documentation>{{ ind_message }}</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="IND_CntEntered" label="How Many?" type="enum" defaultValue="one">
|
||||
<camunda:validation>
|
||||
<camunda:constraint name="required" config="true" />
|
||||
</camunda:validation>
|
||||
<camunda:value id="value_one" name="1 IND number" />
|
||||
<camunda:value id="value_two" name="2 IND number" />
|
||||
<camunda:value id="value_three" name="3 IND number" />
|
||||
<camunda:value id="value_na" name="No IND Numbers in PB" />
|
||||
</camunda:formField>
|
||||
<camunda:formField id="FormField_0h8vmid" label="Test" type="string">
|
||||
<camunda:properties>
|
||||
<camunda:property id="value_expression" value="model.ind_cnt" />
|
||||
</camunda:properties>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_1cwibmt</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1bn0jp7</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_1bn0jp7" sourceRef="Activity_0yf2ypo" targetRef="IND_n1_info" />
|
||||
<bpmn:sequenceFlow id="Flow_1p563xr" sourceRef="IND_n2_info" targetRef="IND_n3_info" />
|
||||
<bpmn:sequenceFlow id="Flow_0jqdolk" sourceRef="IND_n3_info" targetRef="EndEvent_1h89sl4" />
|
||||
<bpmn:sequenceFlow id="Flow_10rb7gb" sourceRef="IND_n1_info" targetRef="Gateway_0ckycp9" />
|
||||
<bpmn:exclusiveGateway id="Gateway_0ckycp9">
|
||||
<bpmn:incoming>Flow_10rb7gb</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_TwoOrThree</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_OneOnly</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:sequenceFlow id="Flow_TwoOrThree" name="Two or Three INDs" sourceRef="Gateway_0ckycp9" targetRef="IND_n2_info">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">IND_CntEntered != "value_one"</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_OneOnly" name="One IND" sourceRef="Gateway_0ckycp9" targetRef="EndEvent_1h89sl4">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">IND_CntEntered == "value_one"</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_04jm0bm">
|
||||
<bpmndi:BPMNEdge id="Flow_00n2n7p_di" bpmnElement="Flow_OneOnly">
|
||||
<di:waypoint x="940" y="142" />
|
||||
<di:waypoint x="940" y="260" />
|
||||
<di:waypoint x="1510" y="260" />
|
||||
<di:waypoint x="1510" y="135" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1205" y="242" width="43" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1o2u7k3_di" bpmnElement="Flow_TwoOrThree">
|
||||
<di:waypoint x="965" y="117" />
|
||||
<di:waypoint x="1070" y="117" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="987" y="86" width="65" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_10rb7gb_di" bpmnElement="Flow_10rb7gb">
|
||||
<di:waypoint x="860" y="117" />
|
||||
<di:waypoint x="915" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0jqdolk_di" bpmnElement="Flow_0jqdolk">
|
||||
<di:waypoint x="1380" y="117" />
|
||||
<di:waypoint x="1492" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1p563xr_di" bpmnElement="Flow_1p563xr">
|
||||
<di:waypoint x="1170" y="117" />
|
||||
<di:waypoint x="1280" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1bn0jp7_di" bpmnElement="Flow_1bn0jp7">
|
||||
<di:waypoint x="670" y="117" />
|
||||
<di:waypoint x="760" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1cwibmt_di" bpmnElement="SequenceFlow_1cwibmt">
|
||||
<di:waypoint x="520" y="117" />
|
||||
<di:waypoint x="570" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1uzcl1f_di" bpmnElement="SequenceFlow_1uzcl1f">
|
||||
<di:waypoint x="340" y="117" />
|
||||
<di:waypoint x="420" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1dhb8f4_di" bpmnElement="SequenceFlow_1dhb8f4">
|
||||
<di:waypoint x="188" y="117" />
|
||||
<di:waypoint x="240" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="152" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_1h89sl4_di" bpmnElement="EndEvent_1h89sl4">
|
||||
<dc:Bounds x="1492" y="99" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="414" y="202" width="74" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ScriptTask_1fn00ox_di" bpmnElement="ScriptTask_LoadIRBDetails">
|
||||
<dc:Bounds x="240" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="BusinessRuleTask_1cszgkx_di" bpmnElement="Task_SupplementIDE">
|
||||
<dc:Bounds x="420" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_0a2dfa8_di" bpmnElement="IND_n1_info">
|
||||
<dc:Bounds x="760" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_1smni98_di" bpmnElement="IND_n2_info">
|
||||
<dc:Bounds x="1070" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="UserTask_1378hd8_di" bpmnElement="IND_n3_info">
|
||||
<dc:Bounds x="1280" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0wfey2b_di" bpmnElement="Activity_0yf2ypo">
|
||||
<dc:Bounds x="570" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_0ckycp9_di" bpmnElement="Gateway_0ckycp9" isMarkerVisible="true">
|
||||
<dc:Bounds x="915" y="92" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -93,8 +93,8 @@ class ExampleDataLoader:
|
||||
description="Supplemental information for the IDE number entered in Protocol Builder",
|
||||
category_id=0,
|
||||
display_order=3)
|
||||
self.create_spec(id="ind_supplement",
|
||||
name="ind_supplement",
|
||||
self.create_spec(id="ind_update",
|
||||
name="ind_update",
|
||||
display_name="IND Supplement Info",
|
||||
description="Supplement information for the Investigational New Drug(s) specified in Protocol Builder",
|
||||
category_id=0,
|
||||
|
38
migrations/versions/ffef4661a37d_.py
Normal file
38
migrations/versions/ffef4661a37d_.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: ffef4661a37d
|
||||
Revises: 5acd138e969c
|
||||
Create Date: 2020-07-14 19:52:05.270939
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ffef4661a37d'
|
||||
down_revision = '5acd138e969c'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('task_event', sa.Column('task_lane', sa.String(), nullable=True))
|
||||
op.drop_constraint('task_event_user_uid_fkey', 'task_event', type_='foreignkey')
|
||||
op.execute("update task_event set action = 'COMPLETE' where action='Complete'")
|
||||
op.execute("update task_event set action = 'TOKEN_RESET' where action='Backwards Move'")
|
||||
op.execute("update task_event set action = 'HARD_RESET' where action='Restart (Hard)'")
|
||||
op.execute("update task_event set action = 'SOFT_RESET' where action='Restart (Soft)'")
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_foreign_key('task_event_user_uid_fkey', 'task_event', 'user', ['user_uid'], ['uid'])
|
||||
op.drop_column('task_event', 'task_lane')
|
||||
op.execute("update task_event set action = 'Complete' where action='COMPLETE'")
|
||||
op.execute("update task_event set action = 'Backwards Move' where action='TOKEN_RESET'")
|
||||
op.execute("update task_event set action = 'Restart (Hard)' where action='HARD_RESET'")
|
||||
op.execute("update task_event set action = 'Restart (Soft)' where action='SOFT_RESET'")
|
||||
# ### end Alembic commands ###
|
@ -16,7 +16,7 @@ from crc.models.api_models import WorkflowApiSchema, MultiInstanceType
|
||||
from crc.models.approval import ApprovalModel, ApprovalStatus
|
||||
from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.user import UserModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel
|
||||
@ -230,7 +230,7 @@ class BaseTest(unittest.TestCase):
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
def create_study(self, uid="dhf8r", title="Beer conception in the bipedal software engineer", primary_investigator_id="lb3dp"):
|
||||
def create_study(self, uid="dhf8r", title="Beer consumption in the bipedal software engineer", primary_investigator_id="lb3dp"):
|
||||
study = session.query(StudyModel).filter_by(user_uid=uid).filter_by(title=title).first()
|
||||
if study is None:
|
||||
user = self.create_user(uid=uid)
|
||||
@ -263,13 +263,13 @@ class BaseTest(unittest.TestCase):
|
||||
|
||||
return full_study
|
||||
|
||||
def create_workflow(self, workflow_name, study=None, category_id=None):
|
||||
def create_workflow(self, workflow_name, study=None, category_id=None, as_user="dhf8r"):
|
||||
db.session.flush()
|
||||
spec = db.session.query(WorkflowSpecModel).filter(WorkflowSpecModel.name == workflow_name).first()
|
||||
if spec is None:
|
||||
spec = self.load_test_spec(workflow_name, category_id=category_id)
|
||||
if study is None:
|
||||
study = self.create_study()
|
||||
study = self.create_study(uid=as_user)
|
||||
workflow_model = StudyService._create_workflow_model(study, spec)
|
||||
return workflow_model
|
||||
|
||||
@ -313,6 +313,7 @@ class BaseTest(unittest.TestCase):
|
||||
self.assertEqual(workflow.workflow_spec_id, workflow_api.workflow_spec_id)
|
||||
return workflow_api
|
||||
|
||||
|
||||
def complete_form(self, workflow_in, task_in, dict_data, error_code=None, terminate_loop=None, user_uid="dhf8r"):
|
||||
prev_completed_task_count = workflow_in.completed_tasks
|
||||
if isinstance(task_in, dict):
|
||||
@ -339,7 +340,7 @@ class BaseTest(unittest.TestCase):
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
|
||||
# Assure stats are updated on the model
|
||||
# Assure task events are updated on the model
|
||||
workflow = WorkflowApiSchema().load(json_data)
|
||||
# The total number of tasks may change over time, as users move through gateways
|
||||
# branches may be pruned. As we hit parallel Multi-Instance new tasks may be created...
|
||||
@ -352,6 +353,7 @@ class BaseTest(unittest.TestCase):
|
||||
task_events = session.query(TaskEventModel) \
|
||||
.filter_by(workflow_id=workflow.id) \
|
||||
.filter_by(task_id=task_id) \
|
||||
.filter_by(action=WorkflowService.TASK_ACTION_COMPLETE) \
|
||||
.order_by(TaskEventModel.date.desc()).all()
|
||||
self.assertGreater(len(task_events), 0)
|
||||
event = task_events[0]
|
||||
|
177
tests/data/invalid_roles/invalid_roles.bpmn
Normal file
177
tests/data/invalid_roles/invalid_roles.bpmn
Normal file
@ -0,0 +1,177 @@
|
||||
<?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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0ybr9ph" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||
<bpmn:collaboration id="Collaboration_0xjb3la">
|
||||
<bpmn:participant id="Participant_0ozb2sp" processRef="Process_1aebbrh" />
|
||||
</bpmn:collaboration>
|
||||
<bpmn:process id="Process_1aebbrh" isExecutable="true">
|
||||
<bpmn:laneSet id="LaneSet_0ilprw6">
|
||||
<bpmn:lane id="Lane_1s1s7a1">
|
||||
<bpmn:flowNodeRef>StartEvent_1</bpmn:flowNodeRef>
|
||||
<bpmn:flowNodeRef>Activity_1hljoeq</bpmn:flowNodeRef>
|
||||
<bpmn:flowNodeRef>Event_0lscajc</bpmn:flowNodeRef>
|
||||
<bpmn:flowNodeRef>Activity_19ccxoj</bpmn:flowNodeRef>
|
||||
</bpmn:lane>
|
||||
<bpmn:lane id="Lane_1m47545" name="supervisor">
|
||||
<bpmn:flowNodeRef>Gateway_1fkgc4u</bpmn:flowNodeRef>
|
||||
<bpmn:flowNodeRef>Activity_14eor1x</bpmn:flowNodeRef>
|
||||
</bpmn:lane>
|
||||
</bpmn:laneSet>
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0a7090c</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:userTask id="Activity_1hljoeq" name="Request Approval" camunda:formKey="form">
|
||||
<bpmn:documentation># Answer me these questions 3, ere the other side you see!</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="favorite_color" label="What is your favorite color?" type="string" defaultValue="Yellow" />
|
||||
<camunda:formField id="quest" label="What is your quest?" type="string" defaultValue="To seek the holly Grail!" />
|
||||
<camunda:formField id="swallow_speed" label="What is the air speed velocity of an unladen swallow?" defaultValue="About 24 miles per hour" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0a7090c</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_070gq5r</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1hcpt7c</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:exclusiveGateway id="Gateway_1fkgc4u">
|
||||
<bpmn:incoming>Flow_1gp4zfd</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0vnghsi</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_1g38q6b</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:endEvent id="Event_0lscajc">
|
||||
<bpmn:documentation># Your responses were approved!
|
||||
|
||||
|
||||
Gosh! you must really know a lot about colors and swallows and stuff!
|
||||
Your supervisor provided the following feedback:
|
||||
|
||||
|
||||
{{feedback}}
|
||||
|
||||
|
||||
You are all done! WARNING: If you go back and reanswer the questions it will create a new approval request.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_1g38q6b</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:manualTask id="Activity_19ccxoj" name="Review Feedback">
|
||||
<bpmn:documentation># Your Request was rejected
|
||||
|
||||
|
||||
Perhaps you don't know the right answer to one of the questions.
|
||||
Your Supervisor provided the following feedback:
|
||||
|
||||
|
||||
{{feedback}}
|
||||
|
||||
|
||||
Please press save to re-try the questions, and submit your responses again.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0vnghsi</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_070gq5r</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:userTask id="Activity_14eor1x" name="Approve Responses" camunda:formKey="form2">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="approval" label="I approve of this information" type="boolean" defaultValue="false" />
|
||||
<camunda:formField id="feedback" label="Feedback" type="string" defaultValue="Please provide any feedback you have here." />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1hcpt7c</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1gp4zfd</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_0a7090c" sourceRef="StartEvent_1" targetRef="Activity_1hljoeq" />
|
||||
<bpmn:sequenceFlow id="Flow_1gp4zfd" sourceRef="Activity_14eor1x" targetRef="Gateway_1fkgc4u" />
|
||||
<bpmn:sequenceFlow id="Flow_0vnghsi" name="rejected" sourceRef="Gateway_1fkgc4u" targetRef="Activity_19ccxoj">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">approval==True</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_1g38q6b" name="approved" sourceRef="Gateway_1fkgc4u" targetRef="Event_0lscajc">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">approval==True</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_1hcpt7c" sourceRef="Activity_1hljoeq" targetRef="Activity_14eor1x" />
|
||||
<bpmn:sequenceFlow id="Flow_070gq5r" sourceRef="Activity_19ccxoj" targetRef="Activity_1hljoeq" />
|
||||
<bpmn:textAnnotation id="TextAnnotation_1ys83yq">
|
||||
<bpmn:text>Removed a field that would set the supervisor, making this not validate.</bpmn:text>
|
||||
</bpmn:textAnnotation>
|
||||
<bpmn:association id="Association_1kcb9ou" sourceRef="Activity_1hljoeq" targetRef="TextAnnotation_1ys83yq" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0xjb3la">
|
||||
<bpmndi:BPMNShape id="Participant_0ozb2sp_di" bpmnElement="Participant_0ozb2sp" isHorizontal="true">
|
||||
<dc:Bounds x="190" y="80" width="550" height="370" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Lane_1s1s7a1_di" bpmnElement="Lane_1s1s7a1" isHorizontal="true">
|
||||
<dc:Bounds x="220" y="80" width="520" height="245" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Lane_1m47545_di" bpmnElement="Lane_1m47545" isHorizontal="true">
|
||||
<dc:Bounds x="220" y="325" width="520" height="125" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_1ys83yq_di" bpmnElement="TextAnnotation_1ys83yq">
|
||||
<dc:Bounds x="250" y="100" width="130.6238034460753" height="68.28334396936822" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_0a7090c_di" bpmnElement="Flow_0a7090c">
|
||||
<di:waypoint x="276" y="260" />
|
||||
<di:waypoint x="330" y="260" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1gp4zfd_di" bpmnElement="Flow_1gp4zfd">
|
||||
<di:waypoint x="430" y="390" />
|
||||
<di:waypoint x="485" y="390" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0vnghsi_di" bpmnElement="Flow_0vnghsi">
|
||||
<di:waypoint x="510" y="365" />
|
||||
<di:waypoint x="510" y="300" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="520" y="334" width="40" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1g38q6b_di" bpmnElement="Flow_1g38q6b">
|
||||
<di:waypoint x="535" y="390" />
|
||||
<di:waypoint x="680" y="390" />
|
||||
<di:waypoint x="680" y="278" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="585" y="372" width="46" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1hcpt7c_di" bpmnElement="Flow_1hcpt7c">
|
||||
<di:waypoint x="380" y="300" />
|
||||
<di:waypoint x="380" y="350" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_070gq5r_di" bpmnElement="Flow_070gq5r">
|
||||
<di:waypoint x="510" y="220" />
|
||||
<di:waypoint x="510" y="160" />
|
||||
<di:waypoint x="380" y="160" />
|
||||
<di:waypoint x="380" y="220" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="240" y="242" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0xcxw40_di" bpmnElement="Activity_1hljoeq">
|
||||
<dc:Bounds x="330" y="220" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1fkgc4u_di" bpmnElement="Gateway_1fkgc4u" isMarkerVisible="true">
|
||||
<dc:Bounds x="485" y="365" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_0lscajc_di" bpmnElement="Event_0lscajc">
|
||||
<dc:Bounds x="662" y="242" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1jfdeta_di" bpmnElement="Activity_19ccxoj">
|
||||
<dc:Bounds x="460" y="220" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0zc7cgy_di" bpmnElement="Activity_14eor1x">
|
||||
<dc:Bounds x="330" y="350" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Association_1kcb9ou_di" bpmnElement="Association_1kcb9ou">
|
||||
<di:waypoint x="359" y="220" />
|
||||
<di:waypoint x="333" y="168" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -1,155 +1,124 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"attributes": {
|
||||
"cn": [
|
||||
"Laura Barnes (lb3dp)"
|
||||
],
|
||||
"displayName": "Laura Barnes",
|
||||
"givenName": [
|
||||
"Laura"
|
||||
],
|
||||
"mail": [
|
||||
"lb3dp@virginia.edu"
|
||||
],
|
||||
"objectClass": [
|
||||
"top",
|
||||
"person",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"uvaPerson",
|
||||
"uidObject"
|
||||
],
|
||||
"telephoneNumber": [
|
||||
"+1 (434) 924-1723"
|
||||
],
|
||||
"title": [
|
||||
"E0:Associate Professor of Systems and Information Engineering"
|
||||
],
|
||||
"uvaDisplayDepartment": [
|
||||
"E0:EN-Eng Sys and Environment"
|
||||
],
|
||||
"uvaPersonIAMAffiliation": [
|
||||
"faculty"
|
||||
],
|
||||
"uvaPersonSponsoredType": [
|
||||
"Staff"
|
||||
]
|
||||
},
|
||||
"dn": "uid=lb3dp,ou=People,o=University of Virginia,c=US",
|
||||
"raw": {
|
||||
"cn": [
|
||||
"Laura Barnes (lb3dp)"
|
||||
],
|
||||
"displayName": [
|
||||
"Laura Barnes"
|
||||
],
|
||||
"givenName": [
|
||||
"Laura"
|
||||
],
|
||||
"mail": [
|
||||
"lb3dp@virginia.edu"
|
||||
],
|
||||
"objectClass": [
|
||||
"top",
|
||||
"person",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"uvaPerson",
|
||||
"uidObject"
|
||||
],
|
||||
"telephoneNumber": [
|
||||
"+1 (434) 924-1723"
|
||||
],
|
||||
"title": [
|
||||
"E0:Associate Professor of Systems and Information Engineering"
|
||||
],
|
||||
"uvaDisplayDepartment": [
|
||||
"E0:EN-Eng Sys and Environment"
|
||||
],
|
||||
"uvaPersonIAMAffiliation": [
|
||||
"faculty"
|
||||
],
|
||||
"uvaPersonSponsoredType": [
|
||||
"Staff"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"cn": [
|
||||
"Dan Funk (dhf8r)"
|
||||
],
|
||||
"displayName": "Dan Funk",
|
||||
"givenName": [
|
||||
"Dan"
|
||||
],
|
||||
"mail": [
|
||||
"dhf8r@virginia.edu"
|
||||
],
|
||||
"objectClass": [
|
||||
"top",
|
||||
"person",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"uvaPerson",
|
||||
"uidObject"
|
||||
],
|
||||
"telephoneNumber": [
|
||||
"+1 (434) 924-1723"
|
||||
],
|
||||
"title": [
|
||||
"E42:He's a hoopy frood"
|
||||
],
|
||||
"uvaDisplayDepartment": [
|
||||
"E0:EN-Eng Study of Parallel Universes"
|
||||
],
|
||||
"uvaPersonIAMAffiliation": [
|
||||
"faculty"
|
||||
],
|
||||
"uvaPersonSponsoredType": [
|
||||
"Staff"
|
||||
]
|
||||
},
|
||||
"dn": "uid=dhf8r,ou=People,o=University of Virginia,c=US",
|
||||
"raw": {
|
||||
"cn": [
|
||||
"Dan Funk (dhf84)"
|
||||
],
|
||||
"displayName": [
|
||||
"Dan Funk"
|
||||
],
|
||||
"givenName": [
|
||||
"Dan"
|
||||
],
|
||||
"mail": [
|
||||
"dhf8r@virginia.edu"
|
||||
],
|
||||
"objectClass": [
|
||||
"top",
|
||||
"person",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"uvaPerson",
|
||||
"uidObject"
|
||||
],
|
||||
"telephoneNumber": [
|
||||
"+1 (434) 924-1723"
|
||||
],
|
||||
"title": [
|
||||
"E42:He's a hoopy frood"
|
||||
],
|
||||
"uvaDisplayDepartment": [
|
||||
"E0:EN-Eng Study of Parallel Universes"
|
||||
],
|
||||
"uvaPersonIAMAffiliation": [
|
||||
"faculty"
|
||||
],
|
||||
"uvaPersonSponsoredType": [
|
||||
"Staff"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
"entries": [
|
||||
{
|
||||
"dn": "uid=lb3dp,ou=People,o=University of Virginia,c=US",
|
||||
"raw": {
|
||||
"cn": [
|
||||
"Laura Barnes (lb3dp)"
|
||||
],
|
||||
"displayName": [
|
||||
"Laura Barnes"
|
||||
],
|
||||
"givenName": [
|
||||
"Laura"
|
||||
],
|
||||
"mail": [
|
||||
"lb3dp@virginia.edu"
|
||||
],
|
||||
"objectClass": [
|
||||
"top",
|
||||
"person",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"uvaPerson",
|
||||
"uidObject"
|
||||
],
|
||||
"telephoneNumber": [
|
||||
"+1 (434) 924-1723"
|
||||
],
|
||||
"title": [
|
||||
"E0:Associate Professor of Systems and Information Engineering"
|
||||
],
|
||||
"uvaDisplayDepartment": [
|
||||
"E0:EN-Eng Sys and Environment"
|
||||
],
|
||||
"uvaPersonIAMAffiliation": [
|
||||
"faculty"
|
||||
],
|
||||
"uvaPersonSponsoredType": [
|
||||
"Staff"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"dn": "uid=dhf8r,ou=People,o=University of Virginia,c=US",
|
||||
"raw": {
|
||||
"cn": [
|
||||
"Dan Funk (dhf84)"
|
||||
],
|
||||
"displayName": [
|
||||
"Dan Funk"
|
||||
],
|
||||
"givenName": [
|
||||
"Dan"
|
||||
],
|
||||
"mail": [
|
||||
"dhf8r@virginia.edu"
|
||||
],
|
||||
"objectClass": [
|
||||
"top",
|
||||
"person",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"uvaPerson",
|
||||
"uidObject"
|
||||
],
|
||||
"telephoneNumber": [
|
||||
"+1 (434) 924-1723"
|
||||
],
|
||||
"title": [
|
||||
"E42:He's a hoopy frood"
|
||||
],
|
||||
"uvaDisplayDepartment": [
|
||||
"E0:EN-Eng Study of Parallel Universes"
|
||||
],
|
||||
"uvaPersonIAMAffiliation": [
|
||||
"faculty"
|
||||
],
|
||||
"uvaPersonSponsoredType": [
|
||||
"Staff"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"dn": "uid=lje5u,ou=People,o=University of Virginia,c=US",
|
||||
"raw": {
|
||||
"cn": [
|
||||
"Elder, Lori J (lje5u)"
|
||||
],
|
||||
"displayName": [
|
||||
"Lori Elder"
|
||||
],
|
||||
"givenName": [
|
||||
"Lori"
|
||||
],
|
||||
"mail": [
|
||||
"lje5u@virginia.edu"
|
||||
],
|
||||
"objectClass": [
|
||||
"top",
|
||||
"person",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"uvaPerson",
|
||||
"uidObject"
|
||||
],
|
||||
"telephoneNumber": [
|
||||
"+1 (434) 924-1723"
|
||||
],
|
||||
"title": [
|
||||
"E42:The vision"
|
||||
],
|
||||
"uvaDisplayDepartment": [
|
||||
"E0:EN-Phy Anything could go here."
|
||||
],
|
||||
"uvaPersonIAMAffiliation": [
|
||||
"faculty"
|
||||
],
|
||||
"uvaPersonSponsoredType": [
|
||||
"Staff"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
155
tests/data/roles/roles.bpmn
Normal file
155
tests/data/roles/roles.bpmn
Normal file
@ -0,0 +1,155 @@
|
||||
<?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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0ybr9ph" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.0.0">
|
||||
<bpmn:collaboration id="Collaboration_0xjb3la">
|
||||
<bpmn:participant id="Participant_0ozb2sp" name="pool_name" processRef="Process_1aebbrh" />
|
||||
</bpmn:collaboration>
|
||||
<bpmn:process id="Process_1aebbrh" isExecutable="true">
|
||||
<bpmn:laneSet id="LaneSet_0ilprw6">
|
||||
<bpmn:lane id="Lane_1s1s7a1">
|
||||
<bpmn:flowNodeRef>StartEvent_1</bpmn:flowNodeRef>
|
||||
<bpmn:flowNodeRef>Activity_1hljoeq</bpmn:flowNodeRef>
|
||||
<bpmn:flowNodeRef>Event_0lscajc</bpmn:flowNodeRef>
|
||||
<bpmn:flowNodeRef>Activity_19ccxoj</bpmn:flowNodeRef>
|
||||
</bpmn:lane>
|
||||
<bpmn:lane id="Lane_1m47545" name="supervisor">
|
||||
<bpmn:flowNodeRef>Gateway_1fkgc4u</bpmn:flowNodeRef>
|
||||
<bpmn:flowNodeRef>Activity_14eor1x</bpmn:flowNodeRef>
|
||||
</bpmn:lane>
|
||||
</bpmn:laneSet>
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0a7090c</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:userTask id="Activity_1hljoeq" name="Request Approval" camunda:formKey="request_form_key">
|
||||
<bpmn:documentation># Answer me these questions 3, ere the other side you see!</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="favorite_color" label="What is your favorite color?" type="string" defaultValue="Yellow" />
|
||||
<camunda:formField id="quest" label="What is your quest?" type="string" defaultValue="To seek the holly Grail!" />
|
||||
<camunda:formField id="swallow_speed" label="What is the air speed velocity of an unladen swallow?" type="string" defaultValue="About 24 miles per hour" />
|
||||
<camunda:formField id="supervisor" label="Please enter the UVA Id of your supervisor" type="string" defaultValue="dhf8r" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0a7090c</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_070gq5r</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1hcpt7c</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:exclusiveGateway id="Gateway_1fkgc4u">
|
||||
<bpmn:incoming>Flow_1gp4zfd</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0vnghsi</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_1g38q6b</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:endEvent id="Event_0lscajc">
|
||||
<bpmn:documentation># Your responses were approved!
|
||||
|
||||
|
||||
Gosh! you must really know a lot about colors and swallows and stuff!
|
||||
Your supervisor provided the following feedback:
|
||||
|
||||
|
||||
{{feedback}}
|
||||
|
||||
|
||||
You are all done! WARNING: If you go back and reanswer the questions it will create a new approval request.</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_1g38q6b</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:manualTask id="Activity_19ccxoj" name="Review Feedback">
|
||||
<bpmn:documentation># Your Request was rejected
|
||||
|
||||
|
||||
Perhaps you don't know the right answer to one of the questions.
|
||||
Your Supervisor provided the following feedback:
|
||||
|
||||
|
||||
{{feedback}}
|
||||
|
||||
|
||||
Please press save to re-try the questions, and submit your responses again.</bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0vnghsi</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_070gq5r</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:sequenceFlow id="Flow_0a7090c" sourceRef="StartEvent_1" targetRef="Activity_1hljoeq" />
|
||||
<bpmn:sequenceFlow id="Flow_1gp4zfd" sourceRef="Activity_14eor1x" targetRef="Gateway_1fkgc4u" />
|
||||
<bpmn:sequenceFlow id="Flow_0vnghsi" name="rejected" sourceRef="Gateway_1fkgc4u" targetRef="Activity_19ccxoj">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">approval==False</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_1g38q6b" name="approved" sourceRef="Gateway_1fkgc4u" targetRef="Event_0lscajc">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">approval==True</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_1hcpt7c" sourceRef="Activity_1hljoeq" targetRef="Activity_14eor1x" />
|
||||
<bpmn:sequenceFlow id="Flow_070gq5r" sourceRef="Activity_19ccxoj" targetRef="Activity_1hljoeq" />
|
||||
<bpmn:userTask id="Activity_14eor1x" name="Approve Responses" camunda:formKey="approval_form_key">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="approval" label="I approve of this information" type="boolean" />
|
||||
<camunda:formField id="feedback" label="Feedback" type="string" defaultValue="Please provide any feedback you have here." />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1hcpt7c</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1gp4zfd</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0xjb3la">
|
||||
<bpmndi:BPMNShape id="Participant_0ozb2sp_di" bpmnElement="Participant_0ozb2sp" isHorizontal="true">
|
||||
<dc:Bounds x="190" y="80" width="550" height="310" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Lane_1m47545_di" bpmnElement="Lane_1m47545" isHorizontal="true">
|
||||
<dc:Bounds x="220" y="265" width="520" height="125" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Lane_1s1s7a1_di" bpmnElement="Lane_1s1s7a1" isHorizontal="true">
|
||||
<dc:Bounds x="220" y="80" width="520" height="185" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_070gq5r_di" bpmnElement="Flow_070gq5r">
|
||||
<di:waypoint x="510" y="160" />
|
||||
<di:waypoint x="510" y="100" />
|
||||
<di:waypoint x="380" y="100" />
|
||||
<di:waypoint x="380" y="160" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1hcpt7c_di" bpmnElement="Flow_1hcpt7c">
|
||||
<di:waypoint x="380" y="240" />
|
||||
<di:waypoint x="380" y="290" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1g38q6b_di" bpmnElement="Flow_1g38q6b">
|
||||
<di:waypoint x="535" y="330" />
|
||||
<di:waypoint x="680" y="330" />
|
||||
<di:waypoint x="680" y="218" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="585" y="312" width="46" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0vnghsi_di" bpmnElement="Flow_0vnghsi">
|
||||
<di:waypoint x="510" y="305" />
|
||||
<di:waypoint x="510" y="240" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="520" y="274" width="40" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1gp4zfd_di" bpmnElement="Flow_1gp4zfd">
|
||||
<di:waypoint x="430" y="330" />
|
||||
<di:waypoint x="485" y="330" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0a7090c_di" bpmnElement="Flow_0a7090c">
|
||||
<di:waypoint x="276" y="200" />
|
||||
<di:waypoint x="330" y="200" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="240" y="182" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0xcxw40_di" bpmnElement="Activity_1hljoeq">
|
||||
<dc:Bounds x="330" y="160" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1fkgc4u_di" bpmnElement="Gateway_1fkgc4u" isMarkerVisible="true">
|
||||
<dc:Bounds x="485" y="305" width="50" height="50" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_0lscajc_di" bpmnElement="Event_0lscajc">
|
||||
<dc:Bounds x="662" y="182" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1jfdeta_di" bpmnElement="Activity_19ccxoj">
|
||||
<dc:Bounds x="460" y="160" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0zc7cgy_di" bpmnElement="Activity_14eor1x">
|
||||
<dc:Bounds x="330" y="290" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -8,7 +8,7 @@ from crc import session, app
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus, \
|
||||
ProtocolBuilderStudySchema
|
||||
from crc.models.approval import ApprovalStatus
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.study import StudyModel, StudySchema
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowModel
|
||||
from crc.services.file_service import FileService
|
||||
|
@ -9,80 +9,10 @@ from crc import session, app
|
||||
from crc.models.api_models import WorkflowApiSchema, MultiInstanceType, TaskSchema
|
||||
from crc.models.file import FileModelSchema
|
||||
from crc.models.workflow import WorkflowStatus
|
||||
from crc.services.workflow_service import WorkflowService
|
||||
from crc.models.stats import TaskEventModel
|
||||
|
||||
|
||||
class TestTasksApi(BaseTest):
|
||||
|
||||
def get_workflow_api(self, workflow, soft_reset=False, hard_reset=False):
|
||||
rv = self.app.get('/v1.0/workflow/%i?soft_reset=%s&hard_reset=%s' %
|
||||
(workflow.id, str(soft_reset), str(hard_reset)),
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json")
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
workflow_api = WorkflowApiSchema().load(json_data)
|
||||
self.assertEqual(workflow.workflow_spec_id, workflow_api.workflow_spec_id)
|
||||
return workflow_api
|
||||
|
||||
def complete_form(self, workflow_in, task_in, dict_data, error_code = None):
|
||||
prev_completed_task_count = workflow_in.completed_tasks
|
||||
if isinstance(task_in, dict):
|
||||
task_id = task_in["id"]
|
||||
else:
|
||||
task_id = task_in.id
|
||||
rv = self.app.put('/v1.0/workflow/%i/task/%s/data' % (workflow_in.id, task_id),
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json",
|
||||
data=json.dumps(dict_data))
|
||||
if error_code:
|
||||
self.assert_failure(rv, error_code=error_code)
|
||||
return
|
||||
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
|
||||
# Assure stats are updated on the model
|
||||
workflow = WorkflowApiSchema().load(json_data)
|
||||
# The total number of tasks may change over time, as users move through gateways
|
||||
# branches may be pruned. As we hit parallel Multi-Instance new tasks may be created...
|
||||
self.assertIsNotNone(workflow.total_tasks)
|
||||
self.assertEqual(prev_completed_task_count + 1, workflow.completed_tasks)
|
||||
# Assure a record exists in the Task Events
|
||||
task_events = session.query(TaskEventModel) \
|
||||
.filter_by(workflow_id=workflow.id) \
|
||||
.filter_by(task_id=task_id) \
|
||||
.order_by(TaskEventModel.date.desc()).all()
|
||||
self.assertGreater(len(task_events), 0)
|
||||
event = task_events[0]
|
||||
self.assertIsNotNone(event.study_id)
|
||||
self.assertEqual("dhf8r", event.user_uid)
|
||||
self.assertEqual(workflow.id, event.workflow_id)
|
||||
self.assertEqual(workflow.workflow_spec_id, event.workflow_spec_id)
|
||||
self.assertEqual(workflow.spec_version, event.spec_version)
|
||||
self.assertEqual(WorkflowService.TASK_ACTION_COMPLETE, event.action)
|
||||
self.assertEqual(task_in.id, task_id)
|
||||
self.assertEqual(task_in.name, event.task_name)
|
||||
self.assertEqual(task_in.title, event.task_title)
|
||||
self.assertEqual(task_in.type, event.task_type)
|
||||
self.assertEqual("COMPLETED", event.task_state)
|
||||
# Not sure what vodoo is happening inside of marshmallow to get me in this state.
|
||||
if isinstance(task_in.multi_instance_type, MultiInstanceType):
|
||||
self.assertEqual(task_in.multi_instance_type.value, event.mi_type)
|
||||
else:
|
||||
self.assertEqual(task_in.multi_instance_type, event.mi_type)
|
||||
|
||||
self.assertEqual(task_in.multi_instance_count, event.mi_count)
|
||||
self.assertEqual(task_in.multi_instance_index, event.mi_index)
|
||||
self.assertEqual(task_in.process_name, event.process_name)
|
||||
self.assertIsNotNone(event.date)
|
||||
|
||||
# Assure that there is data in the form_data
|
||||
self.assertIsNotNone(event.form_data)
|
||||
|
||||
workflow = WorkflowApiSchema().load(json_data)
|
||||
return workflow
|
||||
|
||||
def assert_options_populated(self, results, lookup_data_keys):
|
||||
option_keys = ['value', 'label', 'data']
|
||||
self.assertIsInstance(results, list)
|
||||
|
202
tests/test_user_roles.py
Normal file
202
tests/test_user_roles.py
Normal file
@ -0,0 +1,202 @@
|
||||
import json
|
||||
|
||||
from tests.base_test import BaseTest
|
||||
from crc.models.workflow import WorkflowStatus
|
||||
from crc import db
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.task_event import TaskEventModel, TaskEventSchema
|
||||
from crc.services.workflow_service import WorkflowService
|
||||
|
||||
|
||||
class TestTasksApi(BaseTest):
|
||||
|
||||
def test_raise_error_if_role_does_not_exist_in_data(self):
|
||||
workflow = self.create_workflow('roles', as_user="lje5u")
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid="lje5u")
|
||||
data = workflow_api.next_task.data
|
||||
# User lje5u can complete the first task
|
||||
self.complete_form(workflow, workflow_api.next_task, data, user_uid="lje5u")
|
||||
|
||||
# The next task is a supervisor task, and should raise an error if the role
|
||||
# information is not in the task data.
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid="lje5u")
|
||||
data = workflow_api.next_task.data
|
||||
data["approved"] = True
|
||||
result = self.complete_form(workflow, workflow_api.next_task, data, user_uid="lje5u",
|
||||
error_code="permission_denied")
|
||||
|
||||
def test_validation_of_workflow_fails_if_workflow_does_not_define_user_for_lane(self):
|
||||
error = None
|
||||
try:
|
||||
workflow = self.create_workflow('invalid_roles', as_user="lje5u")
|
||||
WorkflowService.test_spec(workflow.workflow_spec_id)
|
||||
except ApiError as ae:
|
||||
error = ae
|
||||
self.assertIsNotNone(error, "An error should be raised.")
|
||||
self.assertEquals("invalid_role", error.code)
|
||||
|
||||
def test_raise_error_if_user_does_not_have_the_correct_role(self):
|
||||
submitter = self.create_user(uid='lje5u')
|
||||
supervisor = self.create_user(uid='lb3dp')
|
||||
workflow = self.create_workflow('roles', as_user=submitter.uid)
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
|
||||
# User lje5u can complete the first task, and set her supervisor
|
||||
data = workflow_api.next_task.data
|
||||
data['supervisor'] = supervisor.uid
|
||||
self.complete_form(workflow, workflow_api.next_task, data, user_uid=submitter.uid)
|
||||
|
||||
# But she can not complete the supervisor role.
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
data = workflow_api.next_task.data
|
||||
data["approval"] = True
|
||||
result = self.complete_form(workflow, workflow_api.next_task, data, user_uid=submitter.uid,
|
||||
error_code="permission_denied")
|
||||
|
||||
# Only her supervisor can do that.
|
||||
self.complete_form(workflow, workflow_api.next_task, data, user_uid=supervisor.uid)
|
||||
|
||||
def test_nav_includes_lanes(self):
|
||||
submitter = self.create_user(uid='lje5u')
|
||||
workflow = self.create_workflow('roles', as_user=submitter.uid)
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
|
||||
nav = workflow_api.navigation
|
||||
self.assertEquals(5, len(nav))
|
||||
self.assertEquals("supervisor", nav[1]['lane'])
|
||||
|
||||
def test_get_outstanding_tasks_awaiting_current_user(self):
|
||||
submitter = self.create_user(uid='lje5u')
|
||||
supervisor = self.create_user(uid='lb3dp')
|
||||
workflow = self.create_workflow('roles', as_user=submitter.uid)
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
|
||||
# User lje5u can complete the first task, and set her supervisor
|
||||
data = workflow_api.next_task.data
|
||||
data['supervisor'] = supervisor.uid
|
||||
workflow_api = self.complete_form(workflow, workflow_api.next_task, data, user_uid=submitter.uid)
|
||||
|
||||
# At this point there should be a task_log with an action of Lane Change on it for
|
||||
# the supervisor.
|
||||
task_logs = db.session.query(TaskEventModel). \
|
||||
filter(TaskEventModel.user_uid == supervisor.uid). \
|
||||
filter(TaskEventModel.action == WorkflowService.TASK_ACTION_ASSIGNMENT).all()
|
||||
self.assertEquals(1, len(task_logs))
|
||||
|
||||
# A call to the /task endpoint as the supervisor user should return a list of
|
||||
# tasks that need their attention.
|
||||
rv = self.app.get('/v1.0/task_events?action=ASSIGNMENT',
|
||||
headers=self.logged_in_headers(supervisor),
|
||||
content_type="application/json")
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
tasks = TaskEventSchema(many=True).load(json_data)
|
||||
self.assertEquals(1, len(tasks))
|
||||
self.assertEquals(workflow.id, tasks[0]['workflow']['id'])
|
||||
self.assertEquals(workflow.study.id, tasks[0]['study']['id'])
|
||||
|
||||
# Assure we can say something sensible like:
|
||||
# You have a task called "Approval" to be completed in the "Supervisor Approval" workflow
|
||||
# for the study 'Why dogs are stinky' managed by user "Jane Smith (js42x)",
|
||||
# please check here to complete the task.
|
||||
# Display name isn't set in the tests, so just checking name, but the full workflow details are included.
|
||||
# I didn't delve into the full user details to keep things decoupled from ldap, so you just get the
|
||||
# uid back, but could query to get the full entry.
|
||||
self.assertEquals("roles", tasks[0]['workflow']['name'])
|
||||
self.assertEquals("Beer consumption in the bipedal software engineer", tasks[0]['study']['title'])
|
||||
self.assertEquals("lje5u", tasks[0]['study']['user_uid'])
|
||||
|
||||
# Completing the next step of the workflow will close the task.
|
||||
data['approval'] = True
|
||||
self.complete_form(workflow, workflow_api.next_task, data, user_uid=supervisor.uid)
|
||||
|
||||
def test_navigation_and_current_task_updates_through_workflow(self):
|
||||
|
||||
submitter = self.create_user(uid='lje5u')
|
||||
supervisor = self.create_user(uid='lb3dp')
|
||||
workflow = self.create_workflow('roles', as_user=submitter.uid)
|
||||
|
||||
# Navigation as Submitter with ready task.
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEquals(5, len(nav))
|
||||
self.assertEquals('READY', nav[0]['state']) # First item is ready, no progress yet.
|
||||
self.assertEquals('LOCKED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEquals('LOCKED', nav[2]['state']) # third item is a gateway, and belongs to no one, and is locked.
|
||||
self.assertEquals('NOOP', nav[3]['state']) # Approved Path, has no operation
|
||||
self.assertEquals('NOOP', nav[4]['state']) # Rejected Path, has no operation.
|
||||
self.assertEquals('READY', workflow_api.next_task.state)
|
||||
|
||||
# Navigation as Submitter after handoff to supervisor
|
||||
data = workflow_api.next_task.data
|
||||
data['supervisor'] = supervisor.uid
|
||||
workflow_api = self.complete_form(workflow, workflow_api.next_task, data, user_uid=submitter.uid)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEquals('COMPLETED', nav[0]['state']) # First item is ready, no progress yet.
|
||||
self.assertEquals('LOCKED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEquals('LOCKED', nav[2]['state']) # third item is a gateway, and belongs to no one, and is locked.
|
||||
self.assertEquals('LOCKED', workflow_api.next_task.state)
|
||||
# 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
|
||||
# is guaranteed to have a supervisor attribute in it that will contain the users uid, which
|
||||
# could be looked up through an ldap service.
|
||||
self.assertEquals('supervisor', workflow_api.next_task.lane)
|
||||
|
||||
|
||||
# Navigation as Supervisor
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=supervisor.uid)
|
||||
nav = workflow_api.navigation
|
||||
self.assertEquals(5, len(nav))
|
||||
self.assertEquals('LOCKED', nav[0]['state']) # First item belongs to the submitter, and is locked.
|
||||
self.assertEquals('READY', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEquals('LOCKED', nav[2]['state']) # third item is a gateway, and belongs to no one, and is locked.
|
||||
self.assertEquals('READY', workflow_api.next_task.state)
|
||||
|
||||
data = workflow_api.next_task.data
|
||||
data["approval"] = False
|
||||
workflow_api = self.complete_form(workflow, workflow_api.next_task, data, user_uid=supervisor.uid)
|
||||
|
||||
# Navigation as Supervisor, after completing task.
|
||||
nav = workflow_api.navigation
|
||||
self.assertEquals(5, len(nav))
|
||||
self.assertEquals('LOCKED', nav[0]['state']) # First item belongs to the submitter, and is locked.
|
||||
self.assertEquals('COMPLETED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEquals('COMPLETED', nav[2]['state']) # third item is a gateway, and is now complete.
|
||||
self.assertEquals('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.assertEquals(5, len(nav))
|
||||
self.assertEquals('COMPLETED', nav[0]['state']) # First item belongs to the submitter, and is locked.
|
||||
self.assertEquals('LOCKED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEquals('LOCKED', nav[2]['state']) # third item is a gateway belonging to the supervisor, and is locked.
|
||||
self.assertEquals('READY', workflow_api.next_task.state)
|
||||
|
||||
# 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.assertEquals(5, len(nav))
|
||||
self.assertEquals('COMPLETED', nav[0]['state']) # We still have some issues here, the navigation will be off when looping back.
|
||||
self.assertEquals('LOCKED', nav[1]['state']) # Second item is locked, it is the review and doesn't belong to this user.
|
||||
self.assertEquals('LOCKED', nav[2]['state']) # third item is a gateway belonging to the supervisor, and is locked.
|
||||
self.assertEquals('READY', workflow_api.next_task.state)
|
||||
|
||||
data["favorite_color"] = "blue"
|
||||
data["quest"] = "to seek the holy grail"
|
||||
workflow_api = self.complete_form(workflow, workflow_api.next_task, data, user_uid=submitter.uid)
|
||||
self.assertEquals('LOCKED', workflow_api.next_task.state)
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=supervisor.uid)
|
||||
self.assertEquals('READY', workflow_api.next_task.state)
|
||||
|
||||
data = workflow_api.next_task.data
|
||||
data["approval"] = True
|
||||
workflow_api = self.complete_form(workflow, workflow_api.next_task, data, user_uid=supervisor.uid)
|
||||
self.assertEquals('LOCKED', workflow_api.next_task.state)
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow, user_uid=submitter.uid)
|
||||
self.assertEquals('COMPLETED', workflow_api.next_task.state)
|
||||
self.assertEquals('EndEvent', workflow_api.next_task.type) # Are are at the end.
|
||||
self.assertEquals(WorkflowStatus.complete, workflow_api.status)
|
@ -371,4 +371,16 @@ class TestWorkflowProcessor(BaseTest):
|
||||
self._populate_form_with_random_data(task)
|
||||
|
||||
|
||||
def test_get_role_by_name(self):
|
||||
self.load_example_data()
|
||||
workflow_spec_model = self.load_test_spec("roles")
|
||||
study = session.query(StudyModel).first()
|
||||
processor = self.get_processor(study, workflow_spec_model)
|
||||
processor.do_engine_steps()
|
||||
tasks = processor.next_user_tasks()
|
||||
task = tasks[0]
|
||||
self._populate_form_with_random_data(task)
|
||||
processor.complete_task(task)
|
||||
supervisor_task = processor.next_user_tasks()[0]
|
||||
self.assertEquals("supervisor", supervisor_task.task_spec.lane)
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
@ -7,7 +8,7 @@ from crc.services.workflow_service import WorkflowService
|
||||
from SpiffWorkflow import Task as SpiffTask, WorkflowException
|
||||
from example_data import ExampleDataLoader
|
||||
from crc import db
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.api_models import Task
|
||||
from crc.api.common import ApiError
|
||||
|
||||
@ -88,6 +89,7 @@ class TestWorkflowService(BaseTest):
|
||||
WorkflowService.populate_form_with_random_data(task, task_api, required_only=False)
|
||||
self.assertTrue(isinstance(task.data["sponsor"], dict))
|
||||
|
||||
@unittest.skip("RRT no longer needs to be supported")
|
||||
def test_fix_legacy_data_model_for_rrt(self):
|
||||
ExampleDataLoader().load_rrt() # Make sure the research_rampup is loaded, as it's not a test spec.
|
||||
workflow = self.create_workflow('research_rampup')
|
||||
@ -102,9 +104,8 @@ class TestWorkflowService(BaseTest):
|
||||
WorkflowService.populate_form_with_random_data(task, task_api, False)
|
||||
task.complete()
|
||||
# create the task events
|
||||
WorkflowService.log_task_action('dhf8r', workflow, task,
|
||||
WorkflowService.TASK_ACTION_COMPLETE,
|
||||
version=processor.get_version_string())
|
||||
WorkflowService.log_task_action('dhf8r', processor, task,
|
||||
WorkflowService.TASK_ACTION_COMPLETE)
|
||||
processor.save()
|
||||
db.session.commit()
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import json
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from tests.base_test import BaseTest
|
||||
@ -51,6 +52,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
||||
app.config['PB_ENABLED'] = True
|
||||
self.validate_all_loaded_workflows()
|
||||
|
||||
@unittest.skip("RRT no longer needs to be supported")
|
||||
def test_successful_validation_of_rrt_workflows(self):
|
||||
self.load_example_data(use_rrt_data=True)
|
||||
self.validate_all_loaded_workflows()
|
||||
|
Loading…
x
Reference in New Issue
Block a user