Merge branch 'dev' into email-data-script-487

This commit is contained in:
mike cullerton 2021-10-07 12:30:13 -04:00
commit 24932072dc
30 changed files with 576 additions and 397 deletions

459
Pipfile.lock generated
View File

@ -25,11 +25,11 @@
}, },
"alembic": { "alembic": {
"hashes": [ "hashes": [
"sha256:bc5bdf03d1b9814ee4d72adc0b19df2123f6c50a60c1ea761733f3640feedb8d", "sha256:9d33f3ff1488c4bfab1e1a6dfebbf085e8a8e1a3e047a43ad29ad1f67f012a1d",
"sha256:d0c580041f9f6487d5444df672a83da9be57398f39d6c1802bbedec6fefbeef6" "sha256:e3cab9e59778b3b6726bb2da9ced451c6622d558199fd3ef914f3b1e8f4ef704"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.7.3" "version": "==1.7.4"
}, },
"amqp": { "amqp": {
"hashes": [ "hashes": [
@ -187,9 +187,11 @@
}, },
"click-didyoumean": { "click-didyoumean": {
"hashes": [ "hashes": [
"sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb" "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
"sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
], ],
"version": "==0.0.3" "markers": "python_version < '4' and python_full_version >= '3.6.2'",
"version": "==0.3.0"
}, },
"click-plugins": { "click-plugins": {
"hashes": [ "hashes": [
@ -240,69 +242,52 @@
}, },
"coverage": { "coverage": {
"hashes": [ "hashes": [
"sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", "sha256:08fd55d2e00dac4c18a2fa26281076035ec86e764acdc198b9185ce749ada58f",
"sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", "sha256:11ce082eb0f7c2bbfe96f6c8bcc3a339daac57de4dc0f3186069ec5c58da911c",
"sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", "sha256:17983f6ccc47f4864fd16d20ff677782b23d1207bf222d10e4d676e4636b0872",
"sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", "sha256:25df2bc53a954ba2ccf230fa274d1de341f6aa633d857d75e5731365f7181749",
"sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", "sha256:274a612f67f931307706b60700f1e4cf80e1d79dff6c282fc9301e4565e78724",
"sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", "sha256:3dfb23cc180b674a11a559183dff9655beb9da03088f3fe3c4f3a6d200c86f05",
"sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", "sha256:43bada49697a62ffa0283c7f01bbc76aac562c37d4bb6c45d56dd008d841194e",
"sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", "sha256:4865dc4a7a566147cbdc2b2f033a6cccc99a7dcc89995137765c384f6c73110b",
"sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", "sha256:581fddd2f883379bd5af51da9233e0396b6519f3d3eeae4fb88867473be6d56e",
"sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", "sha256:5c191e01b23e760338f19d8ba2470c0dad44c8b45e41ac043b2db84efc62f695",
"sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", "sha256:6e216e4021c934246c308fd3e0d739d9fa8a3f4ea414f584ab90ef9c1592f282",
"sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", "sha256:72f8c99f1527c5a8ee77c890ea810e26b39fd0b4c2dffc062e20a05b2cca60ef",
"sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", "sha256:7593a49300489d064ebb6c58539f52cbbc4a2e6a4385de5e92cae1563f88a425",
"sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", "sha256:7844a8c6a0fee401edbf578713c2473e020759267c40261b294036f9d3eb6a2d",
"sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", "sha256:7af2f8e7bb54ace984de790e897f858e88068d8fbc46c9490b7c19c59cf51822",
"sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", "sha256:7dbda34e8e26bd86606ba8a9c13ccb114802e01758a3d0a75652ffc59a573220",
"sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", "sha256:82b58d37c47d93a171be9b5744bcc96a0012cbf53d5622b29a49e6be2097edd7",
"sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", "sha256:8305e14112efb74d0b5fec4df6e41cafde615c2392a7e51c84013cafe945842c",
"sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", "sha256:8426fec5ad5a6e8217921716b504e9b6e1166dc147e8443b4855e329db686282",
"sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", "sha256:88f1810eb942e7063d051d87aaaa113eb5fd5a7fd2cda03a972de57695b8bb1a",
"sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", "sha256:8da0c4a26a831b392deaba5fdd0cd7838d173b47ce2ec3d0f37be630cb09ef6e",
"sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", "sha256:a9dbfcbc56d8de5580483cf2caff6a59c64d3e88836cbe5fb5c20c05c29a8808",
"sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", "sha256:aa5d4d43fa18cc9d0c6e02a83de0b9729b5451a9066574bd276481474f0a53ab",
"sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", "sha256:adb0f4c3c8ba8104378518a1954cbf3d891a22c13fd0e0bf135391835f44f288",
"sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", "sha256:b4ee5815c776dfa3958ba71c7cd4cdd8eb40d79358a18352feb19562fe4408c4",
"sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", "sha256:b5dd5ae0a9cd55d71f1335c331e9625382239b8cede818fb62d8d2702336dbf8",
"sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", "sha256:b78dd3eeb8f5ff26d2113c41836bac04a9ea91be54c346826b54a373133c8c53",
"sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", "sha256:bea681309bdd88dd1283a8ba834632c43da376d9bce05820826090aad80c0126",
"sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", "sha256:befb5ffa9faabef6dadc42622c73de168001425258f0b7e402a2934574e7a04b",
"sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", "sha256:d795a2c92fe8cb31f6e9cd627ee4f39b64eb66bf47d89d8fcf7cb3d17031c887",
"sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", "sha256:d82cbef1220703ce56822be7fbddb40736fc1a928ac893472df8aff7421ae0aa",
"sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", "sha256:e63490e8a6675cee7a71393ee074586f7eeaf0e9341afd006c5d6f7eec7c16d7",
"sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", "sha256:e735ab8547d8a1fe8e58dd765d6f27ac539b395f52160d767b7189f379f9be7a",
"sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", "sha256:fa816e97cfe1f691423078dffa39a18106c176f28008db017b3ce3e947c34aa5",
"sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", "sha256:fff04bfefb879edcf616f1ce5ea6f4a693b5976bdc5e163f8464f349c25b59f0"
"sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
"sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
"sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
"sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
"sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
"sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
"sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
"sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
"sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
"sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
"sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
"sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
"sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
"sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
"sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
"sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
"sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.5" "version": "==6.0"
}, },
"dateparser": { "dateparser": {
"hashes": [ "hashes": [
"sha256:159cc4e01a593706a15cd4e269a0b3345edf3aef8bf9278a57dac8adf5bf1e4a", "sha256:faa2b97f51f3b5ff1ba2f17be90de2b733fb6191f89b4058787473e8202f3044",
"sha256:17202df32c7a36e773136ff353aa3767e987f8b3e27374c39fd21a30a803d6f8" "sha256:fec344db1f73d005182e214c0ff27313c748bbe0c1638ce9d48a809ddfdab2a0"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.0.0" "version": "==1.1.0"
}, },
"deprecated": { "deprecated": {
"hashes": [ "hashes": [
@ -328,11 +313,11 @@
}, },
"docxtpl": { "docxtpl": {
"hashes": [ "hashes": [
"sha256:84b5b35eca090f03768109440ad8e4c5040d8614e2c5bdc807a7ca55d23d7018", "sha256:c90ca0b7251d3aa20bb5b67d1b6d4dcd546cb0c56aa080f6733656622bd086e1",
"sha256:fae3c2f6afcb11ceb85dace09a99d4d64d604623653d66b754e90fca150e77b3" "sha256:e8d3dba9c9638f8d3212187e6b8cd1dbcc87f4fb34a436873e4e86a530602b99"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.12.0" "version": "==0.14.2"
}, },
"et-xmlfile": { "et-xmlfile": {
"hashes": [ "hashes": [
@ -420,59 +405,59 @@
}, },
"greenlet": { "greenlet": {
"hashes": [ "hashes": [
"sha256:04e1849c88aa56584d4a0a6e36af5ec7cc37993fdc1fda72b56aa1394a92ded3", "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711",
"sha256:05e72db813c28906cdc59bd0da7c325d9b82aa0b0543014059c34c8c4ad20e16", "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd",
"sha256:07e6d88242e09b399682b39f8dfa1e7e6eca66b305de1ff74ed9eb1a7d8e539c", "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073",
"sha256:090126004c8ab9cd0787e2acf63d79e80ab41a18f57d6448225bbfcba475034f", "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708",
"sha256:1796f2c283faab2b71c67e9b9aefb3f201fdfbee5cb55001f5ffce9125f63a45", "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67",
"sha256:2f89d74b4f423e756a018832cd7a0a571e0a31b9ca59323b77ce5f15a437629b", "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23",
"sha256:34e6675167a238bede724ee60fe0550709e95adaff6a36bcc97006c365290384", "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1",
"sha256:3e594015a2349ec6dcceda9aca29da8dc89e85b56825b7d1f138a3f6bb79dd4c", "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08",
"sha256:3f8fc59bc5d64fa41f58b0029794f474223693fd00016b29f4e176b3ee2cfd9f", "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd",
"sha256:3fc6a447735749d651d8919da49aab03c434a300e9f0af1c886d560405840fd1", "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa",
"sha256:40abb7fec4f6294225d2b5464bb6d9552050ded14a7516588d6f010e7e366dcc", "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8",
"sha256:44556302c0ab376e37939fd0058e1f0db2e769580d340fb03b01678d1ff25f68", "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40",
"sha256:476ba9435afaead4382fbab8f1882f75e3fb2285c35c9285abb3dd30237f9142", "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab",
"sha256:4870b018ca685ff573edd56b93f00a122f279640732bb52ce3a62b73ee5c4a92", "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6",
"sha256:4adaf53ace289ced90797d92d767d37e7cdc29f13bd3830c3f0a561277a4ae83", "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc",
"sha256:4eae94de9924bbb4d24960185363e614b1b62ff797c23dc3c8a7c75bbb8d187e", "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b",
"sha256:5317701c7ce167205c0569c10abc4bd01c7f4cf93f642c39f2ce975fa9b78a3c", "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e",
"sha256:5c3b735ccf8fc8048664ee415f8af5a3a018cc92010a0d7195395059b4b39b7d", "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963",
"sha256:5cde7ee190196cbdc078511f4df0be367af85636b84d8be32230f4871b960687", "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3",
"sha256:655ab836324a473d4cd8cf231a2d6f283ed71ed77037679da554e38e606a7117", "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d",
"sha256:6ce9d0784c3c79f3e5c5c9c9517bbb6c7e8aa12372a5ea95197b8a99402aa0e6", "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d",
"sha256:6e0696525500bc8aa12eae654095d2260db4dc95d5c35af2b486eae1bf914ccd", "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28",
"sha256:75ff270fd05125dce3303e9216ccddc541a9e072d4fc764a9276d44dee87242b", "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3",
"sha256:8039f5fe8030c43cd1732d9a234fdcbf4916fcc32e21745ca62e75023e4d4649", "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e",
"sha256:84488516639c3c5e5c0e52f311fff94ebc45b56788c2a3bfe9cf8e75670f4de3", "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c",
"sha256:84782c80a433d87530ae3f4b9ed58d4a57317d9918dfcc6a59115fa2d8731f2c", "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d",
"sha256:8ddb38fb6ad96c2ef7468ff73ba5c6876b63b664eebb2c919c224261ae5e8378", "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0",
"sha256:98b491976ed656be9445b79bc57ed21decf08a01aaaf5fdabf07c98c108111f6", "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497",
"sha256:990e0f5e64bcbc6bdbd03774ecb72496224d13b664aa03afd1f9b171a3269272", "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee",
"sha256:9b02e6039eafd75e029d8c58b7b1f3e450ca563ef1fe21c7e3e40b9936c8d03e", "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713",
"sha256:a11b6199a0b9dc868990456a2667167d0ba096c5224f6258e452bfbe5a9742c5", "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58",
"sha256:a414f8e14aa7bacfe1578f17c11d977e637d25383b6210587c29210af995ef04", "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a",
"sha256:a91ee268f059583176c2c8b012a9fce7e49ca6b333a12bbc2dd01fc1a9783885", "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06",
"sha256:ac991947ca6533ada4ce7095f0e28fe25d5b2f3266ad5b983ed4201e61596acf", "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88",
"sha256:b050dbb96216db273b56f0e5960959c2b4cb679fe1e58a0c3906fa0a60c00662", "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4",
"sha256:b97a807437b81f90f85022a9dcfd527deea38368a3979ccb49d93c9198b2c722", "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5",
"sha256:bad269e442f1b7ffa3fa8820b3c3aa66f02a9f9455b5ba2db5a6f9eea96f56de", "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c",
"sha256:bf3725d79b1ceb19e83fb1aed44095518c0fcff88fba06a76c0891cfd1f36837", "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a",
"sha256:c0f22774cd8294078bdf7392ac73cf00bfa1e5e0ed644bd064fdabc5f2a2f481", "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1",
"sha256:c1862f9f1031b1dee3ff00f1027fcd098ffc82120f43041fe67804b464bbd8a7", "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43",
"sha256:c8d4ed48eed7414ccb2aaaecbc733ed2a84c299714eae3f0f48db085342d5629", "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627",
"sha256:cf31e894dabb077a35bbe6963285d4515a387ff657bd25b0530c7168e48f167f", "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b",
"sha256:d15cb6f8706678dc47fb4e4f8b339937b04eda48a0af1cca95f180db552e7663", "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168",
"sha256:dfcb5a4056e161307d103bc013478892cfd919f1262c2bb8703220adcb986362", "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d",
"sha256:e02780da03f84a671bb4205c5968c120f18df081236d7b5462b380fd4f0b497b", "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5",
"sha256:e2002a59453858c7f3404690ae80f10c924a39f45f6095f18a985a1234c37334", "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478",
"sha256:e22a82d2b416d9227a500c6860cf13e74060cf10e7daf6695cbf4e6a94e0eee4", "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf",
"sha256:e41f72f225192d5d4df81dad2974a8943b0f2d664a2a5cfccdf5a01506f5523c", "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce",
"sha256:f253dad38605486a4590f9368ecbace95865fea0f2b66615d121ac91fd1a1563", "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c",
"sha256:fddfb31aa2ac550b938d952bca8a87f1db0f8dc930ffa14ce05b5c08d27e7fd1" "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"
], ],
"markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))",
"version": "==1.1.1" "version": "==1.1.2"
}, },
"gunicorn": { "gunicorn": {
"hashes": [ "hashes": [
@ -505,6 +490,14 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.2.0" "version": "==1.2.0"
}, },
"importlib-metadata": {
"hashes": [
"sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15",
"sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"
],
"markers": "python_version < '3.9'",
"version": "==4.8.1"
},
"importlib-resources": { "importlib-resources": {
"hashes": [ "hashes": [
"sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977", "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977",
@ -989,10 +982,10 @@
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c",
"sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"
], ],
"version": "==2021.1" "version": "==2021.3"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
@ -1039,49 +1032,49 @@
}, },
"regex": { "regex": {
"hashes": [ "hashes": [
"sha256:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656", "sha256:0de8ad66b08c3e673b61981b9e3626f8784d5564f8c3928e2ad408c0eb5ac38c",
"sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a", "sha256:1f1125bc5172ab3a049bc6f4b9c0aae95a2a2001a77e6d6e4239fa3653e202b5",
"sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae", "sha256:255791523f80ea8e48e79af7120b4697ef3b74f6886995dcdb08c41f8e516be0",
"sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0", "sha256:28040e89a04b60d579c69095c509a4f6a1a5379cd865258e3a186b7105de72c6",
"sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1", "sha256:37868075eda024470bd0feab872c692ac4ee29db1e14baec103257bf6cc64346",
"sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7", "sha256:3b71213ec3bad9a5a02e049f2ec86b3d7c3e350129ae0f4e2f99c12b5da919ed",
"sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3", "sha256:3be40f720af170a6b20ddd2ad7904c58b13d2b56f6734ee5d09bbdeed2fa4816",
"sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c", "sha256:42952d325439ef223e4e9db7ee6d9087b5c68c5c15b1f9de68e990837682fc7b",
"sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7", "sha256:470f2c882f2672d8eeda8ab27992aec277c067d280b52541357e1acd7e606dae",
"sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3", "sha256:4907fb0f9b9309a5bded72343e675a252c2589a41871874feace9a05a540241e",
"sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673", "sha256:4d87459ad3ab40cd8493774f8a454b2e490d8e729e7e402a0625867a983e4e02",
"sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c", "sha256:4fa7ba9ab2eba7284e0d7d94f61df7af86015b0398e123331362270d71fab0b9",
"sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e", "sha256:5b34d2335d6aedec7dcadd3f8283b9682fadad8b9b008da8788d2fce76125ebe",
"sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d", "sha256:6348a7ab2a502cbdd0b7fd0496d614007489adb7361956b38044d1d588e66e04",
"sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2", "sha256:638e98d069b14113e8afba6a54d1ca123f712c0d105e67c1f9211b2a825ef926",
"sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72", "sha256:66696c8336a1b5d1182464f3af3427cc760118f26d0b09a2ddc16a976a4d2637",
"sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3", "sha256:78cf6a1e023caf5e9a982f5377414e1aeac55198831b852835732cfd0a0ca5ff",
"sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11", "sha256:81e125d9ba54c34579e4539a967e976a3c56150796674aec318b1b2f49251be7",
"sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90", "sha256:81fdc90f999b2147fc62e303440c424c47e5573a9b615ed5d43a5b832efcca9e",
"sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5", "sha256:87e9c489aa98f50f367fb26cc9c8908d668e9228d327644d7aa568d47e456f47",
"sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec", "sha256:8c1ad61fa024195136a6b7b89538030bd00df15f90ac177ca278df9b2386c96f",
"sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a", "sha256:9910869c472e5a6728680ca357b5846546cbbd2ab3ad5bef986ef0bc438d0aa6",
"sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561", "sha256:9925985be05d54b3d25fd6c1ea8e50ff1f7c2744c75bdc4d3b45c790afa2bcb3",
"sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab", "sha256:9a0b0db6b49da7fa37ca8eddf9f40a8dbc599bad43e64f452284f37b6c34d91c",
"sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078", "sha256:9c065d95a514a06b92a5026766d72ac91bfabf581adb5b29bc5c91d4b3ee9b83",
"sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc", "sha256:a6f08187136f11e430638c2c66e1db091105d7c2e9902489f0dbc69b44c222b4",
"sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1", "sha256:ad0517df22a97f1da20d8f1c8cb71a5d1997fa383326b81f9cf22c9dadfbdf34",
"sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c", "sha256:b345ecde37c86dd7084c62954468a4a655fd2d24fd9b237949dd07a4d0dd6f4c",
"sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6", "sha256:b55442650f541d195a535ccec33078c78a9521973fb960923da7515e9ed78fa6",
"sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48", "sha256:c2b180ed30856dfa70cfe927b0fd38e6b68198a03039abdbeb1f2029758d87e7",
"sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456", "sha256:c9e30838df7bfd20db6466fd309d9b580d32855f8e2c2e6d74cf9da27dcd9b63",
"sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8", "sha256:cae4099031d80703954c39680323dabd87a69b21262303160776aa0e55970ca0",
"sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18", "sha256:ce7b1cca6c23f19bee8dc40228d9c314d86d1e51996b86f924aca302fc8f8bf9",
"sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4", "sha256:d0861e7f6325e821d5c40514c551fd538b292f8cc3960086e73491b9c5d8291d",
"sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736", "sha256:d331f238a7accfbbe1c4cd1ba610d4c087b206353539331e32a8f05345c74aec",
"sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949", "sha256:e07049cece3462c626d650e8bf42ddbca3abf4aa08155002c28cb6d9a5a281e2",
"sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346", "sha256:e2cb7d4909ed16ed35729d38af585673f1f0833e73dfdf0c18e5be0061107b99",
"sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db", "sha256:e3770781353a4886b68ef10cec31c1f61e8e3a0be5f213c2bb15a86efd999bc4",
"sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad", "sha256:e502f8d4e5ef714bcc2c94d499684890c94239526d61fdf1096547db91ca6aa6",
"sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9", "sha256:e6f2d2f93001801296fe3ca86515eb04915472b5380d4d8752f09f25f0b9b0ed",
"sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819" "sha256:f588209d3e4797882cd238195c175290dbc501973b10a581086b5c6bcd095ffb"
], ],
"version": "==2021.9.24" "version": "==2021.9.30"
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
@ -1183,7 +1176,7 @@
}, },
"spiffworkflow": { "spiffworkflow": {
"git": "https://github.com/sartography/SpiffWorkflow", "git": "https://github.com/sartography/SpiffWorkflow",
"ref": "d911632569d73ec5e039504005a740c3d4d50451" "ref": "4b4628ac2d0edc2aeec387515a26cdab373f95fb"
}, },
"sqlalchemy": { "sqlalchemy": {
"hashes": [ "hashes": [
@ -1293,9 +1286,54 @@
}, },
"wrapt": { "wrapt": {
"hashes": [ "hashes": [
"sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" "sha256:04312fbf51e9dd15261228e6b4bed0c0ed5723ccf986645d2c7308511dccba35",
"sha256:04a00cef5d1b9e0e8db997816437b436e859106283c4771a40c4de4759344765",
"sha256:0b2cbe418beeff3aadb3afc39a67d3f5f6a3eb020ceb5f2bcf56bef14b33629a",
"sha256:19b2c992668c9ca764899bae52987a04041ebc21859d2646db0b27e089c2fd6b",
"sha256:1b46e4fe0f9efbfaf1ee82fc79f9cb044c69b67b181c58370440d396fe40736e",
"sha256:21c1710f61aa95b4be83a32b6d6facbb0efdfac22dee65e1caa72a83deed7cda",
"sha256:2d18618440df6bc072762625e9c843d32a7328347c321b89f8df3a7c4a72ce6c",
"sha256:2f6fbea8936ba862425664fc689182a8ef50a6d88cd49f3cd073eccd3e78c930",
"sha256:3658ae9c704906cab5865a00c1aa9e1fd3555074d1a4462fa1742d7fea8260ae",
"sha256:3816922f0941f1637869a04e25d1e5261dfa55cc6b39c73872cbf192ea562443",
"sha256:40fd2cebad4010787de4221ec27a650635eed3e49e4bbfa8244fc34836cc2457",
"sha256:4f3f99bb8eed5d394bbb898c5191ed91ebf21187d52b2c45895733ae2798f373",
"sha256:5dc6c8cfaf4ff2a4632f8f97d29f555d6951eb0f905d3d47b3fd69bddb653214",
"sha256:6aa687da5565674c9696fafd2b8d44a04fb697ec2431af21c3def9cbedc4082a",
"sha256:6b81913fdba96e286f0c6007eb61f0158e64a1941bfc72fee61b34a4f8f9877f",
"sha256:6c241b4ef0744590ae0ee89305743977e478200cff961bdcc6b3d0530aea3377",
"sha256:77fef0bfdc612f5f30e43392a9f67dddaf4f48f299421bf25f910d0f47173f3d",
"sha256:7929ce97be2f7c49f454a6f8e014225e53cc3767fe48cce94b188de2225232ac",
"sha256:8055f8cc9a80dc1db01f31af6399b83f597ec164f07b7251d2a1bf1c6c025190",
"sha256:836c73f53a0cefc7ba10c6f4a0d78894cb4876f56035fe500b029e0a1ae0ffe9",
"sha256:8a184c655bb41295a9b0c28745a1b762c0c86025e43808b7e814f9cedc6c563d",
"sha256:8a6ba1b00d07f5a90a2d2eb1804a42e2067a6145b7745a8297664a75a8a232ba",
"sha256:909a80ce028821c7ad01bdcaa588126825931d177cdccd00b3545818d4a195ce",
"sha256:947a8d9d7829364e11eca88af18394713c8f98571cbc672b12545977d837f054",
"sha256:95c9fcfc326fdd3e2fd264e808f6474ca7ffd253feb3a505ee5ceb4d78216ef7",
"sha256:972099fa9cf4e43c255701c78ec5098c2fec4d6ea669a110b3414a158e772b0a",
"sha256:97f016514ceac524832e7d1bd41cf928b992ebe0324d59736f84ad5f4bbe0632",
"sha256:9d200716eb4bb1d73f47f3ccc4f98fdf979dcc82d752183828f1be2e332b6874",
"sha256:9f839c47698052ef5c2c094e21f8a06d0828aebe52d20cdb505faa318c62e886",
"sha256:aa637733f1d599077522f6a1f0c6c40389aa90a44cba37afcefef26f8e53d28f",
"sha256:b0eed9b295039a619f64667f27cffbffcfc0559073d562700912ca6266bc8b28",
"sha256:b1137e6aef3ac267c2af7d3af0266ef3f8dd1e5cde67b8eac9fa3b94e7fa0ada",
"sha256:b41ce8ee3825634e67883dd4dab336f95d0cc9d223fb7e224dcd36d66af93694",
"sha256:bc42803987eb46b5fc67ec9a072df15a72ee9db61e3b7dd955d82581bf141f60",
"sha256:bd705e341baccc3d1ef20e790b1f6383bd4ae92a77ba87a86ece8189fab8793c",
"sha256:c803526c0d3fa426e06de379b4eb56102234f2dc3c3a24a500d7962a83ca6166",
"sha256:cb0b12b365b054bee2a53078a67df81781be0686cc3f3ab8bbdd16b2e188570a",
"sha256:d0ae90fd60c7473e437b0dd48ae323c11f631fe47c243056f9e7505d26e8e2f6",
"sha256:db0daf2afca9f3b3a76e96ecb5f55ba82615ec584471d7aa27c1bdeb9e3888bb",
"sha256:e2eb4f38441b56698b4d40d48fd331e4e8a0477264785d08cbced63813d4bd29",
"sha256:e5a0727ea56de6e9a17693589bcf913d6bf1ec49f12d4671993321f3325fda4f",
"sha256:ec803c9d6e4ce037201132d903ff8b0dd26c9688be50ce4c77c420c076e78ff7",
"sha256:f1e2cea943192e24070b65bda862901c02bdf7c6abcd66ef5381ad6511921067",
"sha256:f4377eda306b488255ea4336662cd9015a902d6dc2ed77a3e4c1e3b42387453a",
"sha256:fd5320bf61a2e8d3b46d9e183323293c9a695df8f38c98d17c45e1846758f9a9"
], ],
"version": "==1.12.1" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.13.1"
}, },
"wtforms": { "wtforms": {
"hashes": [ "hashes": [
@ -1322,11 +1360,11 @@
}, },
"zipp": { "zipp": {
"hashes": [ "hashes": [
"sha256:1fc9641b26f3bd81069b7738b039f2819cab6e3fc3399a953e19d92cc81eff4d", "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
"sha256:8dc6c4d5a809d659067cc713f76bcf42fae8ae641db12fddfa93694a15abc96b" "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
], ],
"markers": "python_version < '3.10'", "markers": "python_version < '3.10'",
"version": "==3.5.1" "version": "==3.6.0"
} }
}, },
"develop": { "develop": {
@ -1340,61 +1378,44 @@
}, },
"coverage": { "coverage": {
"hashes": [ "hashes": [
"sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", "sha256:08fd55d2e00dac4c18a2fa26281076035ec86e764acdc198b9185ce749ada58f",
"sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", "sha256:11ce082eb0f7c2bbfe96f6c8bcc3a339daac57de4dc0f3186069ec5c58da911c",
"sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", "sha256:17983f6ccc47f4864fd16d20ff677782b23d1207bf222d10e4d676e4636b0872",
"sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", "sha256:25df2bc53a954ba2ccf230fa274d1de341f6aa633d857d75e5731365f7181749",
"sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", "sha256:274a612f67f931307706b60700f1e4cf80e1d79dff6c282fc9301e4565e78724",
"sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", "sha256:3dfb23cc180b674a11a559183dff9655beb9da03088f3fe3c4f3a6d200c86f05",
"sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", "sha256:43bada49697a62ffa0283c7f01bbc76aac562c37d4bb6c45d56dd008d841194e",
"sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", "sha256:4865dc4a7a566147cbdc2b2f033a6cccc99a7dcc89995137765c384f6c73110b",
"sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", "sha256:581fddd2f883379bd5af51da9233e0396b6519f3d3eeae4fb88867473be6d56e",
"sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", "sha256:5c191e01b23e760338f19d8ba2470c0dad44c8b45e41ac043b2db84efc62f695",
"sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", "sha256:6e216e4021c934246c308fd3e0d739d9fa8a3f4ea414f584ab90ef9c1592f282",
"sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", "sha256:72f8c99f1527c5a8ee77c890ea810e26b39fd0b4c2dffc062e20a05b2cca60ef",
"sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", "sha256:7593a49300489d064ebb6c58539f52cbbc4a2e6a4385de5e92cae1563f88a425",
"sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", "sha256:7844a8c6a0fee401edbf578713c2473e020759267c40261b294036f9d3eb6a2d",
"sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", "sha256:7af2f8e7bb54ace984de790e897f858e88068d8fbc46c9490b7c19c59cf51822",
"sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", "sha256:7dbda34e8e26bd86606ba8a9c13ccb114802e01758a3d0a75652ffc59a573220",
"sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", "sha256:82b58d37c47d93a171be9b5744bcc96a0012cbf53d5622b29a49e6be2097edd7",
"sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", "sha256:8305e14112efb74d0b5fec4df6e41cafde615c2392a7e51c84013cafe945842c",
"sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", "sha256:8426fec5ad5a6e8217921716b504e9b6e1166dc147e8443b4855e329db686282",
"sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", "sha256:88f1810eb942e7063d051d87aaaa113eb5fd5a7fd2cda03a972de57695b8bb1a",
"sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", "sha256:8da0c4a26a831b392deaba5fdd0cd7838d173b47ce2ec3d0f37be630cb09ef6e",
"sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", "sha256:a9dbfcbc56d8de5580483cf2caff6a59c64d3e88836cbe5fb5c20c05c29a8808",
"sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", "sha256:aa5d4d43fa18cc9d0c6e02a83de0b9729b5451a9066574bd276481474f0a53ab",
"sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", "sha256:adb0f4c3c8ba8104378518a1954cbf3d891a22c13fd0e0bf135391835f44f288",
"sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", "sha256:b4ee5815c776dfa3958ba71c7cd4cdd8eb40d79358a18352feb19562fe4408c4",
"sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", "sha256:b5dd5ae0a9cd55d71f1335c331e9625382239b8cede818fb62d8d2702336dbf8",
"sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", "sha256:b78dd3eeb8f5ff26d2113c41836bac04a9ea91be54c346826b54a373133c8c53",
"sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", "sha256:bea681309bdd88dd1283a8ba834632c43da376d9bce05820826090aad80c0126",
"sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", "sha256:befb5ffa9faabef6dadc42622c73de168001425258f0b7e402a2934574e7a04b",
"sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", "sha256:d795a2c92fe8cb31f6e9cd627ee4f39b64eb66bf47d89d8fcf7cb3d17031c887",
"sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", "sha256:d82cbef1220703ce56822be7fbddb40736fc1a928ac893472df8aff7421ae0aa",
"sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", "sha256:e63490e8a6675cee7a71393ee074586f7eeaf0e9341afd006c5d6f7eec7c16d7",
"sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", "sha256:e735ab8547d8a1fe8e58dd765d6f27ac539b395f52160d767b7189f379f9be7a",
"sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", "sha256:fa816e97cfe1f691423078dffa39a18106c176f28008db017b3ce3e947c34aa5",
"sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", "sha256:fff04bfefb879edcf616f1ce5ea6f4a693b5976bdc5e163f8464f349c25b59f0"
"sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
"sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
"sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
"sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
"sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
"sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
"sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
"sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
"sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
"sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
"sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
"sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
"sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
"sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
"sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
"sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
"sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.5" "version": "==6.0"
}, },
"iniconfig": { "iniconfig": {
"hashes": [ "hashes": [

View File

@ -78,7 +78,7 @@ GITHUB_REPO = environ.get('GITHUB_REPO', None)
TARGET_BRANCH = environ.get('TARGET_BRANCH', None) TARGET_BRANCH = environ.get('TARGET_BRANCH', None)
# Email configuration # Email configuration
DEFAULT_SENDER = 'askresearch@virginia.edu' DEFAULT_SENDER = 'uvacrconnect@virginia.edu'
FALLBACK_EMAILS = ['askresearch@virginia.edu', 'sartographysupport@googlegroups.com'] FALLBACK_EMAILS = ['askresearch@virginia.edu', 'sartographysupport@googlegroups.com']
MAIL_DEBUG = environ.get('MAIL_DEBUG', default=True) MAIL_DEBUG = environ.get('MAIL_DEBUG', default=True)
MAIL_SERVER = environ.get('MAIL_SERVER', default='smtp.mailtrap.io') MAIL_SERVER = environ.get('MAIL_SERVER', default='smtp.mailtrap.io')

View File

@ -51,18 +51,30 @@ class ApiError(Exception):
if "task" in task.data: if "task" in task.data:
task.data.pop("task") task.data.pop("task")
# In the unlikely event that the API error can't be serialized, try removing the task_data, as it may # Assure that there is nothing in the json data that can't be serialized.
# contain some invalid data that we can't return, so we can at least get the erro rmessage. instance.task_data = ApiError.remove_unserializeable_from_dict(task.data)
instance.task_data = task.data
try:
json.dumps(ApiErrorSchema().dump(instance))
except TypeError as te:
instance.task_data = {
'task_data_hidden': 'We were unable to serialize the task data when reporting this error'}
app.logger.error(message, exc_info=True) app.logger.error(message, exc_info=True)
return instance return instance
@staticmethod
def remove_unserializeable_from_dict(my_dict):
keys_to_delete = []
for key, value in my_dict.items():
if not ApiError.is_jsonable(value):
keys_to_delete.append(key)
for key in keys_to_delete:
del my_dict[key]
return my_dict
@staticmethod
def is_jsonable(x):
try:
json.dumps(x)
return True
except (TypeError, OverflowError):
return False
@classmethod @classmethod
def from_task_spec(cls, code, message, task_spec, status_code=400): def from_task_spec(cls, code, message, task_spec, status_code=400):
"""Constructs an API Error with details pulled from the current task.""" """Constructs an API Error with details pulled from the current task."""
@ -89,6 +101,8 @@ class ApiError(Exception):
return ApiError.from_task_spec(code, message, exp.sender) return ApiError.from_task_spec(code, message, exp.sender)
class ApiErrorSchema(ma.Schema): class ApiErrorSchema(ma.Schema):
class Meta: class Meta:
fields = ("code", "message", "workflow_name", "file_name", "task_name", "task_id", fields = ("code", "message", "workflow_name", "file_name", "task_name", "task_id",

View File

@ -5,7 +5,6 @@ import inspect
import os import os
import connexion import connexion
from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine
from flask import send_file from flask import send_file
from jinja2 import Template, UndefinedError from jinja2 import Template, UndefinedError
@ -92,7 +91,7 @@ def evaluate_python_expression(body):
of the same hash are unnecessary. """ of the same hash are unnecessary. """
try: try:
script_engine = CustomBpmnScriptEngine() script_engine = CustomBpmnScriptEngine()
result = script_engine.eval(body['expression'], body['data']) result = script_engine._evaluate(body['expression'], **body['data'])
return {"result": result, "expression": body['expression'], "key": body['key']} return {"result": result, "expression": body['expression'], "key": body['key']}
except Exception as e: except Exception as e:
return {"result": False, "expression": body['expression'], "key": body['key'], "error": str(e)} return {"result": False, "expression": body['expression'], "key": body['key'], "error": str(e)}

View File

@ -279,7 +279,6 @@ def update_task(workflow_id, task_id, body, terminate_loop=None, update_all=Fals
processor = WorkflowProcessor(workflow_model) processor = WorkflowProcessor(workflow_model)
task_id = uuid.UUID(task_id) task_id = uuid.UUID(task_id)
spiff_task = processor.bpmn_workflow.get_task(task_id) spiff_task = processor.bpmn_workflow.get_task(task_id)
spiff_task.workflow.script_engine = processor.bpmn_workflow.script_engine
_verify_user_and_role(processor, spiff_task) _verify_user_and_role(processor, spiff_task)
user = UserService.current_user(allow_admin_impersonate=False) # Always log as the real user. user = UserService.current_user(allow_admin_impersonate=False) # Always log as the real user.

View File

@ -153,7 +153,7 @@ def create_or_update_local_spec(remote,workflow_spec_id):
# Set the category # Set the category
if specdict['category'] is not None: if specdict['category'] is not None:
local_category = session.query(WorkflowSpecCategoryModel).\ local_category = session.query(WorkflowSpecCategoryModel).\
filter(WorkflowSpecCategoryModel.name == specdict['category']['name']).first() filter(WorkflowSpecCategoryModel.id == specdict['category']['id']).first()
local_category = WorkflowSpecCategoryModelSchema().load(specdict['category'], session=session, local_category = WorkflowSpecCategoryModelSchema().load(specdict['category'], session=session,
instance=local_category) instance=local_category)
session.add(local_category) session.add(local_category)

View File

@ -104,12 +104,11 @@ class StudyEvent(db.Model):
class WorkflowMetadata(object): class WorkflowMetadata(object):
def __init__(self, id, name = None, display_name = None, description = None, spec_version = None, def __init__(self, id, display_name = None, description = None, spec_version = None,
category_id = None, category_display_name = None, state: WorkflowState = None, category_id = None, category_display_name = None, state: WorkflowState = None,
status: WorkflowStatus = None, total_tasks = None, completed_tasks = None, status: WorkflowStatus = None, total_tasks = None, completed_tasks = None,
is_review=None,display_order = None, state_message = None): is_review=None,display_order = None, state_message = None, workflow_spec_id=None):
self.id = id self.id = id
self.name = name
self.display_name = display_name self.display_name = display_name
self.description = description self.description = description
self.spec_version = spec_version self.spec_version = spec_version
@ -122,6 +121,7 @@ class WorkflowMetadata(object):
self.completed_tasks = completed_tasks self.completed_tasks = completed_tasks
self.is_review = is_review self.is_review = is_review
self.display_order = display_order self.display_order = display_order
self.workflow_spec_id = workflow_spec_id
@classmethod @classmethod
@ -129,7 +129,6 @@ class WorkflowMetadata(object):
is_review = FileService.is_workflow_review(workflow.workflow_spec_id) is_review = FileService.is_workflow_review(workflow.workflow_spec_id)
instance = cls( instance = cls(
id=workflow.id, id=workflow.id,
name=workflow.workflow_spec.name,
display_name=workflow.workflow_spec.display_name, display_name=workflow.workflow_spec.display_name,
description=workflow.workflow_spec.description, description=workflow.workflow_spec.description,
spec_version=workflow.spec_version(), spec_version=workflow.spec_version(),
@ -140,7 +139,8 @@ class WorkflowMetadata(object):
total_tasks=workflow.total_tasks, total_tasks=workflow.total_tasks,
completed_tasks=workflow.completed_tasks, completed_tasks=workflow.completed_tasks,
is_review=is_review, is_review=is_review,
display_order=workflow.workflow_spec.display_order display_order=workflow.workflow_spec.display_order,
workflow_spec_id=workflow.workflow_spec_id
) )
return instance return instance
@ -150,7 +150,7 @@ class WorkflowMetadataSchema(ma.Schema):
status = EnumField(WorkflowStatus) status = EnumField(WorkflowStatus)
class Meta: class Meta:
model = WorkflowMetadata model = WorkflowMetadata
additional = ["id", "name", "display_name", "description", additional = ["id", "display_name", "description",
"total_tasks", "completed_tasks", "display_order", "total_tasks", "completed_tasks", "display_order",
"category_id", "is_review", "category_display_name", "state_message"] "category_id", "is_review", "category_display_name", "state_message"]
unknown = INCLUDE unknown = INCLUDE
@ -159,7 +159,6 @@ class WorkflowMetadataSchema(ma.Schema):
class Category(object): class Category(object):
def __init__(self, model: WorkflowSpecCategoryModel): def __init__(self, model: WorkflowSpecCategoryModel):
self.id = model.id self.id = model.id
self.name = model.name
self.display_name = model.display_name self.display_name = model.display_name
self.display_order = model.display_order self.display_order = model.display_order
self.admin = model.admin self.admin = model.admin
@ -169,7 +168,7 @@ class CategorySchema(ma.Schema):
workflows = fields.List(fields.Nested(WorkflowMetadataSchema), dump_only=True) workflows = fields.List(fields.Nested(WorkflowMetadataSchema), dump_only=True)
class Meta: class Meta:
model = Category model = Category
additional = ["id", "name", "display_name", "display_order", "admin"] additional = ["id", "display_name", "display_order", "admin"]
unknown = INCLUDE unknown = INCLUDE

View File

@ -13,11 +13,11 @@ from crc.models.file import FileModel, FileDataModel
class WorkflowSpecCategoryModel(db.Model): class WorkflowSpecCategoryModel(db.Model):
__tablename__ = 'workflow_spec_category' __tablename__ = 'workflow_spec_category'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
display_name = db.Column(db.String) display_name = db.Column(db.String)
display_order = db.Column(db.Integer) display_order = db.Column(db.Integer)
admin = db.Column(db.Boolean) admin = db.Column(db.Boolean)
class WorkflowSpecCategoryModelSchema(SQLAlchemyAutoSchema): class WorkflowSpecCategoryModelSchema(SQLAlchemyAutoSchema):
class Meta: class Meta:
model = WorkflowSpecCategoryModel model = WorkflowSpecCategoryModel
@ -25,11 +25,9 @@ class WorkflowSpecCategoryModelSchema(SQLAlchemyAutoSchema):
include_relationships = True include_relationships = True
class WorkflowSpecModel(db.Model): class WorkflowSpecModel(db.Model):
__tablename__ = 'workflow_spec' __tablename__ = 'workflow_spec'
id = db.Column(db.String, primary_key=True) id = db.Column(db.String, primary_key=True)
name = db.Column(db.String)
display_name = db.Column(db.String) display_name = db.Column(db.String)
display_order = db.Column(db.Integer, nullable=True) display_order = db.Column(db.Integer, nullable=True)
description = db.Column(db.Text) description = db.Column(db.Text)
@ -62,11 +60,9 @@ class WorkflowSpecModelSchema(SQLAlchemyAutoSchema):
category = marshmallow.fields.Nested(WorkflowSpecCategoryModelSchema, dump_only=True) category = marshmallow.fields.Nested(WorkflowSpecCategoryModelSchema, dump_only=True)
libraries = marshmallow.fields.Function(lambda obj: [{'id':x.library.id, libraries = marshmallow.fields.Function(lambda obj: [{'id':x.library.id,
'name':x.library.name,
'display_name':x.library.display_name} for x in 'display_name':x.library.display_name} for x in
obj.libraries] ) obj.libraries] )
parents = marshmallow.fields.Function(lambda obj: [{'id':x.parent.id, parents = marshmallow.fields.Function(lambda obj: [{'id':x.parent.id,
'name':x.parent.name,
'display_name':x.parent.display_name} for x in 'display_name':x.parent.display_name} for x in
obj.parents] ) obj.parents] )

View File

@ -13,13 +13,13 @@ class ResetWorkflow(Script):
I.e., a new PI""" I.e., a new PI"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs): def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
return hasattr(kwargs, 'workflow_name') return hasattr(kwargs, 'reset_id')
def do_task(self, task, study_id, workflow_id, *args, **kwargs): def do_task(self, task, study_id, workflow_id, *args, **kwargs):
if 'workflow_name' in kwargs.keys(): if 'reset_id' in kwargs.keys():
workflow_name = kwargs['workflow_name'] reset_id = kwargs['reset_id']
workflow_spec: WorkflowSpecModel = session.query(WorkflowSpecModel).filter_by(name=workflow_name).first() workflow_spec: WorkflowSpecModel = session.query(WorkflowSpecModel).filter_by(id=reset_id).first()
if workflow_spec: if workflow_spec:
workflow_model: WorkflowModel = session.query(WorkflowModel).filter_by( workflow_model: WorkflowModel = session.query(WorkflowModel).filter_by(
workflow_spec_id=workflow_spec.id, workflow_spec_id=workflow_spec.id,
@ -35,7 +35,7 @@ class ResetWorkflow(Script):
else: else:
raise ApiError(code='missing_workflow_spec', raise ApiError(code='missing_workflow_spec',
message=f'No WorkflowSpecModel returned. \ message=f'No WorkflowSpecModel returned. \
name: {workflow_name}') id: {workflow_id}')
else: else:
raise ApiError(code='missing_workflow_name', raise ApiError(code='missing_workflow_id',
message='Reset workflow requires a workflow name') message='Reset workflow requires a workflow id')

View File

@ -67,9 +67,13 @@ class LookupService(object):
# if not, we need to rebuild the lookup table. # if not, we need to rebuild the lookup table.
is_current = False is_current = False
if lookup_model: if lookup_model:
is_current = db.session.query(WorkflowSpecDependencyFile). \ if lookup_model.is_ldap: # LDAP is always current
filter(WorkflowSpecDependencyFile.file_data_id == lookup_model.file_data_model_id).\ is_current = True
filter(WorkflowSpecDependencyFile.workflow_id == workflow.id).count() else:
is_current = db.session.query(WorkflowSpecDependencyFile). \
filter(WorkflowSpecDependencyFile.file_data_id == lookup_model.file_data_model_id).\
filter(WorkflowSpecDependencyFile.workflow_id == workflow.id).count()
if not is_current: if not is_current:
# Very very very expensive, but we don't know need this till we do. # Very very very expensive, but we don't know need this till we do.
@ -138,6 +142,7 @@ class LookupService(object):
# Use the results of an LDAP request to populate enum field options # Use the results of an LDAP request to populate enum field options
elif field.has_property(Task.FIELD_PROP_LDAP_LOOKUP): elif field.has_property(Task.FIELD_PROP_LDAP_LOOKUP):
lookup_model = LookupFileModel(workflow_spec_id=workflow_model.workflow_spec_id, lookup_model = LookupFileModel(workflow_spec_id=workflow_model.workflow_spec_id,
task_spec_id=task_spec_id,
field_id=field_id, field_id=field_id,
is_ldap=True) is_ldap=True)

View File

@ -433,26 +433,26 @@ class StudyService(object):
for wfm in workflow_metas: for wfm in workflow_metas:
wfm.state_message = '' wfm.state_message = ''
# do we have a status for you # do we have a status for you
if wfm.name not in status.keys(): if wfm.workflow_spec_id not in status.keys():
warnings.append(ApiError("missing_status", "No status specified for workflow %s" % wfm.name)) warnings.append(ApiError("missing_status", "No status specified for workflow %s" % wfm.workflow_spec_id))
continue continue
if not isinstance(status[wfm.name], dict): if not isinstance(status[wfm.workflow_spec_id], dict):
warnings.append(ApiError(code='invalid_status', warnings.append(ApiError(code='invalid_status',
message=f'Status must be a dictionary with "status" and "message" keys. Name is {wfm.name}. Status is {status[wfm.name]}')) message=f'Status must be a dictionary with "status" and "message" keys. Name is {wfm.workflow_spec_id}. Status is {status[wfm.workflow_spec_id]}'))
continue continue
if 'status' not in status[wfm.name].keys(): if 'status' not in status[wfm.workflow_spec_id].keys():
warnings.append(ApiError("missing_status", warnings.append(ApiError("missing_status",
"Workflow '%s' does not have a status setting" % wfm.name)) "Workflow '%s' does not have a status setting" % wfm.workflow_spec_id))
continue continue
if not WorkflowState.has_value(status[wfm.name]['status']): if not WorkflowState.has_value(status[wfm.workflow_spec_id]['status']):
warnings.append(ApiError("invalid_state", warnings.append(ApiError("invalid_state",
"Workflow '%s' can not be set to '%s', should be one of %s" % ( "Workflow '%s' can not be set to '%s', should be one of %s" % (
wfm.name, status[wfm.name]['status'], ",".join(WorkflowState.list()) wfm.workflow_spec_id, status[wfm.workflow_spec_id]['status'], ",".join(WorkflowState.list())
))) )))
continue continue
wfm.state = WorkflowState[status[wfm.name]['status']] wfm.state = WorkflowState[status[wfm.workflow_spec_id]['status']]
if 'message' in status[wfm.name].keys(): if 'message' in status[wfm.workflow_spec_id].keys():
wfm.state_message = status[wfm.name]['message'] wfm.state_message = status[wfm.workflow_spec_id]['message']
return warnings return warnings
@staticmethod @staticmethod

View File

@ -41,7 +41,25 @@ class CustomBpmnScriptEngine(PythonScriptEngine):
Evaluate the given expression, within the context of the given task and Evaluate the given expression, within the context of the given task and
return the result. return the result.
""" """
return self.evaluate_expression(task, expression) study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
if WorkflowProcessor.WORKFLOW_ID_KEY in task.workflow.data:
workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
else:
workflow_id = None
try:
if task.workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY]:
augmentMethods = Script.generate_augmented_validate_list(task, study_id, workflow_id)
else:
augmentMethods = Script.generate_augmented_list(task, study_id, workflow_id)
return self._evaluate(expression, external_methods=augmentMethods, **task.data)
except Exception as e:
raise WorkflowTaskExecException(task,
"Error evaluating expression "
"'%s', %s" % (expression, str(e)))
@timeit @timeit
def execute(self, task: SpiffTask, script, data): def execute(self, task: SpiffTask, script, data):
@ -62,32 +80,6 @@ class CustomBpmnScriptEngine(PythonScriptEngine):
except Exception as e: except Exception as e:
raise WorkflowTaskExecException(task, f' {script}, {e}', e) raise WorkflowTaskExecException(task, f' {script}, {e}', e)
def evaluate_expression(self, task, expression):
"""
Evaluate the given expression, within the context of the given task and
return the result.
"""
study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
if WorkflowProcessor.WORKFLOW_ID_KEY in task.workflow.data:
workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
else:
workflow_id = None
try:
if task.workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY]:
augmentMethods = Script.generate_augmented_validate_list(task, study_id, workflow_id)
else:
augmentMethods = Script.generate_augmented_list(task, study_id, workflow_id)
exp, valid = self.validate_expression(expression)
return self._eval(exp, external_methods=augmentMethods, **task.data)
except Exception as e:
raise WorkflowTaskExecException(task,
"Error evaluating expression "
"'%s', %s" % (expression, str(e)))
def eval(self, exp, data):
return super()._eval(exp, {}, **data)
@ -463,12 +455,18 @@ class WorkflowProcessor(object):
return nav_item return nav_item
def find_spec_and_field(self, spec_name, field_id): def find_spec_and_field(self, spec_name, field_id):
"""Tracks down a form field by name in the workflow spec, """Tracks down a form field by name in the workflow spec(s),
only looks at ready tasks. Returns a tuple of the task, and form""" Returns a tuple of the task, and form"""
for spec in self.bpmn_workflow.spec.task_specs.values(): workflows = [self.bpmn_workflow]
if spec.name == spec_name and hasattr(spec, "form"): for task in self.bpmn_workflow.get_ready_user_tasks():
for field in spec.form.fields: if task.workflow not in workflows:
if field.id == field_id: workflows.append(task.workflow)
return spec, field
for workflow in workflows:
for spec in workflow.spec.task_specs.values():
if spec.name == spec_name and hasattr(spec, "form"):
for field in spec.form.fields:
if field.id == field_id:
return spec, field
raise ApiError("invalid_field", raise ApiError("invalid_field",
"Unable to find a task in the workflow with a lookup field called: %s" % field_id) "Unable to find a task in the workflow with a lookup field called: %s" % field_id)

View File

@ -1,4 +1,5 @@
import copy import copy
import json
import string import string
from datetime import datetime from datetime import datetime
import random import random
@ -115,7 +116,7 @@ class WorkflowService(object):
except Exception as e: except Exception as e:
app.logger.error(f"Error running waiting task for workflow #%i (%s) for study #%i. %s" % app.logger.error(f"Error running waiting task for workflow #%i (%s) for study #%i. %s" %
(workflow_model.id, (workflow_model.id,
workflow_model.workflow_spec.name, workflow_model.workflow_spec.id,
workflow_model.study_id, workflow_model.study_id,
str(e))) str(e)))
@ -137,8 +138,8 @@ class WorkflowService(object):
study_model = session.query(StudyModel).filter(StudyModel.id == validate_study_id).first() study_model = session.query(StudyModel).filter(StudyModel.id == validate_study_id).first()
spec_model = session.query(WorkflowSpecModel).filter(WorkflowSpecModel.id == spec_id).first() spec_model = session.query(WorkflowSpecModel).filter(WorkflowSpecModel.id == spec_id).first()
status = StudyService._get_study_status(study_model) status = StudyService._get_study_status(study_model)
if spec_model.name in status and status[spec_model.name]['status'] == 'disabled': if spec_model.id in status and status[spec_model.id]['status'] == 'disabled':
raise ApiError(code='disabled_workflow', message=f"This workflow is disabled. {status[spec_model.name]['message']}") raise ApiError(code='disabled_workflow', message=f"This workflow is disabled. {status[spec_model.id]['message']}")
workflow_model = WorkflowService.make_test_workflow(spec_id, validate_study_id) workflow_model = WorkflowService.make_test_workflow(spec_id, validate_study_id)
try: try:
processor = WorkflowProcessor(workflow_model, validate_only=True) processor = WorkflowProcessor(workflow_model, validate_only=True)
@ -149,7 +150,7 @@ class WorkflowService(object):
exit_task = processor.bpmn_workflow.do_engine_steps(exit_at=test_until) exit_task = processor.bpmn_workflow.do_engine_steps(exit_at=test_until)
if (exit_task != None): if (exit_task != None):
raise ApiError.from_task("validation_break", raise ApiError.from_task("validation_break",
f"The validation has been exited early on task '{exit_task.task_spec.name}' and was parented by ", f"The validation has been exited early on task '{exit_task.task_spec.id}' and was parented by ",
exit_task.parent) exit_task.parent)
tasks = processor.bpmn_workflow.get_tasks(SpiffTask.READY) tasks = processor.bpmn_workflow.get_tasks(SpiffTask.READY)
for task in tasks: for task in tasks:
@ -277,7 +278,11 @@ class WorkflowService(object):
form_data[field.id] = WorkflowService.get_random_data_for_field(field, task) form_data[field.id] = WorkflowService.get_random_data_for_field(field, task)
if task.data is None: if task.data is None:
task.data = {} task.data = {}
task.data.update(form_data)
# jsonify, and de-jsonify the data to mimic how data will be returned from the front end for forms and assures
# we aren't generating something that can't be serialized.
form_data_string = json.dumps(form_data)
task.data.update(json.loads(form_data_string))
@staticmethod @staticmethod
def check_field_id(id): def check_field_id(id):
@ -331,7 +336,7 @@ class WorkflowService(object):
# This is generally handled by the front end, but it is possible that the file was uploaded BEFORE # This is generally handled by the front end, but it is possible that the file was uploaded BEFORE
# the doc_code was correctly set, so this is a stop gap measure to assure we still hit it correctly. # the doc_code was correctly set, so this is a stop gap measure to assure we still hit it correctly.
file_id = data[field.id]["id"] file_id = data[field.id]["id"]
doc_code = task.workflow.script_engine.eval(field.get_property(Task.FIELD_PROP_DOC_CODE), data) doc_code = task.workflow.script_engine._evaluate(field.get_property(Task.FIELD_PROP_DOC_CODE), **data)
file = db.session.query(FileModel).filter(FileModel.id == file_id).first() file = db.session.query(FileModel).filter(FileModel.id == file_id).first()
if(file): if(file):
file.irb_doc_code = doc_code file.irb_doc_code = doc_code
@ -369,7 +374,7 @@ class WorkflowService(object):
return None # We may not have enough information to process this return None # We may not have enough information to process this
try: try:
return task.workflow.script_engine.eval(expression, data) return task.workflow.script_engine._evaluate(expression, **data)
except Exception as e: except Exception as e:
message = f"The field {field.id} contains an invalid expression. {e}" message = f"The field {field.id} contains an invalid expression. {e}"
raise ApiError.from_task(f'invalid_{property_name}', message, task=task) raise ApiError.from_task(f'invalid_{property_name}', message, task=task)
@ -435,6 +440,8 @@ class WorkflowService(object):
if default == 'true' or default == 't': if default == 'true' or default == 't':
return True return True
return False return False
elif field.type == 'date' and isinstance(default, datetime):
return default.isoformat()
else: else:
return default return default
@ -484,8 +491,6 @@ class WorkflowService(object):
return [random_value] return [random_value]
else: else:
return random_value return random_value
elif field.type == "long": elif field.type == "long":
return random.randint(1, 1000) return random.randint(1, 1000)
elif field.type == 'boolean': elif field.type == 'boolean':
@ -690,7 +695,7 @@ class WorkflowService(object):
# a BPMN standard, and should not be included in the display. # a BPMN standard, and should not be included in the display.
if task.properties and "display_name" in task.properties: if task.properties and "display_name" in task.properties:
try: try:
task.title = spiff_task.workflow.script_engine.evaluate_expression(spiff_task, task.properties[Task.PROP_EXTENSIONS_TITLE]) task.title = spiff_task.workflow.script_engine.evaluate(spiff_task, task.properties[Task.PROP_EXTENSIONS_TITLE])
except Exception as e: except Exception as e:
# if the task is ready, we should raise an error, but if it is in the future or the past, we may not # if the task is ready, we should raise an error, but if it is in the future or the past, we may not
# have the information we need to properly set the title, so don't error out, and just use what is # have the information we need to properly set the title, so don't error out, and just use what is

View File

@ -30,43 +30,36 @@ class ExampleDataLoader:
categories = [ categories = [
WorkflowSpecCategoryModel( WorkflowSpecCategoryModel(
id=0, id=0,
name='irb_review',
display_name='From PB', display_name='From PB',
display_order=0 display_order=0
), ),
WorkflowSpecCategoryModel( WorkflowSpecCategoryModel(
id=1, id=1,
name='core_info',
display_name='Core Info', display_name='Core Info',
display_order=1 display_order=1
), ),
WorkflowSpecCategoryModel( WorkflowSpecCategoryModel(
id=2, id=2,
name='approvals',
display_name='Approvals', display_name='Approvals',
display_order=2 display_order=2
), ),
WorkflowSpecCategoryModel( WorkflowSpecCategoryModel(
id=3, id=3,
name='data_security_plan',
display_name='Data Security Plan', display_name='Data Security Plan',
display_order=3 display_order=3
), ),
WorkflowSpecCategoryModel( WorkflowSpecCategoryModel(
id=4, id=4,
name='finance',
display_name='Finance', display_name='Finance',
display_order=4 display_order=4
), ),
WorkflowSpecCategoryModel( WorkflowSpecCategoryModel(
id=5, id=5,
name='notifications',
display_name='Notifications', display_name='Notifications',
display_order=5 display_order=5
), ),
WorkflowSpecCategoryModel( WorkflowSpecCategoryModel(
id=6, id=6,
name='status',
display_name='Status', display_name='Status',
display_order=6 display_order=6
), ),
@ -77,31 +70,26 @@ class ExampleDataLoader:
# Pass IRB Review # Pass IRB Review
self.create_spec(id="irb_api_personnel", self.create_spec(id="irb_api_personnel",
name="irb_api_personnel",
display_name="Personnel", display_name="Personnel",
description="TBD", description="TBD",
category_id=0, category_id=0,
display_order=0) display_order=0)
self.create_spec(id="irb_api_details", self.create_spec(id="irb_api_details",
name="irb_api_details",
display_name="Protocol Builder Data", display_name="Protocol Builder Data",
description="TBD", description="TBD",
category_id=0, category_id=0,
display_order=1) display_order=1)
self.create_spec(id="documents_approvals", self.create_spec(id="documents_approvals",
name="documents_approvals",
display_name="Documents & Approvals", display_name="Documents & Approvals",
description="Status of all approvals and documents required from Protocol Builder", description="Status of all approvals and documents required from Protocol Builder",
category_id=0, category_id=0,
display_order=2) display_order=2)
self.create_spec(id="ide_supplement", self.create_spec(id="ide_supplement",
name="ide_supplement",
display_name="IDE Supplement Info", display_name="IDE Supplement Info",
description="Supplemental information for the IDE number entered in Protocol Builder", description="Supplemental information for the IDE number entered in Protocol Builder",
category_id=0, category_id=0,
display_order=3) display_order=3)
self.create_spec(id="ind_update", self.create_spec(id="ind_update",
name="ind_update",
display_name="IND Supplement Info", display_name="IND Supplement Info",
description="Supplement information for the Investigational New Drug(s) specified in Protocol Builder", description="Supplement information for the Investigational New Drug(s) specified in Protocol Builder",
category_id=0, category_id=0,
@ -109,19 +97,16 @@ class ExampleDataLoader:
# Core Info # Core Info
self.create_spec(id="protocol", self.create_spec(id="protocol",
name="protocol",
display_name="Protocol", display_name="Protocol",
description="Upload the Study Protocol here.", description="Upload the Study Protocol here.",
category_id=1, category_id=1,
display_order=0) display_order=0)
self.create_spec(id="non_uva_approval", self.create_spec(id="non_uva_approval",
name="non_uva",
display_name="Non-UVA Institutional Approval", display_name="Non-UVA Institutional Approval",
description="TBD", description="TBD",
category_id=1, category_id=1,
display_order=1) display_order=1)
self.create_spec(id="core_info", self.create_spec(id="core_info",
name="core_info",
display_name="Core Info", display_name="Core Info",
description="TBD", description="TBD",
category_id=1, category_id=1,
@ -129,31 +114,26 @@ class ExampleDataLoader:
# Approvals # Approvals
self.create_spec(id="ids_full_submission", self.create_spec(id="ids_full_submission",
name="ids_full_submission",
display_name="Investigational Drug Service (IDS) Full Submission", display_name="Investigational Drug Service (IDS) Full Submission",
description="TBD", description="TBD",
category_id=2, category_id=2,
display_order=0) display_order=0)
self.create_spec(id="ids_waiver", self.create_spec(id="ids_waiver",
name="ids_waiver",
display_name="Investigational Drug Service (IDS) Waiver", display_name="Investigational Drug Service (IDS) Waiver",
description="TBD", description="TBD",
category_id=2, category_id=2,
display_order=1) display_order=1)
self.create_spec(id="rsc_hire_submission", self.create_spec(id="rsc_hire_submission",
name="rsc_hire_submission",
display_name="RSC/HIRE Submission", display_name="RSC/HIRE Submission",
description="TBD", description="TBD",
category_id=2, category_id=2,
display_order=2) display_order=2)
self.create_spec(id="rsc_hire_committee", self.create_spec(id="rsc_hire_committee",
name="rsc_hire_committee",
display_name="RSC/HIRE Committee", display_name="RSC/HIRE Committee",
description="TBD", description="TBD",
category_id=2, category_id=2,
display_order=3) display_order=3)
self.create_spec(id="department_chair_approval", self.create_spec(id="department_chair_approval",
name="department_chair_approval",
display_name="Department Chair Approval", display_name="Department Chair Approval",
description="TBD", description="TBD",
category_id=2, category_id=2,
@ -161,7 +141,6 @@ class ExampleDataLoader:
# Data Security Plan # Data Security Plan
self.create_spec(id="data_security_plan", self.create_spec(id="data_security_plan",
name="data_security_plan",
display_name="Data Security Plan", display_name="Data Security Plan",
description="Create and generate Data Security Plan", description="Create and generate Data Security Plan",
category_id=3, category_id=3,
@ -169,13 +148,11 @@ class ExampleDataLoader:
# Finance # Finance
self.create_spec(id="sponsor_funding_source", self.create_spec(id="sponsor_funding_source",
name="sponsor_funding_source",
display_name="Sponsor Funding Source", display_name="Sponsor Funding Source",
description="TBD", description="TBD",
category_id=4, category_id=4,
display_order=0) display_order=0)
self.create_spec(id="finance", self.create_spec(id="finance",
name="finance",
display_name="Finance Data", display_name="Finance Data",
description="TBD", description="TBD",
category_id=4, category_id=4,
@ -183,7 +160,6 @@ class ExampleDataLoader:
# Notifications # Notifications
self.create_spec(id="notifications", self.create_spec(id="notifications",
name="notifications",
display_name="Notifications", display_name="Notifications",
description="TBD", description="TBD",
category_id=5, category_id=5,
@ -191,13 +167,11 @@ class ExampleDataLoader:
# Status # Status
self.create_spec(id="enrollment_date", self.create_spec(id="enrollment_date",
name="enrollment_date",
display_name="Enrollment Date", display_name="Enrollment Date",
description="Study enrollment date", description="Study enrollment date",
category_id=6, category_id=6,
display_order=0) display_order=0)
self.create_spec(id="abandoned", self.create_spec(id="abandoned",
name="abandoned",
display_name="Abandoned", display_name="Abandoned",
description="Place study into Abandoned status", description="Place study into Abandoned status",
category_id=6, category_id=6,
@ -205,7 +179,6 @@ class ExampleDataLoader:
# Top Level (Master Status) Workflow # Top Level (Master Status) Workflow
self.create_spec(id="top_level_workflow", self.create_spec(id="top_level_workflow",
name="top_level_workflow",
display_name="Top Level Workflow", display_name="Top Level Workflow",
description="Determines the status of other workflows in a study", description="Determines the status of other workflows in a study",
category_id=None, category_id=None,
@ -221,7 +194,6 @@ class ExampleDataLoader:
category = WorkflowSpecCategoryModel( category = WorkflowSpecCategoryModel(
id=0, id=0,
name='research_rampup_category',
display_name='Research Ramp-up Category', display_name='Research Ramp-up Category',
display_order=0 display_order=0
) )
@ -229,14 +201,12 @@ class ExampleDataLoader:
db.session.commit() db.session.commit()
self.create_spec(id="rrt_top_level_workflow", self.create_spec(id="rrt_top_level_workflow",
name="rrt_top_level_workflow",
display_name="Top Level Workflow", display_name="Top Level Workflow",
description="Does nothing, we don't use the master workflow here.", description="Does nothing, we don't use the master workflow here.",
category_id=None, category_id=None,
master_spec=True) master_spec=True)
self.create_spec(id="research_rampup", self.create_spec(id="research_rampup",
name="research_rampup",
display_name="Research Ramp-up Toolkit", display_name="Research Ramp-up Toolkit",
description="Process for creating a new research ramp-up request.", description="Process for creating a new research ramp-up request.",
category_id=0, category_id=0,
@ -247,7 +217,6 @@ class ExampleDataLoader:
category = WorkflowSpecCategoryModel( category = WorkflowSpecCategoryModel(
id=0, id=0,
name='test_category',
display_name='Test Category', display_name='Test Category',
display_order=0, display_order=0,
admin=False admin=False
@ -256,7 +225,6 @@ class ExampleDataLoader:
db.session.commit() db.session.commit()
self.create_spec(id="empty_workflow", self.create_spec(id="empty_workflow",
name="empty_workflow",
display_name="Top Level Workflow", display_name="Top Level Workflow",
description="Does nothing, we don't use the master workflow here.", description="Does nothing, we don't use the master workflow here.",
category_id=None, category_id=None,
@ -264,7 +232,6 @@ class ExampleDataLoader:
from_tests = True) from_tests = True)
self.create_spec(id="random_fact", self.create_spec(id="random_fact",
name="random_fact",
display_name="Random Fact", display_name="Random Fact",
description="The workflow for a Random Fact.", description="The workflow for a Random Fact.",
category_id=0, category_id=0,
@ -272,7 +239,7 @@ class ExampleDataLoader:
master_spec=False, master_spec=False,
from_tests=True) from_tests=True)
def create_spec(self, id, name, display_name="", description="", filepath=None, master_spec=False, def create_spec(self, id, display_name="", description="", filepath=None, master_spec=False,
category_id=None, display_order=None, from_tests=False, standalone=False, library=False): category_id=None, display_order=None, from_tests=False, standalone=False, library=False):
"""Assumes that a directory exists in static/bpmn with the same name as the given id. """Assumes that a directory exists in static/bpmn with the same name as the given id.
further assumes that the [id].bpmn is the primary file for the workflow. further assumes that the [id].bpmn is the primary file for the workflow.
@ -280,7 +247,6 @@ class ExampleDataLoader:
global file global file
file_service = FileService() file_service = FileService()
spec = WorkflowSpecModel(id=id, spec = WorkflowSpecModel(id=id,
name=name,
display_name=display_name, display_name=display_name,
description=description, description=description,
is_master_spec=master_spec, is_master_spec=master_spec,

View File

@ -0,0 +1,27 @@
"""remove name from spec and category
Revision ID: 8580676e5302
Revises: 5c63a89ee7b7
Create Date: 2021-10-04 11:58:41.290139
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8580676e5302'
down_revision = '5c63a89ee7b7'
branch_labels = None
depends_on = None
def upgrade():
op.drop_column('workflow_spec_category', 'name')
op.drop_column('workflow_spec', 'name')
def downgrade():
op.add_column('workflow_spec_category', sa.Column('name', sa.String()))
op.add_column('workflow_spec', sa.Column('name', sa.String()))

View File

@ -0,0 +1,82 @@
"""empty message
Revision ID: ac1141d29d37
Revises: 8580676e5302
Create Date: 2021-10-06 14:05:58.062277
"""
import re
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
from crc.models.workflow import WorkflowSpecModel, WorkflowModel
revision = 'ac1141d29d37'
down_revision = '8580676e5302'
branch_labels = None
depends_on = None
def upgrade():
print("Doing the upgrade")
op.execute('ALTER TABLE workflow DROP CONSTRAINT workflow_workflow_spec_id_fkey')
op.execute('ALTER TABLE file DROP CONSTRAINT file_workflow_spec_id_fkey')
op.execute('ALTER TABLE workflow_library DROP CONSTRAINT workflow_library_workflow_spec_id_fkey')
op.execute('ALTER TABLE workflow_library DROP CONSTRAINT workflow_library_library_spec_id_fkey')
op.execute('ALTER TABLE task_event DROP CONSTRAINT task_event_workflow_spec_id_fkey')
# Use Alchemy's connection and transaction to noodle over the data.
connection = op.get_bind()
# Select all existing names that need migrating.
results = connection.execute(sa.select([
WorkflowSpecModel.id,
WorkflowSpecModel.display_name,
])).fetchall()
# Iterate over all selected data tuples.
for id, display_name in results:
new_id = display_name.lower().\
replace(",", "").\
replace("'", "").\
replace(" ", "_").\
replace("-", "_").\
replace(".", "_").\
replace("/","_").\
replace("\\", "_")
old_id = id
op.execute("Update workflow_spec set id='%s' where id='%s'" % (new_id, old_id))
op.execute("Update workflow set workflow_spec_id='%s' where workflow_spec_id='%s'" % (new_id, old_id))
op.execute("Update file set workflow_spec_id='%s' where workflow_spec_id='%s'" % (new_id, old_id))
op.execute("Update workflow_library set workflow_spec_id='%s' where workflow_spec_id='%s'" % (new_id, old_id))
op.execute("Update workflow_library set library_spec_id='%s' where library_spec_id='%s'" % (new_id, old_id))
op.execute("Update task_event set workflow_spec_id='%s' where workflow_spec_id='%s'" % (new_id, old_id))
op.create_foreign_key(
'workflow_workflow_spec_id_fkey',
'workflow', 'workflow_spec',
['workflow_spec_id'], ['id'],
)
op.create_foreign_key(
'file_workflow_spec_id_fkey',
'file', 'workflow_spec',
['workflow_spec_id'], ['id'],
)
op.create_foreign_key(
'workflow_library_workflow_spec_id_fkey',
'workflow_library', 'workflow_spec',
['workflow_spec_id'], ['id'],
)
op.create_foreign_key(
'workflow_library_library_spec_id_fkey',
'workflow_library', 'workflow_spec',
['library_spec_id'], ['id'],
)
op.create_foreign_key(
'task_event_workflow_spec_id_fkey',
'task_event', 'workflow_spec',
['workflow_spec_id'], ['id'],
)
def downgrade():
pass

View File

@ -178,7 +178,7 @@ class BaseTest(unittest.TestCase):
def load_test_spec(dir_name, display_name=None, master_spec=False, category_id=None): def load_test_spec(dir_name, display_name=None, master_spec=False, category_id=None):
"""Loads a spec into the database based on a directory in /tests/data""" """Loads a spec into the database based on a directory in /tests/data"""
if category_id is None: if category_id is None:
category = WorkflowSpecCategoryModel(name="test", display_name="Test Workflows", display_order=0) category = WorkflowSpecCategoryModel(display_name="Test Workflows", display_order=0)
session.add(category) session.add(category)
session.commit() session.commit()
category_id = category.id category_id = category.id
@ -188,7 +188,7 @@ class BaseTest(unittest.TestCase):
filepath = os.path.join(app.root_path, '..', 'tests', 'data', dir_name, "*") filepath = os.path.join(app.root_path, '..', 'tests', 'data', dir_name, "*")
if display_name is None: if display_name is None:
display_name = dir_name display_name = dir_name
return ExampleDataLoader().create_spec(id=dir_name, name=dir_name, filepath=filepath, master_spec=master_spec, return ExampleDataLoader().create_spec(id=dir_name, filepath=filepath, master_spec=master_spec,
display_name=display_name, category_id=category_id) display_name=display_name, category_id=category_id)
@staticmethod @staticmethod
@ -274,13 +274,13 @@ class BaseTest(unittest.TestCase):
return study return study
def create_workflow(self, workflow_name, display_name=None, study=None, category_id=None, as_user="dhf8r"): def create_workflow(self, dir_name, display_name=None, study=None, category_id=None, as_user="dhf8r"):
session.flush() session.flush()
spec = session.query(WorkflowSpecModel).filter(WorkflowSpecModel.name == workflow_name).first() spec = session.query(WorkflowSpecModel).filter(WorkflowSpecModel.id == dir_name).first()
if spec is None: if spec is None:
if display_name is None: if display_name is None:
display_name = workflow_name display_name = dir_name
spec = self.load_test_spec(workflow_name, display_name, category_id=category_id) spec = self.load_test_spec(dir_name, display_name, category_id=category_id)
if study is None: if study is None:
study = self.create_study(uid=as_user) study = self.create_study(uid=as_user)
workflow_model = StudyService._create_workflow_model(study, spec) workflow_model = StudyService._create_workflow_model(study, spec)

View File

@ -0,0 +1,65 @@
<?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:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1717350" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0">
<bpmn:process id="Process_1y3o9tq" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0ecke9e</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0ecke9e" sourceRef="StartEvent_1" targetRef="Date_Value_Expression" />
<bpmn:userTask id="Date_Value_Expression" name="Date Form" camunda:formKey="My Form">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="my_date" label="my Date" type="date">
<camunda:properties>
<camunda:property id="value_expression" value="datetime.datetime.now()" />
</camunda:properties>
<camunda:validation>
<camunda:constraint name="required" config="True" />
</camunda:validation>
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0ecke9e</bpmn:incoming>
<bpmn:outgoing>Flow_04yzu4r</bpmn:outgoing>
</bpmn:userTask>
<bpmn:endEvent id="Event_06knzzw">
<bpmn:documentation>The Date is {{my_date}}</bpmn:documentation>
<bpmn:incoming>Flow_1hz2cs6</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_04yzu4r" sourceRef="Date_Value_Expression" targetRef="Activity_0irsthq" />
<bpmn:sequenceFlow id="Flow_1hz2cs6" sourceRef="Activity_0irsthq" targetRef="Event_06knzzw" />
<bpmn:scriptTask id="Activity_0irsthq" name="Date Script">
<bpmn:incoming>Flow_04yzu4r</bpmn:incoming>
<bpmn:outgoing>Flow_1hz2cs6</bpmn:outgoing>
<bpmn:script>dateparser.parse(my_date)
</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1y3o9tq">
<bpmndi:BPMNEdge id="Flow_0ecke9e_di" bpmnElement="Flow_0ecke9e">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_04yzu4r_di" bpmnElement="Flow_04yzu4r">
<di:waypoint x="370" y="117" />
<di:waypoint x="420" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1hz2cs6_di" bpmnElement="Flow_1hz2cs6">
<di:waypoint x="520" y="117" />
<di:waypoint x="542" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0myo4ou_di" bpmnElement="Date_Value_Expression">
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_06knzzw_di" bpmnElement="Event_06knzzw">
<dc:Bounds x="542" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_01ztf01_di" bpmnElement="Activity_0irsthq">
<dc:Bounds x="420" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -29,5 +29,8 @@ class TestEmailScript(BaseTest):
self.assertIn(task_data['ApprvlApprvr1'], outbox[0].body) self.assertIn(task_data['ApprvlApprvr1'], outbox[0].body)
self.assertIn(task_data['ApprvlApprvr1'], outbox[0].html) self.assertIn(task_data['ApprvlApprvr1'], outbox[0].html)
# Correct From field
self.assertEqual('uvacrconnect@virginia.edu', outbox[0].sender)
db_emails = EmailModel.query.count() db_emails = EmailModel.query.count()
self.assertEqual(db_emails, 1) self.assertEqual(db_emails, 1)

View File

@ -28,7 +28,7 @@ class TestFilesApi(BaseTest):
self.assertEqual(5, len(json_data)) self.assertEqual(5, len(json_data))
files = FileModelSchema(many=True).load(json_data, session=session) files = FileModelSchema(many=True).load(json_data, session=session)
file_names = [f.name for f in files] file_names = [f.name for f in files]
self.assertTrue("%s.bpmn" % spec.name in file_names) self.assertTrue("%s.bpmn" % spec.id in file_names)
def test_list_multiple_files_for_workflow_spec(self): def test_list_multiple_files_for_workflow_spec(self):
self.load_example_data() self.load_example_data()

View File

@ -67,12 +67,11 @@ class TestStudyApi(BaseTest):
# Categories are read only, so switching to sub-scripting here. # Categories are read only, so switching to sub-scripting here.
# This assumes there is one test category set up in the example data. # This assumes there is one test category set up in the example data.
category = study.categories[0] category = study.categories[0]
self.assertEqual("test_category", category['name'])
self.assertEqual("Test Category", category['display_name']) self.assertEqual("Test Category", category['display_name'])
self.assertEqual(False, category['admin']) self.assertEqual(False, category['admin'])
self.assertEqual(1, len(category["workflows"])) self.assertEqual(1, len(category["workflows"]))
workflow = category["workflows"][0] workflow = category["workflows"][0]
self.assertEqual("random_fact", workflow["name"]) self.assertEqual("Random Fact", workflow["display_name"])
self.assertEqual("optional", workflow["state"]) self.assertEqual("optional", workflow["state"])
self.assertEqual("not_started", workflow["status"]) self.assertEqual("not_started", workflow["status"])
self.assertEqual(0, workflow["total_tasks"]) self.assertEqual(0, workflow["total_tasks"])

View File

@ -26,7 +26,7 @@ class TestStudyService(BaseTest):
# Assure some basic models are in place, This is a damn mess. Our database models need an overhaul to make # Assure some basic models are in place, This is a damn mess. Our database models need an overhaul to make
# this easier - better relationship modeling is now critical. # this easier - better relationship modeling is now critical.
cat = WorkflowSpecCategoryModel(name="approvals", display_name="Approvals", display_order=0) cat = WorkflowSpecCategoryModel(id=None, display_name="Approvals", display_order=0)
db.session.add(cat) db.session.add(cat)
db.session.commit() db.session.commit()
self.load_test_spec("top_level_workflow", master_spec=True, category_id=cat.id) self.load_test_spec("top_level_workflow", master_spec=True, category_id=cat.id)
@ -265,4 +265,4 @@ class TestStudyService(BaseTest):
self.assertEquals(1, len(associates)) self.assertEquals(1, len(associates))
assoc_json = StudyAssociatedSchema(many=True).dump(associates) assoc_json = StudyAssociatedSchema(many=True).dump(associates)
print(assoc_json) print(assoc_json)
self.assertEquals("Dan", assoc_json[0]['ldap_info']['given_name']) self.assertEquals("Dan", assoc_json[0]['ldap_info']['given_name'])

View File

@ -12,7 +12,7 @@ class TestAutoSetPrimaryBPMN(BaseTest):
self.load_example_data() self.load_example_data()
category_id = session.query(WorkflowSpecCategoryModel).first().id category_id = session.query(WorkflowSpecCategoryModel).first().id
# Add a workflow spec # Add a workflow spec
spec = WorkflowSpecModel(id='make_cookies', name='make_cookies', display_name='Cooooookies', spec = WorkflowSpecModel(id='make_cookies', display_name='Cooooookies',
description='Om nom nom delicious cookies', category_id=category_id, description='Om nom nom delicious cookies', category_id=category_id,
standalone=False) standalone=False)
rv = self.app.post('/v1.0/workflow-specification', rv = self.app.post('/v1.0/workflow-specification',

View File

@ -102,10 +102,10 @@ class TestTasksApi(BaseTest):
# You have a task called "Approval" to be completed in the "Supervisor Approval" workflow # 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)", # for the study 'Why dogs are stinky' managed by user "Jane Smith (js42x)",
# please check here to complete the task. # 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. # Just checking display_name for workflow, 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 # 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. # uid back, but could query to get the full entry.
self.assertEqual("roles", tasks[0]['workflow']['name']) self.assertEqual("Roles", tasks[0]['workflow']['display_name'])
self.assertEqual("Beer consumption in the bipedal software engineer", tasks[0]['study']['title']) self.assertEqual("Beer consumption in the bipedal software engineer", tasks[0]['study']['title'])
self.assertEqual("lje5u", tasks[0]['study']['user_uid']) self.assertEqual("lje5u", tasks[0]['study']['user_uid'])

View File

@ -348,7 +348,7 @@ class TestWorkflowProcessor(BaseTest):
study = session.query(StudyModel).first() study = session.query(StudyModel).first()
workflow_spec_model = db.session.query(WorkflowSpecModel).\ workflow_spec_model = db.session.query(WorkflowSpecModel).\
filter(WorkflowSpecModel.name == "top_level_workflow").first() filter(WorkflowSpecModel.id == "top_level_workflow").first()
self.assertIsNotNone(workflow_spec_model) self.assertIsNotNone(workflow_spec_model)
processor = self.get_processor(study, workflow_spec_model) processor = self.get_processor(study, workflow_spec_model)

View File

@ -22,7 +22,7 @@ class TestWorkflowReset(BaseTest):
second_task = workflow_api.next_task second_task = workflow_api.next_task
self.assertEqual('Task_GetAge', second_task.name) self.assertEqual('Task_GetAge', second_task.name)
ResetWorkflow().do_task(second_task, workflow.study_id, workflow.id, workflow_name='two_user_tasks') ResetWorkflow().do_task(second_task, workflow.study_id, workflow.id, reset_id='two_user_tasks')
workflow_api = self.get_workflow_api(workflow) workflow_api = self.get_workflow_api(workflow)
task = workflow_api.next_task task = workflow_api.next_task
@ -42,4 +42,4 @@ class TestWorkflowReset(BaseTest):
first_task = workflow_api.next_task first_task = workflow_api.next_task
with self.assertRaises(ApiError): with self.assertRaises(ApiError):
ResetWorkflow().do_task(first_task, workflow.study_id, workflow.id, workflow_name='bad_workflow_name') ResetWorkflow().do_task(first_task, workflow.study_id, workflow.id, reset_id='bad_workflow_name')

View File

@ -29,7 +29,7 @@ class TestWorkflowSpec(BaseTest):
num_before = session.query(WorkflowSpecModel).count() num_before = session.query(WorkflowSpecModel).count()
category_id = session.query(WorkflowSpecCategoryModel).first().id category_id = session.query(WorkflowSpecCategoryModel).first().id
category_count = session.query(WorkflowSpecModel).filter_by(category_id=category_id).count() category_count = session.query(WorkflowSpecModel).filter_by(category_id=category_id).count()
spec = WorkflowSpecModel(id='make_cookies', name='make_cookies', display_name='Cooooookies', spec = WorkflowSpecModel(id='make_cookies', display_name='Cooooookies',
description='Om nom nom delicious cookies', category_id=category_id, description='Om nom nom delicious cookies', category_id=category_id,
standalone=False) standalone=False)
rv = self.app.post('/v1.0/workflow-specification', rv = self.app.post('/v1.0/workflow-specification',
@ -58,7 +58,7 @@ class TestWorkflowSpec(BaseTest):
self.load_example_data() self.load_example_data()
category_id = 99 category_id = 99
category = WorkflowSpecCategoryModel(id=category_id, name='trap', display_name="It's a trap!", display_order=0) category = WorkflowSpecCategoryModel(id=category_id, display_name="It's a trap!", display_order=0)
session.add(category) session.add(category)
session.commit() session.commit()
@ -107,13 +107,13 @@ class TestWorkflowSpec(BaseTest):
def test_display_order_after_delete_spec(self): def test_display_order_after_delete_spec(self):
self.load_example_data() self.load_example_data()
workflow_spec_category = session.query(WorkflowSpecCategoryModel).first() workflow_spec_category = session.query(WorkflowSpecCategoryModel).first()
spec_model_1 = WorkflowSpecModel(id='test_spec_1', name='test_spec_1', display_name='Test Spec 1', spec_model_1 = WorkflowSpecModel(id='test_spec_1', display_name='Test Spec 1',
description='Test Spec 1 Description', category_id=workflow_spec_category.id, description='Test Spec 1 Description', category_id=workflow_spec_category.id,
display_order=1, standalone=False) display_order=1, standalone=False)
spec_model_2 = WorkflowSpecModel(id='test_spec_2', name='test_spec_2', display_name='Test Spec 2', spec_model_2 = WorkflowSpecModel(id='test_spec_2', display_name='Test Spec 2',
description='Test Spec 2 Description', category_id=workflow_spec_category.id, description='Test Spec 2 Description', category_id=workflow_spec_category.id,
display_order=2, standalone=False) display_order=2, standalone=False)
spec_model_3 = WorkflowSpecModel(id='test_spec_3', name='test_spec_3', display_name='Test Spec 3', spec_model_3 = WorkflowSpecModel(id='test_spec_3', display_name='Test Spec 3',
description='Test Spec 3 Description', category_id=workflow_spec_category.id, description='Test Spec 3 Description', category_id=workflow_spec_category.id,
display_order=3, standalone=False) display_order=3, standalone=False)
session.add(spec_model_1) session.add(spec_model_1)
@ -159,7 +159,6 @@ class TestWorkflowSpec(BaseTest):
count = session.query(WorkflowSpecCategoryModel).count() count = session.query(WorkflowSpecCategoryModel).count()
category = WorkflowSpecCategoryModel( category = WorkflowSpecCategoryModel(
id=count, id=count,
name='another_test_category',
display_name='Another Test Category', display_name='Another Test Category',
display_order=0 display_order=0
) )
@ -169,18 +168,18 @@ class TestWorkflowSpec(BaseTest):
data=json.dumps(WorkflowSpecCategoryModelSchema().dump(category)) data=json.dumps(WorkflowSpecCategoryModelSchema().dump(category))
) )
self.assert_success(rv) self.assert_success(rv)
result = session.query(WorkflowSpecCategoryModel).filter(WorkflowSpecCategoryModel.name=='another_test_category').first() result = session.query(WorkflowSpecCategoryModel).filter(WorkflowSpecCategoryModel.id==count).first()
self.assertEqual('Another Test Category', result.display_name) self.assertEqual('Another Test Category', result.display_name)
self.assertEqual(count, result.id) self.assertEqual(count, result.id)
def test_update_workflow_spec_category(self): def test_update_workflow_spec_category(self):
self.load_example_data() self.load_example_data()
category = session.query(WorkflowSpecCategoryModel).first() category = session.query(WorkflowSpecCategoryModel).first()
category_name_before = category.name display_name_before = category.display_name
new_category_name = category_name_before + '_asdf' new_display_name = display_name_before + '_asdf'
self.assertNotEqual(category_name_before, new_category_name) self.assertNotEqual(display_name_before, new_display_name)
category.name = new_category_name category.display_name = new_display_name
rv = self.app.put(f'/v1.0/workflow-specification-category/{category.id}', rv = self.app.put(f'/v1.0/workflow-specification-category/{category.id}',
content_type="application/json", content_type="application/json",
@ -188,25 +187,22 @@ class TestWorkflowSpec(BaseTest):
data=json.dumps(WorkflowSpecCategoryModelSchema().dump(category))) data=json.dumps(WorkflowSpecCategoryModelSchema().dump(category)))
self.assert_success(rv) self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True)) json_data = json.loads(rv.get_data(as_text=True))
self.assertEqual(new_category_name, json_data['name']) self.assertEqual(new_display_name, json_data['display_name'])
def test_delete_workflow_spec_category(self): def test_delete_workflow_spec_category(self):
self.load_example_data() self.load_example_data()
category_model_1 = WorkflowSpecCategoryModel( category_model_1 = WorkflowSpecCategoryModel(
id=1, id=1,
name='test_category_1',
display_name='Test Category 1', display_name='Test Category 1',
display_order=1 display_order=1
) )
category_model_2 = WorkflowSpecCategoryModel( category_model_2 = WorkflowSpecCategoryModel(
id=2, id=2,
name='test_category_2',
display_name='Test Category 2', display_name='Test Category 2',
display_order=2 display_order=2
) )
category_model_3 = WorkflowSpecCategoryModel( category_model_3 = WorkflowSpecCategoryModel(
id=3, id=3,
name='test_category_3',
display_name='Test Category 3', display_name='Test Category 3',
display_order=3 display_order=3
) )
@ -225,7 +221,7 @@ class TestWorkflowSpec(BaseTest):
def test_add_library_with_category_id(self): def test_add_library_with_category_id(self):
self.load_example_data() self.load_example_data()
category_id = session.query(WorkflowSpecCategoryModel).first().id category_id = session.query(WorkflowSpecCategoryModel).first().id
spec = WorkflowSpecModel(id='test_spec', name='test_spec', display_name='Test Spec', spec = WorkflowSpecModel(id='test_spec', display_name='Test Spec',
description='Library with a category id', category_id=category_id, description='Library with a category id', category_id=category_id,
standalone=False, library=True) standalone=False, library=True)
rv = self.app.post('/v1.0/workflow-specification', rv = self.app.post('/v1.0/workflow-specification',

View File

@ -13,19 +13,16 @@ class TestWorkflowSpecCategoryReorder(BaseTest):
def _load_test_categories(): def _load_test_categories():
category_model_1 = WorkflowSpecCategoryModel( category_model_1 = WorkflowSpecCategoryModel(
id=1, id=1,
name='test_category_1',
display_name='Test Category 1', display_name='Test Category 1',
display_order=1 display_order=1
) )
category_model_2 = WorkflowSpecCategoryModel( category_model_2 = WorkflowSpecCategoryModel(
id=2, id=2,
name='test_category_2',
display_name='Test Category 2', display_name='Test Category 2',
display_order=2 display_order=2
) )
category_model_3 = WorkflowSpecCategoryModel( category_model_3 = WorkflowSpecCategoryModel(
id=3, id=3,
name='test_category_3',
display_name='Test Category 3', display_name='Test Category 3',
display_order=3 display_order=3
) )

View File

@ -10,21 +10,21 @@ class TestWorkflowSpecReorder(BaseTest):
def _load_sample_workflow_specs(self): def _load_sample_workflow_specs(self):
workflow_spec_category = session.query(WorkflowSpecCategoryModel).first() workflow_spec_category = session.query(WorkflowSpecCategoryModel).first()
spec_model_1 = WorkflowSpecModel(id='test_spec_1', name='test_spec_1', display_name='Test Spec 1', spec_model_1 = WorkflowSpecModel(id='test_spec_1', display_name='Test Spec 1',
description='Test Spec 1 Description', category_id=workflow_spec_category.id, description='Test Spec 1 Description', category_id=workflow_spec_category.id,
standalone=False) standalone=False)
rv_1 = self.app.post('/v1.0/workflow-specification', rv_1 = self.app.post('/v1.0/workflow-specification',
headers=self.logged_in_headers(), headers=self.logged_in_headers(),
content_type="application/json", content_type="application/json",
data=json.dumps(WorkflowSpecModelSchema().dump(spec_model_1))) data=json.dumps(WorkflowSpecModelSchema().dump(spec_model_1)))
spec_model_2 = WorkflowSpecModel(id='test_spec_2', name='test_spec_2', display_name='Test Spec 2', spec_model_2 = WorkflowSpecModel(id='test_spec_2', display_name='Test Spec 2',
description='Test Spec 2 Description', category_id=workflow_spec_category.id, description='Test Spec 2 Description', category_id=workflow_spec_category.id,
standalone=False) standalone=False)
rv_2 = self.app.post('/v1.0/workflow-specification', rv_2 = self.app.post('/v1.0/workflow-specification',
headers=self.logged_in_headers(), headers=self.logged_in_headers(),
content_type="application/json", content_type="application/json",
data=json.dumps(WorkflowSpecModelSchema().dump(spec_model_2))) data=json.dumps(WorkflowSpecModelSchema().dump(spec_model_2)))
spec_model_3 = WorkflowSpecModel(id='test_spec_3', name='test_spec_3', display_name='Test Spec 3', spec_model_3 = WorkflowSpecModel(id='test_spec_3', display_name='Test Spec 3',
description='Test Spec 3 Description', category_id=workflow_spec_category.id, description='Test Spec 3 Description', category_id=workflow_spec_category.id,
standalone=False) standalone=False)
rv_3 = self.app.post('/v1.0/workflow-specification', rv_3 = self.app.post('/v1.0/workflow-specification',
@ -37,11 +37,11 @@ class TestWorkflowSpecReorder(BaseTest):
self.load_example_data() self.load_example_data()
rv_1, rv_2, rv_3 = self._load_sample_workflow_specs() rv_1, rv_2, rv_3 = self._load_sample_workflow_specs()
self.assertEqual(1, rv_1.json['display_order']) self.assertEqual(1, rv_1.json['display_order'])
self.assertEqual('test_spec_1', rv_1.json['name']) self.assertEqual('test_spec_1', rv_1.json['id'])
self.assertEqual(2, rv_2.json['display_order']) self.assertEqual(2, rv_2.json['display_order'])
self.assertEqual('test_spec_2', rv_2.json['name']) self.assertEqual('test_spec_2', rv_2.json['id'])
self.assertEqual(3, rv_3.json['display_order']) self.assertEqual(3, rv_3.json['display_order'])
self.assertEqual('test_spec_3', rv_3.json['name']) self.assertEqual('test_spec_3', rv_3.json['id'])
def test_workflow_spec_reorder_bad_direction(self): def test_workflow_spec_reorder_bad_direction(self):
self.load_example_data() self.load_example_data()
@ -163,13 +163,13 @@ class TestWorkflowSpecReorder(BaseTest):
# but it is # but it is
# test_spec_1, random_fact, test_spec_2, test_spec_3 # test_spec_1, random_fact, test_spec_2, test_spec_3
self.assertEqual(1, bad_orders[0].display_order) self.assertEqual(1, bad_orders[0].display_order)
self.assertEqual('test_spec_1', bad_orders[0].name) self.assertEqual('test_spec_1', bad_orders[0].id)
self.assertEqual(1, bad_orders[1].display_order) self.assertEqual(1, bad_orders[1].display_order)
self.assertEqual('random_fact', bad_orders[1].name) self.assertEqual('random_fact', bad_orders[1].id)
self.assertEqual(1, bad_orders[2].display_order) self.assertEqual(1, bad_orders[2].display_order)
self.assertEqual('test_spec_2', bad_orders[2].name) self.assertEqual('test_spec_2', bad_orders[2].id)
self.assertEqual(3, bad_orders[3].display_order) self.assertEqual(3, bad_orders[3].display_order)
self.assertEqual('test_spec_3', bad_orders[3].name) self.assertEqual('test_spec_3', bad_orders[3].id)
# Move test_spec_2 up # Move test_spec_2 up
# This should cause a cleanup of the bad display_order numbers # This should cause a cleanup of the bad display_order numbers
@ -179,11 +179,11 @@ class TestWorkflowSpecReorder(BaseTest):
# After moving 2 up, the order should be # After moving 2 up, the order should be
# test_spec_1, test_spec_2, random_fact, test_spec_3 # test_spec_1, test_spec_2, random_fact, test_spec_3
# Make sure we have good display_order numbers too # Make sure we have good display_order numbers too
self.assertEqual('test_spec_1', rv.json[0]['name']) self.assertEqual('test_spec_1', rv.json[0]['id'])
self.assertEqual(0, rv.json[0]['display_order']) self.assertEqual(0, rv.json[0]['display_order'])
self.assertEqual('test_spec_2', rv.json[1]['name']) self.assertEqual('test_spec_2', rv.json[1]['id'])
self.assertEqual(1, rv.json[1]['display_order']) self.assertEqual(1, rv.json[1]['display_order'])
self.assertEqual('random_fact', rv.json[2]['name']) self.assertEqual('random_fact', rv.json[2]['id'])
self.assertEqual(2, rv.json[2]['display_order']) self.assertEqual(2, rv.json[2]['display_order'])
self.assertEqual('test_spec_3', rv.json[3]['name']) self.assertEqual('test_spec_3', rv.json[3]['id'])
self.assertEqual(3, rv.json[3]['display_order']) self.assertEqual(3, rv.json[3]['display_order'])

View File

@ -163,7 +163,6 @@ class TestWorkflowSpecValidation(BaseTest):
# workflow spec to validate # workflow spec to validate
spec_model = WorkflowSpecModel(id='data_security_plan', spec_model = WorkflowSpecModel(id='data_security_plan',
name='data_security_plan',
display_name='Data Security Plan', display_name='Data Security Plan',
description='Data Security Plan', description='Data Security Plan',
is_master_spec=False, is_master_spec=False,
@ -186,3 +185,12 @@ class TestWorkflowSpecValidation(BaseTest):
api_error = json_data[0] api_error = json_data[0]
self.assertEqual('disabled_workflow', api_error['code']) self.assertEqual('disabled_workflow', api_error['code'])
self.assertEqual('This workflow is disabled. This is my mocked disable message.', api_error['message']) self.assertEqual('This workflow is disabled. This is my mocked disable message.', api_error['message'])
def test_date_generation_during_validation(self):
# We hit a bug where the date was generated as a part of a value_expression during validation, but
# it wasn't converted to an ISO String as it would be if submitted through the API.
# subsequent attempts to work with the expected date_string failed, because it was already a date.
# This can't happen in the front end code base, but it was breaking validation.
errors = self.validate_workflow("date_value_expression")
self.assertEqual(0, len(errors))