ethyca-fides 2.64.5rc4__py2.py3-none-any.whl → 2.64.6b1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ethyca-fides might be problematic. Click here for more details.

Files changed (301) hide show
  1. {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/METADATA +3 -3
  2. {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/RECORD +252 -257
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/41a634d8c669_manual_task_restrict_deletes.py +257 -0
  5. fides/api/alembic/migrations/versions/6a76a1fa4f3f_add_manual_task_instance_table.py +256 -0
  6. fides/api/alembic/migrations/versions/aadfe83c5644_add_manual_task_to_connectiontype_enum.py +46 -0
  7. fides/api/api/v1/api.py +2 -0
  8. fides/api/api/v1/endpoints/partitioning_endpoints.py +108 -0
  9. fides/api/db/base.py +6 -3
  10. fides/api/db/database.py +27 -2
  11. fides/api/graph/config.py +16 -9
  12. fides/api/graph/traversal.py +18 -0
  13. fides/api/models/attachment.py +15 -3
  14. fides/api/models/comment.py +23 -5
  15. fides/api/models/connectionconfig.py +11 -0
  16. fides/api/models/db_cache.py +1 -1
  17. fides/api/models/detection_discovery/core.py +15 -15
  18. fides/api/models/fides_user_respondent_email_verification.py +27 -2
  19. fides/api/models/manual_task.py +965 -0
  20. fides/api/models/tcf_publisher_restrictions.py +16 -4
  21. fides/api/schemas/partitioning/__init__.py +17 -0
  22. fides/api/schemas/partitioning/bigquery_time_based_partitioning.py +31 -0
  23. fides/api/schemas/partitioning/time_based_partitioning.py +1376 -0
  24. fides/api/service/connectors/__init__.py +4 -0
  25. fides/api/service/connectors/manual_task_connector.py +96 -0
  26. fides/api/service/connectors/query_configs/bigquery_query_config.py +44 -22
  27. fides/api/service/connectors/query_configs/query_config.py +5 -2
  28. fides/api/service/privacy_request/dsr_package/templates/collection_index.html +9 -1
  29. fides/api/service/privacy_request/dsr_package/templates/main.css +6 -2
  30. fides/api/service/privacy_request/request_runner_service.py +7 -0
  31. fides/api/task/create_request_tasks.py +16 -0
  32. fides/api/task/execute_request_tasks.py +10 -1
  33. fides/api/task/filter_results.py +6 -0
  34. fides/api/task/graph_task.py +1 -0
  35. fides/api/task/manual/manual_task_graph_task.py +300 -0
  36. fides/api/task/manual/manual_task_utils.py +322 -0
  37. fides/api/task/task_resources.py +3 -0
  38. fides/api/util/connection_util.py +25 -2
  39. fides/common/api/v1/urn_registry.py +4 -0
  40. fides/ui-build/static/admin/404.html +1 -1
  41. fides/ui-build/static/admin/_next/static/chunks/{1040-c1c1372a7f909aef.js → 1040-d246ed641088a416.js} +1 -1
  42. fides/ui-build/static/admin/_next/static/chunks/1169-ae67fde0c6d69abc.js +1 -0
  43. fides/ui-build/static/admin/_next/static/chunks/1807-3beab149351d5ded.js +1 -0
  44. fides/ui-build/static/admin/_next/static/chunks/{1817-96182c1558f80b63.js → 1817-60f08a3619b9139c.js} +1 -1
  45. fides/ui-build/static/admin/_next/static/chunks/{2921-f5608275555bd7d9.js → 2921-85515257dd94ef4d.js} +1 -1
  46. fides/ui-build/static/admin/_next/static/chunks/{3450-272f26c102f3510d.js → 3450-4e472b9e2754fa47.js} +1 -1
  47. fides/ui-build/static/admin/_next/static/chunks/3615-5e2d062d684b8fa1.js +1 -0
  48. fides/ui-build/static/admin/_next/static/chunks/{3855-beb58821d1ddba89.js → 3855-2d045674fbf72a3c.js} +1 -1
  49. fides/ui-build/static/admin/_next/static/chunks/{3872-82482e55e69b5a93.js → 3872-056ddf3ed9d10b51.js} +1 -1
  50. fides/ui-build/static/admin/_next/static/chunks/{3923-a54c286a2ba0a47a.js → 3923-257df982a95371b5.js} +1 -1
  51. fides/ui-build/static/admin/_next/static/chunks/{401-d2ce0a5a9120e056.js → 401-3f2160e3910d075b.js} +1 -1
  52. fides/ui-build/static/admin/_next/static/chunks/{409-86f4f687105917fb.js → 409-3e2248a63dd60e58.js} +1 -1
  53. fides/ui-build/static/admin/_next/static/chunks/4121-10bfa009892586fa.js +1 -0
  54. fides/ui-build/static/admin/_next/static/chunks/{4230-63abbdfb9e9016b9.js → 4230-38c6e446801a8729.js} +1 -1
  55. fides/ui-build/static/admin/_next/static/chunks/{431-77d59d43e90058ca.js → 431-13b0ef67d5a3df2f.js} +1 -1
  56. fides/ui-build/static/admin/_next/static/chunks/{5309-10f68cf805817cfb.js → 5309-fd8cd5aedd45f7c1.js} +1 -1
  57. fides/ui-build/static/admin/_next/static/chunks/{5574-b6db9d62362e72d9.js → 5574-028ef28c3cf16995.js} +1 -1
  58. fides/ui-build/static/admin/_next/static/chunks/570-c99f07161bd339cd.js +1 -0
  59. fides/ui-build/static/admin/_next/static/chunks/6084-fa1c82d03f6c256a.js +1 -0
  60. fides/ui-build/static/admin/_next/static/chunks/6662-d8ae12f69d325004.js +1 -0
  61. fides/ui-build/static/admin/_next/static/chunks/{6853-8a1b8e1c8b249f2f.js → 6853-2644f28976b46c25.js} +1 -1
  62. fides/ui-build/static/admin/_next/static/chunks/{6882-6c94583bffe85ba7.js → 6882-ea071425d25dd2b0.js} +1 -1
  63. fides/ui-build/static/admin/_next/static/chunks/{6954-dc3540389daf94da.js → 6954-bb360fb60aac9440.js} +1 -1
  64. fides/ui-build/static/admin/_next/static/chunks/7476-7073ec015f84a3e0.js +1 -0
  65. fides/ui-build/static/admin/_next/static/chunks/7630-302a13c63f9bfb45.js +1 -0
  66. fides/ui-build/static/admin/_next/static/chunks/787-b393c03ade9d93dc.js +1 -0
  67. fides/ui-build/static/admin/_next/static/chunks/{79-26ccd45dfd6653a7.js → 79-b67ba449b0f2cc9c.js} +1 -1
  68. fides/ui-build/static/admin/_next/static/chunks/{796-38c0f7e6755ad359.js → 796-b7608f09607288b8.js} +1 -1
  69. fides/ui-build/static/admin/_next/static/chunks/827-c6fe34fb336467ae.js +1 -0
  70. fides/ui-build/static/admin/_next/static/chunks/9014-eeae6f581158e645.js +1 -0
  71. fides/ui-build/static/admin/_next/static/chunks/{9046-bbce3c73af16daf9.js → 9046-ece5efe762b810cb.js} +1 -1
  72. fides/ui-build/static/admin/_next/static/chunks/{905-742074a074be1055.js → 905-ffdbd0b14167e8bd.js} +1 -1
  73. fides/ui-build/static/admin/_next/static/chunks/{9226-5e0ce31cfdedd5ee.js → 9226-c9111590692341b1.js} +1 -1
  74. fides/ui-build/static/admin/_next/static/chunks/{9392.25024e070026343d.js → 9392.9a948112de74781b.js} +1 -1
  75. fides/ui-build/static/admin/_next/static/chunks/{9676.e60a53f1f5890847.js → 9676.cc515c853b8cf578.js} +1 -1
  76. fides/ui-build/static/admin/_next/static/chunks/9767-277a0229aae7662a.js +1 -0
  77. fides/ui-build/static/admin/_next/static/chunks/9826-82c473dcaf892d00.js +1 -0
  78. fides/ui-build/static/admin/_next/static/chunks/{9951-d3d5d0fe4c4edb86.js → 9951-dbd76d7f3a7f1b9a.js} +1 -1
  79. fides/ui-build/static/admin/_next/static/chunks/pages/{404-d41660858638adee.js → 404-ef01376efb8427e1.js} +1 -1
  80. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-6b3539f8d82ce9ae.js → _app-7430e1499432b029.js} +92 -89
  81. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-7c5e1e845372c99b.js → manual-4057c399f58d331e.js} +1 -1
  82. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-87e78f52af21c4a9.js → multiple-2726ca51f0327347.js} +1 -1
  83. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-a2457d5da25aa854.js +1 -0
  84. fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-6374a3a7747df964.js → add-vendors-9fe46ed10a7e7a1a.js} +1 -1
  85. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-0512151e2ed0f4c1.js → configure-095828301f22cdaa.js} +1 -1
  86. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-09435f4ab2deafd2.js → [id]-de60a5b74f6b20ff.js} +1 -1
  87. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-a0039f216fb3eb93.js → new-06bb3b0bf097fcdb.js} +1 -1
  88. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-cd036518b5d4efbe.js → privacy-experience-1ee05fd0bc012ec6.js} +1 -1
  89. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-0e534580abf670be.js → [id]-5a2e61c7d88bdda1.js} +1 -1
  90. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-bb1790eb87b63109.js → new-1bc69669215fa55c.js} +1 -1
  91. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-67e9846f877a3f38.js → privacy-notices-fa8394d2e1072aba.js} +1 -1
  92. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-69a92b6ddbc2bae2.js → properties-213c5405e9e4219c.js} +1 -1
  93. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{reporting-d07d05f4b898a5ed.js → reporting-44aa77149d71a6fe.js} +1 -1
  94. fides/ui-build/static/admin/_next/static/chunks/pages/{consent-23e886d692ab6d1a.js → consent-23ab7bd613a0ad53.js} +1 -1
  95. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-c623d6f1a61c8ea9.js → [resourceUrn]-11d52f1570759c4d.js} +1 -1
  96. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-6c766c766dc97c5c.js → [projectUrn]-dae11464a091537f.js} +1 -1
  97. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-28937a8da3d73145.js → projects-7442023478422295.js} +1 -1
  98. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-57bd5cdf784f059f.js → [resourceUrn]-b83afa5565d0c84e.js} +1 -1
  99. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-51d99174c8006eb5.js → resources-d8db234a44a2ddf4.js} +1 -1
  100. fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-627fbf19dce88e81.js → data-catalog-5597d2e691313bb0.js} +1 -1
  101. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-ed1629b05519a370.js → [systemId]-da06d05d255d0cd0.js} +1 -1
  102. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-ad307bfc5f51fd9d.js → [monitorId]-895591fe32af0f4c.js} +1 -1
  103. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-07cbd61ede6e18ac.js → action-center-a932a39e29ac3489.js} +1 -1
  104. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-6a2aaed8d0e66d82.js → activity-2cfdf4d55a7594ba.js} +1 -1
  105. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-06edce289876dea1.js → [resourceUrn]-393e20924c83373e.js} +1 -1
  106. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-faf326a6200637d0.js → detection-8733807dad4bc96e.js} +1 -1
  107. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-64acf269256ee74f.js → [resourceUrn]-14bd7500362ff224.js} +1 -1
  108. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-8c3e4be6d36da66d.js → discovery-9e7dfd5a6acc2e8f.js} +1 -1
  109. fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-b576a94b583a7940.js → datamap-07881d1a5fc03d41.js} +1 -1
  110. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-4912858ffde4621a.js → [...subfieldNames]-57be9d40c67b0acb.js} +1 -1
  111. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-df21dd7ca0f35718.js → [collectionName]-bd78d6c6264e2467.js} +1 -1
  112. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-5ce28a329e8f667f.js → [datasetId]-3c9103487b55e76f.js} +1 -1
  113. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-e30a546a8cfb6658.js +1 -0
  114. fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-c6fab547396df6ac.js → dataset-500610c0b60f9e7e.js} +1 -1
  115. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-d04c9925d324eee1.js → [id]-34958fe8183d9479.js} +1 -1
  116. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-656abd09ea5ee39a.js → new-761294dbaf59bbe1.js} +1 -1
  117. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-59abd36059863e05.js +1 -0
  118. fides/ui-build/static/admin/_next/static/chunks/pages/{index-b66687e8194495a2.js → index-ae100a873eb66d59.js} +1 -1
  119. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-8eee4d0314c83e50.js +1 -0
  120. fides/ui-build/static/admin/_next/static/chunks/pages/integrations-bf3b87b0dd702551.js +1 -0
  121. fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-b74db8488e2e4b58.js → [id]-2aada2c7b9156053.js} +1 -1
  122. fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-a607018ff097b6c2.js → add-template-fbb7b0c43dedf072.js} +1 -1
  123. fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-4a1a04c5179d2053.js → messaging-7230d7ee21368cef.js} +1 -1
  124. fides/ui-build/static/admin/_next/static/chunks/pages/poc/{ant-components-1567c9770a5f05aa.js → ant-components-7c234412502f8102.js} +1 -1
  125. fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{AntForm-4949fe7f1815462b.js → AntForm-72791aab60f3a4bf.js} +1 -1
  126. fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikAntFormItem-6085f73850302d55.js → FormikAntFormItem-da7d11d6146fa746.js} +1 -1
  127. fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikControlled-a3158e8217c13850.js → FormikControlled-3045d24344e99017.js} +1 -1
  128. fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikField-8576f1ef5c67d87d.js → FormikField-5b5b165d3f41a1de.js} +1 -1
  129. fides/ui-build/static/admin/_next/static/chunks/pages/poc/{forms-341e67462b5e3352.js → forms-3afd8b6ba6234366.js} +1 -1
  130. fides/ui-build/static/admin/_next/static/chunks/pages/poc/table-migration-52d50286216bcb8c.js +1 -0
  131. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-00fb442c4adb7371.js +1 -0
  132. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-535b6e5003d892eb.js +1 -0
  133. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-7ded688a1e832c63.js +1 -0
  134. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-d24230b890b13762.js → configure-08e0863d432b3348.js} +1 -1
  135. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-c8b02ae92dd7e45b.js +1 -0
  136. fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-2bedc5013e13ab52.js → [id]-b8dcb1f5213521ef.js} +1 -1
  137. fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-9d68bd70299dd945.js → add-property-7d8418bb7cb9e1fd.js} +1 -1
  138. fides/ui-build/static/admin/_next/static/chunks/pages/{properties-c3116b6bfe2e695e.js → properties-537003822f360ae7.js} +1 -1
  139. fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-6dfa7091b99d8321.js → datamap-ac25749935da453b.js} +1 -1
  140. fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-9751059905bba190.js → alpha-647d59bb0aa060bf.js} +1 -1
  141. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-ad29d77012ec9dba.js → about-73adc03fe0182e3f.js} +1 -1
  142. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/{[purpose_id]-32e3190c1ad00b40.js → [purpose_id]-ffa9e9e79ad75828.js} +1 -1
  143. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-4fb6c8c4bd9cdb3f.js +1 -0
  144. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-d2c0aac32f5d2930.js → custom-fields-94c1b7bfc6cec4d2.js} +1 -1
  145. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-a10674380f94d014.js → domain-records-e9838b8e49e64e57.js} +1 -1
  146. fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-0de16778ad2f8865.js +1 -0
  147. fides/ui-build/static/admin/_next/static/chunks/pages/settings/email-templates-aac71677024210ad.js +1 -0
  148. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-cc7d629433fa6d44.js → locations-f43e238571b21643.js} +1 -1
  149. fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-f5e0a34f6ae473ad.js +1 -0
  150. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-64a813cb2741683b.js → regulations-05d3c86ca3ca4d5d.js} +1 -1
  151. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-31fcf7b52d8028b0.js +1 -0
  152. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-fb3d094e4f4585f6.js → [id]-6db01a1afe2f82d5.js} +1 -1
  153. fides/ui-build/static/admin/_next/static/chunks/pages/{systems-c2df5b7b0596a9cb.js → systems-8ec7472a7032301f.js} +1 -1
  154. fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-84caae0fc02f8ae1.js +1 -0
  155. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-b124cc24b930c9e1.js → new-a2524414e968f862.js} +1 -1
  156. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-fd2ff6b13052c54e.js → [id]-e452541827698b1f.js} +1 -1
  157. fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-fa052b0439920ef6.js → user-management-530be849391d2425.js} +1 -1
  158. fides/ui-build/static/admin/_next/static/chunks/{webpack-e61d457474e00565.js → webpack-e25ccaae1ef867a2.js} +1 -1
  159. fides/ui-build/static/admin/_next/static/css/23391a3311f80cfe.css +1 -0
  160. fides/ui-build/static/admin/_next/static/css/399d4757862a3982.css +1 -0
  161. fides/ui-build/static/admin/_next/static/z7GzAZhoGiO521zm7WPYX/_buildManifest.js +1 -0
  162. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  163. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  164. fides/ui-build/static/admin/add-systems.html +1 -1
  165. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  166. fides/ui-build/static/admin/consent/configure.html +1 -1
  167. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  168. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  169. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  170. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  171. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  172. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  173. fides/ui-build/static/admin/consent/properties.html +1 -1
  174. fides/ui-build/static/admin/consent/reporting.html +1 -1
  175. fides/ui-build/static/admin/consent.html +1 -1
  176. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  177. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  178. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  179. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  180. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  181. fides/ui-build/static/admin/data-catalog.html +1 -1
  182. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  183. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  184. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  185. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  186. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  187. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  188. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  189. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  190. fides/ui-build/static/admin/datamap.html +1 -1
  191. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  192. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  193. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  194. fides/ui-build/static/admin/dataset/new.html +1 -1
  195. fides/ui-build/static/admin/dataset.html +1 -1
  196. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  197. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  198. fides/ui-build/static/admin/datastore-connection.html +1 -1
  199. fides/ui-build/static/admin/index.html +1 -1
  200. fides/ui-build/static/admin/integrations/[id].html +1 -1
  201. fides/ui-build/static/admin/integrations.html +1 -1
  202. fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
  203. fides/ui-build/static/admin/lib/fides-headless.js +1 -1
  204. fides/ui-build/static/admin/lib/fides-preview.js +1 -1
  205. fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
  206. fides/ui-build/static/admin/lib/fides.js +2 -2
  207. fides/ui-build/static/admin/login/[provider].html +1 -1
  208. fides/ui-build/static/admin/login.html +1 -1
  209. fides/ui-build/static/admin/messaging/[id].html +1 -1
  210. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  211. fides/ui-build/static/admin/messaging.html +1 -1
  212. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  213. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  214. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  215. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  216. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  217. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  218. fides/ui-build/static/admin/poc/forms.html +1 -1
  219. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  220. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  221. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  222. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  223. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  224. fides/ui-build/static/admin/privacy-requests.html +1 -1
  225. fides/ui-build/static/admin/properties/[id].html +1 -1
  226. fides/ui-build/static/admin/properties/add-property.html +1 -1
  227. fides/ui-build/static/admin/properties.html +1 -1
  228. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  229. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  230. fides/ui-build/static/admin/settings/about.html +1 -1
  231. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  232. fides/ui-build/static/admin/settings/consent.html +1 -1
  233. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  234. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  235. fides/ui-build/static/admin/settings/domains.html +1 -1
  236. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  237. fides/ui-build/static/admin/settings/locations.html +1 -1
  238. fides/ui-build/static/admin/settings/organization.html +1 -1
  239. fides/ui-build/static/admin/settings/regulations.html +1 -1
  240. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  241. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  242. fides/ui-build/static/admin/systems.html +1 -1
  243. fides/ui-build/static/admin/taxonomy.html +1 -1
  244. fides/ui-build/static/admin/user-management/new.html +1 -1
  245. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  246. fides/ui-build/static/admin/user-management.html +1 -1
  247. fides/api/models/manual_tasks/__init__.py +0 -14
  248. fides/api/models/manual_tasks/manual_task.py +0 -120
  249. fides/api/models/manual_tasks/manual_task_config.py +0 -136
  250. fides/api/models/manual_tasks/manual_task_log.py +0 -104
  251. fides/api/schemas/manual_tasks/manual_task_config.py +0 -311
  252. fides/api/schemas/manual_tasks/manual_task_schemas.py +0 -79
  253. fides/api/schemas/manual_tasks/manual_task_status.py +0 -151
  254. fides/service/manual_tasks/__init__.py +0 -0
  255. fides/service/manual_tasks/manual_task_config_service.py +0 -370
  256. fides/service/manual_tasks/manual_task_service.py +0 -294
  257. fides/service/manual_tasks/utils.py +0 -185
  258. fides/ui-build/static/admin/_next/static/TUIhCXaasOCEZxPYTqsV5/_buildManifest.js +0 -1
  259. fides/ui-build/static/admin/_next/static/chunks/1100-3fdbdf211c3c2a5b.js +0 -1
  260. fides/ui-build/static/admin/_next/static/chunks/2430-b480401d44c55416.js +0 -1
  261. fides/ui-build/static/admin/_next/static/chunks/3505-192986c86dc47869.js +0 -1
  262. fides/ui-build/static/admin/_next/static/chunks/3513-a563133845dc990f.js +0 -1
  263. fides/ui-build/static/admin/_next/static/chunks/3670-2abd9b2f17770872.js +0 -1
  264. fides/ui-build/static/admin/_next/static/chunks/3983-17ae9c232bddc413.js +0 -1
  265. fides/ui-build/static/admin/_next/static/chunks/4060-3486b45081151b69.js +0 -1
  266. fides/ui-build/static/admin/_next/static/chunks/4121-f0aecb2abd384945.js +0 -1
  267. fides/ui-build/static/admin/_next/static/chunks/4481-d181a9db72984adf.js +0 -1
  268. fides/ui-build/static/admin/_next/static/chunks/6060-cb1ab5be7067bf7b.js +0 -4
  269. fides/ui-build/static/admin/_next/static/chunks/6277-ccdb50f676a1b336.js +0 -1
  270. fides/ui-build/static/admin/_next/static/chunks/6659-b2088f525bf13c17.js +0 -1
  271. fides/ui-build/static/admin/_next/static/chunks/6662-42940a2b00933e79.js +0 -1
  272. fides/ui-build/static/admin/_next/static/chunks/69-943b19d39da339d9.js +0 -1
  273. fides/ui-build/static/admin/_next/static/chunks/7553-e7ae268701f3dcfe.js +0 -1
  274. fides/ui-build/static/admin/_next/static/chunks/8433-1e065c55c8da73b0.js +0 -1
  275. fides/ui-build/static/admin/_next/static/chunks/9767-96ed554a043c3c4d.js +0 -1
  276. fides/ui-build/static/admin/_next/static/chunks/c78d26b1-88a3e1bacb2a03c2.js +0 -1
  277. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-68955d8441e60668.js +0 -1
  278. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-5287c76ecf600281.js +0 -1
  279. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-23fbec0590c8d192.js +0 -1
  280. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-906bc5f05702efb0.js +0 -1
  281. fides/ui-build/static/admin/_next/static/chunks/pages/integrations-024a1facb9be04d0.js +0 -1
  282. fides/ui-build/static/admin/_next/static/chunks/pages/poc/table-migration-6f0e64f0c52bd68f.js +0 -1
  283. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-6971c7773dbf9b51.js +0 -1
  284. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-303bd2182da03088.js +0 -1
  285. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-831572cc7f42615f.js +0 -1
  286. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-569ff31eff637034.js +0 -1
  287. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-13a17a4ace7293d1.js +0 -1
  288. fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-78449b0e02bced88.js +0 -1
  289. fides/ui-build/static/admin/_next/static/chunks/pages/settings/email-templates-7d299c4cb3199036.js +0 -1
  290. fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-a9fa55c40fa570a6.js +0 -1
  291. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-547b0cfe1e49e6d2.js +0 -1
  292. fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-261deb6fb3e51cb3.js +0 -1
  293. fides/ui-build/static/admin/_next/static/css/5ded47c57dae5baf.css +0 -1
  294. fides/ui-build/static/admin/_next/static/css/c693338e3bc8dcc6.css +0 -1
  295. fides/ui-build/static/admin/_next/static/css/e458b5f1afdbb7fc.css +0 -1
  296. {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/WHEEL +0 -0
  297. {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/entry_points.txt +0 -0
  298. {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/licenses/LICENSE +0 -0
  299. {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/top_level.txt +0 -0
  300. /fides/api/{schemas/manual_tasks → task/manual}/__init__.py +0 -0
  301. /fides/ui-build/static/admin/_next/static/{TUIhCXaasOCEZxPYTqsV5 → z7GzAZhoGiO521zm7WPYX}/_ssgManifest.js +0 -0
@@ -0,0 +1,300 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ from loguru import logger
4
+ from sqlalchemy.orm import Session
5
+
6
+ from fides.api.common_exceptions import AwaitingAsyncTaskCallback
7
+ from fides.api.models.attachment import AttachmentType
8
+ from fides.api.models.manual_task import (
9
+ ManualTask,
10
+ ManualTaskConfigurationType,
11
+ ManualTaskEntityType,
12
+ ManualTaskFieldType,
13
+ ManualTaskInstance,
14
+ StatusType,
15
+ )
16
+ from fides.api.models.privacy_request import PrivacyRequest
17
+ from fides.api.schemas.policy import ActionType
18
+ from fides.api.schemas.privacy_request import PrivacyRequestStatus
19
+ from fides.api.task.graph_task import GraphTask, retry
20
+ from fides.api.task.manual.manual_task_utils import (
21
+ ManualTaskAddress,
22
+ get_manual_tasks_for_connection_config,
23
+ )
24
+ from fides.api.util.collection_util import Row
25
+
26
+
27
+ class ManualTaskGraphTask(GraphTask):
28
+ """GraphTask implementation for ManualTask execution"""
29
+
30
+ @retry(action_type=ActionType.access, default_return=[])
31
+ def access_request(self, *inputs: List[Row]) -> List[Row]:
32
+ """
33
+ Execute manual task logic following the standard GraphTask pattern:
34
+ 1. Create ManualTaskInstances if they don't exist
35
+ 2. Check for submissions
36
+ 3. Return data if submitted, raise AwaitingAsyncTaskCallback if not
37
+ """
38
+ db = self.resources.session
39
+ collection_address = self.execution_node.address
40
+
41
+ # Verify this is a manual task address
42
+ if not ManualTaskAddress.is_manual_task_address(collection_address):
43
+ raise ValueError(f"Invalid manual task address: {collection_address}")
44
+
45
+ connection_key = ManualTaskAddress.get_connection_key(collection_address)
46
+
47
+ # Get manual tasks for this connection
48
+ manual_tasks = get_manual_tasks_for_connection_config(db, connection_key)
49
+
50
+ if not manual_tasks:
51
+ return []
52
+
53
+ # Check/create manual task instances for ACCESS configs only
54
+ self._ensure_manual_task_instances(
55
+ db,
56
+ manual_tasks,
57
+ self.resources.request,
58
+ ManualTaskConfigurationType.access_privacy_request,
59
+ )
60
+
61
+ # Check if all manual task instances have submissions for ACCESS configs only
62
+ submitted_data = self._get_submitted_data(
63
+ db,
64
+ manual_tasks,
65
+ self.resources.request,
66
+ ManualTaskConfigurationType.access_privacy_request,
67
+ )
68
+
69
+ if submitted_data is not None:
70
+ result: List[Row] = [submitted_data] if submitted_data else []
71
+ self.request_task.access_data = result
72
+
73
+ # Mark request task as complete and write execution log
74
+ self.log_end(ActionType.access)
75
+ return result
76
+
77
+ # Set privacy request status to requires_input if not already set
78
+ if self.resources.request.status != PrivacyRequestStatus.requires_input:
79
+ self.resources.request.status = PrivacyRequestStatus.requires_input
80
+ self.resources.request.save(db)
81
+
82
+ # This should trigger log_awaiting_processing via the @retry decorator
83
+ raise AwaitingAsyncTaskCallback(
84
+ f"Manual task for {connection_key} requires user input"
85
+ )
86
+
87
+ def _ensure_manual_task_instances(
88
+ self,
89
+ db: Session,
90
+ manual_tasks: List[ManualTask],
91
+ privacy_request: PrivacyRequest,
92
+ allowed_config_type: "ManualTaskConfigurationType",
93
+ ) -> None:
94
+ """Create ManualTaskInstances for configs matching `allowed_config_type` if they don't exist."""
95
+
96
+ for manual_task in manual_tasks:
97
+ # ------------------------------------------------------------------
98
+ # Short-circuit: if instances already exist for this task & entity
99
+ # (no matter what config version they were created for) we should reuse
100
+ # them instead of creating a brand-new one that would result in
101
+ # duplicates when configurations are versioned after the privacy
102
+ # request has started.
103
+ # ------------------------------------------------------------------
104
+ existing_task_instance = (
105
+ db.query(ManualTaskInstance)
106
+ .filter(
107
+ ManualTaskInstance.task_id == manual_task.id,
108
+ ManualTaskInstance.entity_id == privacy_request.id,
109
+ ManualTaskInstance.entity_type
110
+ == ManualTaskEntityType.privacy_request,
111
+ )
112
+ .first()
113
+ )
114
+ if existing_task_instance:
115
+ # An instance already exists for this privacy request – no need
116
+ # to create another one tied to a newer config version.
117
+ continue
118
+
119
+ # Check each active config for instances (now we know none exist yet)
120
+ for config in manual_task.configs:
121
+ if not config.is_current or config.config_type != allowed_config_type:
122
+ # Skip configs that are not current or not relevant for this request type
123
+ continue
124
+
125
+ ManualTaskInstance.create(
126
+ db=db,
127
+ data={
128
+ "task_id": manual_task.id,
129
+ "config_id": config.id,
130
+ "entity_id": privacy_request.id,
131
+ "entity_type": ManualTaskEntityType.privacy_request.value,
132
+ "status": StatusType.pending.value,
133
+ },
134
+ )
135
+
136
+ # pylint: disable=too-many-branches,too-many-nested-blocks
137
+ def _get_submitted_data(
138
+ self,
139
+ db: Session,
140
+ manual_tasks: List[ManualTask],
141
+ privacy_request: PrivacyRequest,
142
+ allowed_config_type: "ManualTaskConfigurationType",
143
+ ) -> Optional[Dict[str, Any]]:
144
+ """
145
+ Check if all manual task instances have submissions for ALL fields and return aggregated data
146
+ Returns None if any field submissions are missing (all fields must be completed or skipped)
147
+ """
148
+ aggregated_data: Dict[str, Any] = {}
149
+
150
+ def _format_size(size_bytes: int) -> str:
151
+ units = ["B", "KB", "MB", "GB", "TB"]
152
+ size = float(size_bytes)
153
+ for unit in units:
154
+ if size < 1024.0:
155
+ return f"{size:.1f} {unit}"
156
+ size /= 1024.0
157
+ return f"{size:.1f} PB"
158
+
159
+ for manual_task in manual_tasks:
160
+
161
+ candidate_instances: list[ManualTaskInstance] = (
162
+ db.query(ManualTaskInstance)
163
+ .filter(
164
+ ManualTaskInstance.task_id == manual_task.id,
165
+ ManualTaskInstance.entity_id == privacy_request.id,
166
+ ManualTaskInstance.entity_type
167
+ == ManualTaskEntityType.privacy_request,
168
+ )
169
+ .all()
170
+ )
171
+
172
+ if not candidate_instances:
173
+ return None # No instance yet for this manual task
174
+
175
+ for inst in candidate_instances:
176
+ # Skip instances tied to other request types
177
+ if not inst.config or inst.config.config_type != allowed_config_type:
178
+ continue
179
+
180
+ all_fields = inst.config.field_definitions or []
181
+
182
+ # Every field must have a submission
183
+ if not all(inst.get_submission_for_field(f.id) for f in all_fields):
184
+ return None # At least one instance still incomplete
185
+
186
+ # Ensure status set
187
+ if inst.status != StatusType.completed:
188
+ inst.status = StatusType.completed
189
+ inst.save(db)
190
+
191
+ # Aggregate submission data from this instance
192
+ for submission in inst.submissions:
193
+ if not submission.field or not submission.field.field_key:
194
+ continue
195
+
196
+ field_key = submission.field.field_key
197
+
198
+ if not isinstance(submission.data, dict):
199
+ continue
200
+
201
+ data_dict: Dict[str, Any] = submission.data
202
+
203
+ field_type = data_dict.get("field_type")
204
+
205
+ if field_type == ManualTaskFieldType.attachment.value:
206
+ attachment_map: Dict[str, Dict[str, Any]] = {}
207
+ for attachment in submission.attachments or []:
208
+ if (
209
+ attachment.attachment_type
210
+ == AttachmentType.include_with_access_package
211
+ ):
212
+ try:
213
+ size, url = attachment.retrieve_attachment()
214
+ attachment_map[attachment.file_name] = {
215
+ "url": str(url) if url else None,
216
+ "size": (
217
+ _format_size(size) if size else "Unknown"
218
+ ),
219
+ }
220
+ except (
221
+ Exception
222
+ ) as exc: # pylint: disable=broad-exception-caught
223
+ logger.warning(
224
+ "Error retrieving attachment {}: {}",
225
+ attachment.file_name,
226
+ str(exc),
227
+ )
228
+
229
+ aggregated_data[field_key] = attachment_map or None
230
+ else:
231
+ aggregated_data[field_key] = data_dict.get("value")
232
+
233
+ return aggregated_data if aggregated_data else None
234
+
235
+ def dry_run_task(self) -> int:
236
+ """Return estimated row count for dry run - manual tasks don't have predictable counts"""
237
+ return 1 # Placeholder - manual tasks generate variable data
238
+
239
+ # NEW METHOD: Provide erasure support for manual tasks
240
+ @retry(action_type=ActionType.erasure, default_return=0)
241
+ def erasure_request(
242
+ self,
243
+ retrieved_data: List[Row],
244
+ *erasure_prereqs: int, # noqa: D401, pylint: disable=unused-argument
245
+ ) -> int:
246
+ """Execute manual-task-driven erasure logic.
247
+
248
+ Mirrors access_request behaviour but returns the number of rows masked (always 0)
249
+ once all required manual task submissions are present. If submissions are
250
+ incomplete the privacy request is paused awaiting user input.
251
+ """
252
+ db = self.resources.session
253
+ collection_address = self.execution_node.address
254
+
255
+ # Validate manual task address
256
+ if not ManualTaskAddress.is_manual_task_address(collection_address):
257
+ raise ValueError(f"Invalid manual task address: {collection_address}")
258
+
259
+ connection_key = ManualTaskAddress.get_connection_key(collection_address)
260
+
261
+ # Fetch relevant manual tasks for this connection
262
+ manual_tasks = get_manual_tasks_for_connection_config(db, connection_key)
263
+ if not manual_tasks:
264
+ # No manual tasks defined – nothing to erase
265
+ self.log_end(ActionType.erasure)
266
+ return 0
267
+
268
+ # Create ManualTaskInstances for ERASURE configs only
269
+ self._ensure_manual_task_instances(
270
+ db,
271
+ manual_tasks,
272
+ self.resources.request,
273
+ ManualTaskConfigurationType.erasure_privacy_request,
274
+ )
275
+
276
+ # Check for full submissions – reuse helper used by access flow, filtering ERASURE configs
277
+ submissions_complete = self._get_submitted_data(
278
+ db,
279
+ manual_tasks,
280
+ self.resources.request,
281
+ ManualTaskConfigurationType.erasure_privacy_request,
282
+ )
283
+
284
+ # If any field submissions are missing, pause processing
285
+ if submissions_complete is None:
286
+ if self.resources.request.status != PrivacyRequestStatus.requires_input:
287
+ self.resources.request.status = PrivacyRequestStatus.requires_input
288
+ self.resources.request.save(db)
289
+ raise AwaitingAsyncTaskCallback(
290
+ f"Manual erasure task for {connection_key} requires user input"
291
+ )
292
+
293
+ # Mark rows_masked = 0 (manual tasks do not mask data directly)
294
+ if self.request_task.id:
295
+ # Storing result for DSR 3.0; SQLAlchemy column typing triggers mypy warning
296
+ self.request_task.rows_masked = 0 # type: ignore[assignment]
297
+
298
+ # Mark successful completion
299
+ self.log_end(ActionType.erasure)
300
+ return 0
@@ -0,0 +1,322 @@
1
+ from typing import TYPE_CHECKING, List
2
+
3
+ from sqlalchemy.orm import Session
4
+
5
+ from fides.api.graph.config import (
6
+ Collection,
7
+ CollectionAddress,
8
+ Field,
9
+ GraphDataset,
10
+ ScalarField,
11
+ )
12
+ from fides.api.graph.graph import Node
13
+ from fides.api.models.connectionconfig import ConnectionConfig
14
+
15
+ # Import application models
16
+ from fides.api.models.manual_task import (
17
+ ManualTask,
18
+ ManualTaskConfig,
19
+ ManualTaskConfigurationType,
20
+ ManualTaskEntityType,
21
+ ManualTaskInstance,
22
+ )
23
+ from fides.api.models.privacy_request import PrivacyRequest
24
+
25
+ # TYPE_CHECKING import placed after all runtime imports to avoid lint issues
26
+ if TYPE_CHECKING: # pragma: no cover
27
+ from fides.api.graph.traversal import TraversalNode # noqa: F401
28
+
29
+
30
+ class ManualTaskAddress:
31
+ """Utility class for creating and parsing manual task addresses"""
32
+
33
+ MANUAL_DATA_COLLECTION = "manual_data"
34
+
35
+ @staticmethod
36
+ def create(connection_config_key: str) -> CollectionAddress:
37
+ """Create a CollectionAddress for manual data: {connection_key}:manual_data"""
38
+ return CollectionAddress(
39
+ dataset=connection_config_key,
40
+ collection=ManualTaskAddress.MANUAL_DATA_COLLECTION,
41
+ )
42
+
43
+ @staticmethod
44
+ def is_manual_task_address(address: CollectionAddress) -> bool:
45
+ """Check if address represents manual task data"""
46
+ if isinstance(address, str):
47
+ # Handle string format "connection_key:collection_name"
48
+ return address.endswith(f":{ManualTaskAddress.MANUAL_DATA_COLLECTION}")
49
+
50
+ # Handle CollectionAddress object
51
+ return address.collection == ManualTaskAddress.MANUAL_DATA_COLLECTION
52
+
53
+ @staticmethod
54
+ def get_connection_key(address: CollectionAddress) -> str:
55
+ """Extract connection config key from manual task address"""
56
+ if not ManualTaskAddress.is_manual_task_address(address):
57
+ raise ValueError(f"Not a manual task address: {address}")
58
+
59
+ if isinstance(address, str):
60
+ # Handle string format "connection_key:collection_name"
61
+ return address.split(":")[0]
62
+
63
+ # Handle CollectionAddress object
64
+ return address.dataset
65
+
66
+
67
+ def get_manual_task_addresses(db: Session) -> List[CollectionAddress]:
68
+ """
69
+ Get manual task addresses for all connection configs that have manual tasks.
70
+
71
+ Note: Manual tasks should be included in the graph if they exist for any connection config
72
+ that's part of the dataset graph, regardless of specific policy targets. This allows
73
+ manual tasks to collect additional data that may be needed for the privacy request.
74
+ """
75
+ # Get all connection configs that have manual tasks
76
+ connection_configs_with_manual_tasks = (
77
+ db.query(ConnectionConfig)
78
+ .join(ManualTask, ConnectionConfig.id == ManualTask.parent_entity_id)
79
+ .filter(ManualTask.parent_entity_type == "connection_config")
80
+ .all()
81
+ )
82
+
83
+ # Create addresses for all connections that have manual tasks
84
+ manual_task_addresses = []
85
+ for config in connection_configs_with_manual_tasks:
86
+ manual_task_addresses.append(ManualTaskAddress.create(config.key))
87
+
88
+ return manual_task_addresses
89
+
90
+
91
+ def get_manual_tasks_for_connection_config(
92
+ db: Session, connection_config_key: str
93
+ ) -> List[ManualTask]:
94
+ """Get all ManualTasks for a specific connection config"""
95
+ connection_config = (
96
+ db.query(ConnectionConfig)
97
+ .filter(ConnectionConfig.key == connection_config_key)
98
+ .first()
99
+ )
100
+
101
+ if not connection_config:
102
+ return []
103
+
104
+ return (
105
+ db.query(ManualTask)
106
+ .filter(
107
+ ManualTask.parent_entity_id == connection_config.id,
108
+ ManualTask.parent_entity_type == "connection_config",
109
+ )
110
+ .all()
111
+ )
112
+
113
+
114
+ def create_manual_data_traversal_node(
115
+ db: Session, address: CollectionAddress
116
+ ) -> "TraversalNode":
117
+ """
118
+ Create a TraversalNode for a manual_data collection
119
+ """
120
+ connection_key = address.dataset
121
+
122
+ # Get manual tasks for this connection to determine fields
123
+ manual_tasks = get_manual_tasks_for_connection_config(db, connection_key)
124
+
125
+ # Create fields based on ManualTaskConfigFields
126
+ fields: List[Field] = []
127
+ for manual_task in manual_tasks:
128
+ for config in manual_task.configs:
129
+ for field in config.field_definitions:
130
+ # Create a scalar field for each manual task field
131
+ # Extract data categories from field metadata if available
132
+ field_metadata = field.field_metadata or {}
133
+ data_categories = field_metadata.get("data_categories", [])
134
+
135
+ scalar_field = ScalarField(
136
+ name=field.field_key,
137
+ data_categories=data_categories,
138
+ # Manual task fields don't have complex relationships
139
+ )
140
+ fields.append(scalar_field)
141
+
142
+ # Create a synthetic Collection
143
+ collection = Collection(
144
+ name=ManualTaskAddress.MANUAL_DATA_COLLECTION,
145
+ fields=fields,
146
+ # Manual tasks don't have complex dependencies
147
+ after=set(),
148
+ )
149
+
150
+ # Create a synthetic GraphDataset
151
+ dataset = GraphDataset(
152
+ name=connection_key,
153
+ collections=[collection],
154
+ connection_key=connection_key,
155
+ after=set(),
156
+ )
157
+
158
+ # Create Node and TraversalNode (import locally to avoid cyclic import)
159
+ from fides.api.graph.traversal import TraversalNode # local import
160
+
161
+ node = Node(dataset, collection)
162
+ traversal_node = TraversalNode(node)
163
+
164
+ return traversal_node
165
+
166
+
167
+ def create_manual_task_instances_for_privacy_request(
168
+ db: Session, privacy_request: PrivacyRequest
169
+ ) -> List[ManualTaskInstance]:
170
+ """Create ManualTaskInstance entries for all active manual tasks relevant to a privacy request."""
171
+ instances = []
172
+
173
+ # Get all connection configs that have manual tasks
174
+ connection_configs_with_manual_tasks = (
175
+ db.query(ConnectionConfig)
176
+ .join(ManualTask, ConnectionConfig.id == ManualTask.parent_entity_id)
177
+ .filter(ManualTask.parent_entity_type == "connection_config")
178
+ .all()
179
+ )
180
+
181
+ for connection_config in connection_configs_with_manual_tasks:
182
+ manual_tasks = (
183
+ db.query(ManualTask)
184
+ .filter(
185
+ ManualTask.parent_entity_id == connection_config.id,
186
+ ManualTask.parent_entity_type == "connection_config",
187
+ )
188
+ .all()
189
+ )
190
+
191
+ for manual_task in manual_tasks:
192
+ # Get the active config for this manual task
193
+ active_config = (
194
+ db.query(ManualTaskConfig)
195
+ .filter(
196
+ ManualTaskConfig.task_id == manual_task.id,
197
+ ManualTaskConfig.is_current.is_(True),
198
+ )
199
+ .first()
200
+ )
201
+
202
+ if not active_config:
203
+ continue # Skip if no active config
204
+
205
+ # Check if instance already exists
206
+ existing_instance = (
207
+ db.query(ManualTaskInstance)
208
+ .filter(
209
+ ManualTaskInstance.entity_id == privacy_request.id,
210
+ ManualTaskInstance.entity_type == "privacy_request",
211
+ ManualTaskInstance.task_id == manual_task.id,
212
+ ManualTaskInstance.config_id == active_config.id,
213
+ )
214
+ .first()
215
+ )
216
+
217
+ if not existing_instance:
218
+ instance = ManualTaskInstance(
219
+ entity_id=privacy_request.id,
220
+ entity_type=ManualTaskEntityType.privacy_request,
221
+ task_id=manual_task.id,
222
+ config_id=active_config.id,
223
+ )
224
+ db.add(instance)
225
+ instances.append(instance)
226
+
227
+ if instances:
228
+ db.commit()
229
+
230
+ return instances
231
+
232
+
233
+ def get_manual_task_instances_for_privacy_request(
234
+ db: Session, privacy_request: PrivacyRequest
235
+ ) -> List[ManualTaskInstance]:
236
+ """Get all manual task instances for a privacy request."""
237
+ return (
238
+ db.query(ManualTaskInstance)
239
+ .filter(
240
+ ManualTaskInstance.entity_id == privacy_request.id,
241
+ ManualTaskInstance.entity_type == "privacy_request",
242
+ )
243
+ .all()
244
+ )
245
+
246
+
247
+ def create_manual_task_artificial_graphs(
248
+ db: Session,
249
+ ) -> List:
250
+ """
251
+ Create artificial GraphDataset objects for manual tasks that can be included
252
+ in the main dataset graph during the dataset configuration phase.
253
+
254
+ Manual tasks should be treated as data sources/datasets rather than being
255
+ appended to the traversal graph later.
256
+
257
+ Manual task collections are designed as root nodes that execute immediately when
258
+ the privacy request starts, in parallel with identity processing. They don't depend
259
+ on identity data since they provide manually-entered data rather than consuming it.
260
+
261
+ Args:
262
+ db: Database session
263
+ policy: The policy being executed
264
+
265
+ Returns:
266
+ List of GraphDataset objects representing manual tasks as root nodes
267
+ """
268
+
269
+ manual_task_graphs = []
270
+ manual_addresses = get_manual_task_addresses(db)
271
+
272
+ for address in manual_addresses:
273
+ connection_key = address.dataset
274
+
275
+ # Get manual tasks for this connection to determine fields
276
+ manual_tasks = get_manual_tasks_for_connection_config(db, connection_key)
277
+
278
+ # Create fields based only on ManualTaskConfigFields
279
+ fields: List = []
280
+
281
+ # Manual task collections act as root nodes - they don't need identity dependencies
282
+ # since they provide manually-entered data rather than consuming identity data.
283
+ for manual_task in manual_tasks:
284
+ for config in manual_task.configs:
285
+ if config.config_type not in [
286
+ ManualTaskConfigurationType.access_privacy_request,
287
+ ManualTaskConfigurationType.erasure_privacy_request,
288
+ ]:
289
+ continue
290
+ if not config.is_current:
291
+ continue
292
+ for field in config.field_definitions:
293
+ # Create a scalar field for each manual task field
294
+ field_metadata = field.field_metadata or {}
295
+ data_categories = field_metadata.get("data_categories", [])
296
+
297
+ scalar_field = ScalarField(
298
+ name=field.field_key,
299
+ data_categories=data_categories,
300
+ )
301
+ fields.append(scalar_field)
302
+
303
+ if fields: # Only create graph if there are fields
304
+ # Create a synthetic Collection
305
+ collection = Collection(
306
+ name=ManualTaskAddress.MANUAL_DATA_COLLECTION,
307
+ fields=fields,
308
+ # Manual tasks have no dependencies - they're root nodes
309
+ after=set(),
310
+ )
311
+
312
+ # Create a synthetic GraphDataset
313
+ graph_dataset = GraphDataset(
314
+ name=connection_key,
315
+ collections=[collection],
316
+ connection_key=connection_key,
317
+ after=set(),
318
+ )
319
+
320
+ manual_task_graphs.append(graph_dataset)
321
+
322
+ return manual_task_graphs
@@ -15,6 +15,7 @@ from fides.api.service.connectors import (
15
15
  FidesConnector,
16
16
  GoogleCloudSQLMySQLConnector,
17
17
  GoogleCloudSQLPostgresConnector,
18
+ ManualTaskConnector,
18
19
  MariaDBConnector,
19
20
  MicrosoftSQLServerConnector,
20
21
  MongoDBConnector,
@@ -92,6 +93,8 @@ class Connections:
92
93
  return S3Connector(connection_config)
93
94
  if connection_config.connection_type == ConnectionType.scylla:
94
95
  return ScyllaConnector(connection_config)
96
+ if connection_config.connection_type == ConnectionType.manual_task:
97
+ return ManualTaskConnector(connection_config)
95
98
  raise NotImplementedError(
96
99
  f"No connector available for {connection_config.connection_type}"
97
100
  )
@@ -25,6 +25,11 @@ from fides.api.models.connectionconfig import (
25
25
  ConnectionType,
26
26
  )
27
27
  from fides.api.models.datasetconfig import DatasetConfig
28
+ from fides.api.models.manual_task import (
29
+ ManualTask,
30
+ ManualTaskParentEntityType,
31
+ ManualTaskType,
32
+ )
28
33
  from fides.api.models.manual_webhook import AccessManualWebhook
29
34
  from fides.api.models.privacy_request import PrivacyRequest
30
35
  from fides.api.models.sql_models import Dataset as CtlDataset # type: ignore
@@ -243,7 +248,9 @@ def patch_connection_configs(
243
248
  ).model_dump(mode="json")
244
249
  connection_config.save(db=db)
245
250
  created_or_updated.append(
246
- ConnectionConfigurationResponse(**connection_config.__dict__)
251
+ ConnectionConfigurationResponse.model_validate(
252
+ connection_config
253
+ )
247
254
  )
248
255
  continue
249
256
 
@@ -268,8 +275,24 @@ def patch_connection_configs(
268
275
  connection_config = ConnectionConfig.create_or_update(
269
276
  db, data=config_dict, check_name=False
270
277
  )
278
+
279
+ # Automatically create a ManualTask if this is a connection config of type manual_task
280
+ # and it doesn't already have one
281
+ if (
282
+ connection_config.connection_type == ConnectionType.manual_task
283
+ and not connection_config.manual_task
284
+ ):
285
+ ManualTask.create(
286
+ db=db,
287
+ data={
288
+ "task_type": ManualTaskType.privacy_request,
289
+ "parent_entity_id": connection_config.id,
290
+ "parent_entity_type": ManualTaskParentEntityType.connection_config,
291
+ },
292
+ )
293
+
271
294
  created_or_updated.append(
272
- ConnectionConfigurationResponse(**connection_config.__dict__)
295
+ ConnectionConfigurationResponse.model_validate(connection_config)
273
296
  )
274
297
  except KeyOrNameAlreadyExists as exc:
275
298
  logger.warning(
@@ -155,6 +155,10 @@ AUTHORIZE = "/connection/{connection_key}/authorize"
155
155
  ACCESS_MANUAL_WEBHOOKS = "/access_manual_webhook"
156
156
  ACCESS_MANUAL_WEBHOOK = CONNECTION_BY_KEY + "/access_manual_webhook"
157
157
 
158
+ # Manual Tasks
159
+ MANUAL_TASKS = "/manual-tasks"
160
+ MANUAL_TASK = CONNECTION_BY_KEY + "/manual-task"
161
+
158
162
  # Collection URLs
159
163
  DATASETS = "/dataset"
160
164
  DATASET_CONFIG = "/datasetconfig"