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.
- {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/METADATA +3 -3
- {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/RECORD +252 -257
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/41a634d8c669_manual_task_restrict_deletes.py +257 -0
- fides/api/alembic/migrations/versions/6a76a1fa4f3f_add_manual_task_instance_table.py +256 -0
- fides/api/alembic/migrations/versions/aadfe83c5644_add_manual_task_to_connectiontype_enum.py +46 -0
- fides/api/api/v1/api.py +2 -0
- fides/api/api/v1/endpoints/partitioning_endpoints.py +108 -0
- fides/api/db/base.py +6 -3
- fides/api/db/database.py +27 -2
- fides/api/graph/config.py +16 -9
- fides/api/graph/traversal.py +18 -0
- fides/api/models/attachment.py +15 -3
- fides/api/models/comment.py +23 -5
- fides/api/models/connectionconfig.py +11 -0
- fides/api/models/db_cache.py +1 -1
- fides/api/models/detection_discovery/core.py +15 -15
- fides/api/models/fides_user_respondent_email_verification.py +27 -2
- fides/api/models/manual_task.py +965 -0
- fides/api/models/tcf_publisher_restrictions.py +16 -4
- fides/api/schemas/partitioning/__init__.py +17 -0
- fides/api/schemas/partitioning/bigquery_time_based_partitioning.py +31 -0
- fides/api/schemas/partitioning/time_based_partitioning.py +1376 -0
- fides/api/service/connectors/__init__.py +4 -0
- fides/api/service/connectors/manual_task_connector.py +96 -0
- fides/api/service/connectors/query_configs/bigquery_query_config.py +44 -22
- fides/api/service/connectors/query_configs/query_config.py +5 -2
- fides/api/service/privacy_request/dsr_package/templates/collection_index.html +9 -1
- fides/api/service/privacy_request/dsr_package/templates/main.css +6 -2
- fides/api/service/privacy_request/request_runner_service.py +7 -0
- fides/api/task/create_request_tasks.py +16 -0
- fides/api/task/execute_request_tasks.py +10 -1
- fides/api/task/filter_results.py +6 -0
- fides/api/task/graph_task.py +1 -0
- fides/api/task/manual/manual_task_graph_task.py +300 -0
- fides/api/task/manual/manual_task_utils.py +322 -0
- fides/api/task/task_resources.py +3 -0
- fides/api/util/connection_util.py +25 -2
- fides/common/api/v1/urn_registry.py +4 -0
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{1040-c1c1372a7f909aef.js → 1040-d246ed641088a416.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/1169-ae67fde0c6d69abc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1807-3beab149351d5ded.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{1817-96182c1558f80b63.js → 1817-60f08a3619b9139c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{2921-f5608275555bd7d9.js → 2921-85515257dd94ef4d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3450-272f26c102f3510d.js → 3450-4e472b9e2754fa47.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/3615-5e2d062d684b8fa1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{3855-beb58821d1ddba89.js → 3855-2d045674fbf72a3c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3872-82482e55e69b5a93.js → 3872-056ddf3ed9d10b51.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3923-a54c286a2ba0a47a.js → 3923-257df982a95371b5.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{401-d2ce0a5a9120e056.js → 401-3f2160e3910d075b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{409-86f4f687105917fb.js → 409-3e2248a63dd60e58.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/4121-10bfa009892586fa.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4230-63abbdfb9e9016b9.js → 4230-38c6e446801a8729.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{431-77d59d43e90058ca.js → 431-13b0ef67d5a3df2f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{5309-10f68cf805817cfb.js → 5309-fd8cd5aedd45f7c1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{5574-b6db9d62362e72d9.js → 5574-028ef28c3cf16995.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/570-c99f07161bd339cd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6084-fa1c82d03f6c256a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6662-d8ae12f69d325004.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{6853-8a1b8e1c8b249f2f.js → 6853-2644f28976b46c25.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6882-6c94583bffe85ba7.js → 6882-ea071425d25dd2b0.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6954-dc3540389daf94da.js → 6954-bb360fb60aac9440.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/7476-7073ec015f84a3e0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7630-302a13c63f9bfb45.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/787-b393c03ade9d93dc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{79-26ccd45dfd6653a7.js → 79-b67ba449b0f2cc9c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{796-38c0f7e6755ad359.js → 796-b7608f09607288b8.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/827-c6fe34fb336467ae.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9014-eeae6f581158e645.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{9046-bbce3c73af16daf9.js → 9046-ece5efe762b810cb.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{905-742074a074be1055.js → 905-ffdbd0b14167e8bd.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{9226-5e0ce31cfdedd5ee.js → 9226-c9111590692341b1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{9392.25024e070026343d.js → 9392.9a948112de74781b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{9676.e60a53f1f5890847.js → 9676.cc515c853b8cf578.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/9767-277a0229aae7662a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9826-82c473dcaf892d00.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{9951-d3d5d0fe4c4edb86.js → 9951-dbd76d7f3a7f1b9a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{404-d41660858638adee.js → 404-ef01376efb8427e1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-6b3539f8d82ce9ae.js → _app-7430e1499432b029.js} +92 -89
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-7c5e1e845372c99b.js → manual-4057c399f58d331e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-87e78f52af21c4a9.js → multiple-2726ca51f0327347.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-a2457d5da25aa854.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-6374a3a7747df964.js → add-vendors-9fe46ed10a7e7a1a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-0512151e2ed0f4c1.js → configure-095828301f22cdaa.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-09435f4ab2deafd2.js → [id]-de60a5b74f6b20ff.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-a0039f216fb3eb93.js → new-06bb3b0bf097fcdb.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-cd036518b5d4efbe.js → privacy-experience-1ee05fd0bc012ec6.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-0e534580abf670be.js → [id]-5a2e61c7d88bdda1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-bb1790eb87b63109.js → new-1bc69669215fa55c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-67e9846f877a3f38.js → privacy-notices-fa8394d2e1072aba.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-69a92b6ddbc2bae2.js → properties-213c5405e9e4219c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{reporting-d07d05f4b898a5ed.js → reporting-44aa77149d71a6fe.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{consent-23e886d692ab6d1a.js → consent-23ab7bd613a0ad53.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-c623d6f1a61c8ea9.js → [resourceUrn]-11d52f1570759c4d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-6c766c766dc97c5c.js → [projectUrn]-dae11464a091537f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-28937a8da3d73145.js → projects-7442023478422295.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-57bd5cdf784f059f.js → [resourceUrn]-b83afa5565d0c84e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-51d99174c8006eb5.js → resources-d8db234a44a2ddf4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-627fbf19dce88e81.js → data-catalog-5597d2e691313bb0.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-ed1629b05519a370.js → [systemId]-da06d05d255d0cd0.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-ad307bfc5f51fd9d.js → [monitorId]-895591fe32af0f4c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-07cbd61ede6e18ac.js → action-center-a932a39e29ac3489.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-6a2aaed8d0e66d82.js → activity-2cfdf4d55a7594ba.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-06edce289876dea1.js → [resourceUrn]-393e20924c83373e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-faf326a6200637d0.js → detection-8733807dad4bc96e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-64acf269256ee74f.js → [resourceUrn]-14bd7500362ff224.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-8c3e4be6d36da66d.js → discovery-9e7dfd5a6acc2e8f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-b576a94b583a7940.js → datamap-07881d1a5fc03d41.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-4912858ffde4621a.js → [...subfieldNames]-57be9d40c67b0acb.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-df21dd7ca0f35718.js → [collectionName]-bd78d6c6264e2467.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-5ce28a329e8f667f.js → [datasetId]-3c9103487b55e76f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-e30a546a8cfb6658.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-c6fab547396df6ac.js → dataset-500610c0b60f9e7e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-d04c9925d324eee1.js → [id]-34958fe8183d9479.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-656abd09ea5ee39a.js → new-761294dbaf59bbe1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-59abd36059863e05.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{index-b66687e8194495a2.js → index-ae100a873eb66d59.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-8eee4d0314c83e50.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-bf3b87b0dd702551.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-b74db8488e2e4b58.js → [id]-2aada2c7b9156053.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-a607018ff097b6c2.js → add-template-fbb7b0c43dedf072.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-4a1a04c5179d2053.js → messaging-7230d7ee21368cef.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/{ant-components-1567c9770a5f05aa.js → ant-components-7c234412502f8102.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{AntForm-4949fe7f1815462b.js → AntForm-72791aab60f3a4bf.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikAntFormItem-6085f73850302d55.js → FormikAntFormItem-da7d11d6146fa746.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikControlled-a3158e8217c13850.js → FormikControlled-3045d24344e99017.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikField-8576f1ef5c67d87d.js → FormikField-5b5b165d3f41a1de.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/{forms-341e67462b5e3352.js → forms-3afd8b6ba6234366.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/table-migration-52d50286216bcb8c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-00fb442c4adb7371.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-535b6e5003d892eb.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-7ded688a1e832c63.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-d24230b890b13762.js → configure-08e0863d432b3348.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-c8b02ae92dd7e45b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-2bedc5013e13ab52.js → [id]-b8dcb1f5213521ef.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-9d68bd70299dd945.js → add-property-7d8418bb7cb9e1fd.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{properties-c3116b6bfe2e695e.js → properties-537003822f360ae7.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-6dfa7091b99d8321.js → datamap-ac25749935da453b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-9751059905bba190.js → alpha-647d59bb0aa060bf.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-ad29d77012ec9dba.js → about-73adc03fe0182e3f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/{[purpose_id]-32e3190c1ad00b40.js → [purpose_id]-ffa9e9e79ad75828.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-4fb6c8c4bd9cdb3f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-d2c0aac32f5d2930.js → custom-fields-94c1b7bfc6cec4d2.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-a10674380f94d014.js → domain-records-e9838b8e49e64e57.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-0de16778ad2f8865.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/email-templates-aac71677024210ad.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-cc7d629433fa6d44.js → locations-f43e238571b21643.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-f5e0a34f6ae473ad.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-64a813cb2741683b.js → regulations-05d3c86ca3ca4d5d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-31fcf7b52d8028b0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-fb3d094e4f4585f6.js → [id]-6db01a1afe2f82d5.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{systems-c2df5b7b0596a9cb.js → systems-8ec7472a7032301f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-84caae0fc02f8ae1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-b124cc24b930c9e1.js → new-a2524414e968f862.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-fd2ff6b13052c54e.js → [id]-e452541827698b1f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-fa052b0439920ef6.js → user-management-530be849391d2425.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{webpack-e61d457474e00565.js → webpack-e25ccaae1ef867a2.js} +1 -1
- fides/ui-build/static/admin/_next/static/css/23391a3311f80cfe.css +1 -0
- fides/ui-build/static/admin/_next/static/css/399d4757862a3982.css +1 -0
- fides/ui-build/static/admin/_next/static/z7GzAZhoGiO521zm7WPYX/_buildManifest.js +1 -0
- fides/ui-build/static/admin/add-systems/manual.html +1 -1
- fides/ui-build/static/admin/add-systems/multiple.html +1 -1
- fides/ui-build/static/admin/add-systems.html +1 -1
- fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
- fides/ui-build/static/admin/consent/configure.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
- fides/ui-build/static/admin/consent/properties.html +1 -1
- fides/ui-build/static/admin/consent/reporting.html +1 -1
- fides/ui-build/static/admin/consent.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
- fides/ui-build/static/admin/data-catalog.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
- fides/ui-build/static/admin/data-discovery/activity.html +1 -1
- fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/detection.html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
- fides/ui-build/static/admin/datamap.html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
- fides/ui-build/static/admin/dataset/new.html +1 -1
- fides/ui-build/static/admin/dataset.html +1 -1
- fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
- fides/ui-build/static/admin/datastore-connection/new.html +1 -1
- fides/ui-build/static/admin/datastore-connection.html +1 -1
- fides/ui-build/static/admin/index.html +1 -1
- fides/ui-build/static/admin/integrations/[id].html +1 -1
- fides/ui-build/static/admin/integrations.html +1 -1
- fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
- fides/ui-build/static/admin/lib/fides-headless.js +1 -1
- fides/ui-build/static/admin/lib/fides-preview.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
- fides/ui-build/static/admin/lib/fides.js +2 -2
- fides/ui-build/static/admin/login/[provider].html +1 -1
- fides/ui-build/static/admin/login.html +1 -1
- fides/ui-build/static/admin/messaging/[id].html +1 -1
- fides/ui-build/static/admin/messaging/add-template.html +1 -1
- fides/ui-build/static/admin/messaging.html +1 -1
- fides/ui-build/static/admin/poc/ant-components.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
- fides/ui-build/static/admin/poc/forms.html +1 -1
- fides/ui-build/static/admin/poc/table-migration.html +1 -1
- fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
- fides/ui-build/static/admin/privacy-requests.html +1 -1
- fides/ui-build/static/admin/properties/[id].html +1 -1
- fides/ui-build/static/admin/properties/add-property.html +1 -1
- fides/ui-build/static/admin/properties.html +1 -1
- fides/ui-build/static/admin/reporting/datamap.html +1 -1
- fides/ui-build/static/admin/settings/about/alpha.html +1 -1
- fides/ui-build/static/admin/settings/about.html +1 -1
- fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
- fides/ui-build/static/admin/settings/consent.html +1 -1
- fides/ui-build/static/admin/settings/custom-fields.html +1 -1
- fides/ui-build/static/admin/settings/domain-records.html +1 -1
- fides/ui-build/static/admin/settings/domains.html +1 -1
- fides/ui-build/static/admin/settings/email-templates.html +1 -1
- fides/ui-build/static/admin/settings/locations.html +1 -1
- fides/ui-build/static/admin/settings/organization.html +1 -1
- fides/ui-build/static/admin/settings/regulations.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id].html +1 -1
- fides/ui-build/static/admin/systems.html +1 -1
- fides/ui-build/static/admin/taxonomy.html +1 -1
- fides/ui-build/static/admin/user-management/new.html +1 -1
- fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
- fides/ui-build/static/admin/user-management.html +1 -1
- fides/api/models/manual_tasks/__init__.py +0 -14
- fides/api/models/manual_tasks/manual_task.py +0 -120
- fides/api/models/manual_tasks/manual_task_config.py +0 -136
- fides/api/models/manual_tasks/manual_task_log.py +0 -104
- fides/api/schemas/manual_tasks/manual_task_config.py +0 -311
- fides/api/schemas/manual_tasks/manual_task_schemas.py +0 -79
- fides/api/schemas/manual_tasks/manual_task_status.py +0 -151
- fides/service/manual_tasks/__init__.py +0 -0
- fides/service/manual_tasks/manual_task_config_service.py +0 -370
- fides/service/manual_tasks/manual_task_service.py +0 -294
- fides/service/manual_tasks/utils.py +0 -185
- fides/ui-build/static/admin/_next/static/TUIhCXaasOCEZxPYTqsV5/_buildManifest.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1100-3fdbdf211c3c2a5b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2430-b480401d44c55416.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3505-192986c86dc47869.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3513-a563133845dc990f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3670-2abd9b2f17770872.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3983-17ae9c232bddc413.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4060-3486b45081151b69.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4121-f0aecb2abd384945.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4481-d181a9db72984adf.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6060-cb1ab5be7067bf7b.js +0 -4
- fides/ui-build/static/admin/_next/static/chunks/6277-ccdb50f676a1b336.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6659-b2088f525bf13c17.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6662-42940a2b00933e79.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/69-943b19d39da339d9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7553-e7ae268701f3dcfe.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8433-1e065c55c8da73b0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9767-96ed554a043c3c4d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/c78d26b1-88a3e1bacb2a03c2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-68955d8441e60668.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-5287c76ecf600281.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-23fbec0590c8d192.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-906bc5f05702efb0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-024a1facb9be04d0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/table-migration-6f0e64f0c52bd68f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-6971c7773dbf9b51.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-303bd2182da03088.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-831572cc7f42615f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-569ff31eff637034.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-13a17a4ace7293d1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-78449b0e02bced88.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/email-templates-7d299c4cb3199036.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-a9fa55c40fa570a6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-547b0cfe1e49e6d2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-261deb6fb3e51cb3.js +0 -1
- fides/ui-build/static/admin/_next/static/css/5ded47c57dae5baf.css +0 -1
- fides/ui-build/static/admin/_next/static/css/c693338e3bc8dcc6.css +0 -1
- fides/ui-build/static/admin/_next/static/css/e458b5f1afdbb7fc.css +0 -1
- {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.64.5rc4.dist-info → ethyca_fides-2.64.6b1.dist-info}/top_level.txt +0 -0
- /fides/api/{schemas/manual_tasks → task/manual}/__init__.py +0 -0
- /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
|
fides/api/task/task_resources.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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"
|