ethyca-fides 2.63.0rc2__py2.py3-none-any.whl → 2.63.1__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.
Files changed (131) hide show
  1. {ethyca_fides-2.63.0rc2.dist-info → ethyca_fides-2.63.1.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.63.0rc2.dist-info → ethyca_fides-2.63.1.dist-info}/RECORD +131 -112
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/29e56fa1fdb3_add_monitor_tasks.py +147 -0
  5. fides/api/alembic/migrations/versions/5efcdf18438e_add_manual_task_tables.py +160 -0
  6. fides/api/alembic/migrations/versions/bf713b5a021d_staged_resource_ancestor_link_data_.py +20 -11
  7. fides/api/api/v1/endpoints/privacy_request_endpoints.py +4 -4
  8. fides/api/db/base.py +7 -1
  9. fides/api/migrations/post_upgrade_index_creation.py +3 -3
  10. fides/api/models/connectionconfig.py +1 -1
  11. fides/api/models/detection_discovery/__init__.py +35 -0
  12. fides/api/models/detection_discovery/monitor_task.py +162 -0
  13. fides/api/models/field_types/__init__.py +5 -0
  14. fides/api/models/field_types/encrypted_large_data.py +151 -0
  15. fides/api/models/manual_tasks/__init__.py +8 -0
  16. fides/api/models/manual_tasks/manual_task.py +110 -0
  17. fides/api/models/manual_tasks/manual_task_log.py +100 -0
  18. fides/api/models/privacy_preference.py +1 -1
  19. fides/api/models/privacy_request/execution_log.py +3 -31
  20. fides/api/models/privacy_request/privacy_request.py +16 -3
  21. fides/api/models/privacy_request/request_task.py +36 -25
  22. fides/api/models/worker_task.py +96 -0
  23. fides/api/schemas/external_storage.py +22 -0
  24. fides/api/schemas/manual_tasks/__init__.py +0 -0
  25. fides/api/schemas/manual_tasks/manual_task_schemas.py +79 -0
  26. fides/api/schemas/manual_tasks/manual_task_status.py +151 -0
  27. fides/api/schemas/privacy_request.py +1 -12
  28. fides/api/service/connectors/base_erasure_email_connector.py +1 -1
  29. fides/api/service/connectors/consent_email_connector.py +2 -1
  30. fides/api/service/connectors/dynamic_erasure_email_connector.py +2 -1
  31. fides/api/service/connectors/erasure_email_connector.py +1 -1
  32. fides/api/service/external_data_storage.py +371 -0
  33. fides/api/service/privacy_request/request_runner_service.py +5 -5
  34. fides/api/service/privacy_request/request_service.py +1 -1
  35. fides/api/task/create_request_tasks.py +1 -1
  36. fides/api/task/execute_request_tasks.py +9 -8
  37. fides/api/task/graph_task.py +22 -10
  38. fides/api/util/consent_util.py +1 -1
  39. fides/api/util/data_size.py +102 -0
  40. fides/api/util/encryption/aes_gcm_encryption_util.py +271 -0
  41. fides/service/manual_tasks/__init__.py +0 -0
  42. fides/service/manual_tasks/manual_task_service.py +150 -0
  43. fides/service/privacy_request/privacy_request_service.py +1 -1
  44. fides/ui-build/static/admin/404.html +1 -1
  45. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  46. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  47. fides/ui-build/static/admin/add-systems.html +1 -1
  48. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  49. fides/ui-build/static/admin/consent/configure.html +1 -1
  50. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  51. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  52. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  53. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  54. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  55. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  56. fides/ui-build/static/admin/consent/properties.html +1 -1
  57. fides/ui-build/static/admin/consent/reporting.html +1 -1
  58. fides/ui-build/static/admin/consent.html +1 -1
  59. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  60. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  61. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  62. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  63. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  64. fides/ui-build/static/admin/data-catalog.html +1 -1
  65. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  66. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  67. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  68. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  69. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  70. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  71. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  72. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  73. fides/ui-build/static/admin/datamap.html +1 -1
  74. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  75. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  76. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  77. fides/ui-build/static/admin/dataset/new.html +1 -1
  78. fides/ui-build/static/admin/dataset.html +1 -1
  79. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  80. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  81. fides/ui-build/static/admin/datastore-connection.html +1 -1
  82. fides/ui-build/static/admin/index.html +1 -1
  83. fides/ui-build/static/admin/integrations/[id].html +1 -1
  84. fides/ui-build/static/admin/integrations.html +1 -1
  85. fides/ui-build/static/admin/login/[provider].html +1 -1
  86. fides/ui-build/static/admin/login.html +1 -1
  87. fides/ui-build/static/admin/messaging/[id].html +1 -1
  88. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  89. fides/ui-build/static/admin/messaging.html +1 -1
  90. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  91. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  92. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  93. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  94. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  95. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  96. fides/ui-build/static/admin/poc/forms.html +1 -1
  97. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  98. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  99. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  100. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  101. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  102. fides/ui-build/static/admin/privacy-requests.html +1 -1
  103. fides/ui-build/static/admin/properties/[id].html +1 -1
  104. fides/ui-build/static/admin/properties/add-property.html +1 -1
  105. fides/ui-build/static/admin/properties.html +1 -1
  106. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  107. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  108. fides/ui-build/static/admin/settings/about.html +1 -1
  109. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  110. fides/ui-build/static/admin/settings/consent.html +1 -1
  111. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  112. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  113. fides/ui-build/static/admin/settings/domains.html +1 -1
  114. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  115. fides/ui-build/static/admin/settings/locations.html +1 -1
  116. fides/ui-build/static/admin/settings/organization.html +1 -1
  117. fides/ui-build/static/admin/settings/regulations.html +1 -1
  118. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  119. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  120. fides/ui-build/static/admin/systems.html +1 -1
  121. fides/ui-build/static/admin/taxonomy.html +1 -1
  122. fides/ui-build/static/admin/user-management/new.html +1 -1
  123. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  124. fides/ui-build/static/admin/user-management.html +1 -1
  125. {ethyca_fides-2.63.0rc2.dist-info → ethyca_fides-2.63.1.dist-info}/WHEEL +0 -0
  126. {ethyca_fides-2.63.0rc2.dist-info → ethyca_fides-2.63.1.dist-info}/entry_points.txt +0 -0
  127. {ethyca_fides-2.63.0rc2.dist-info → ethyca_fides-2.63.1.dist-info}/licenses/LICENSE +0 -0
  128. {ethyca_fides-2.63.0rc2.dist-info → ethyca_fides-2.63.1.dist-info}/top_level.txt +0 -0
  129. /fides/api/models/{detection_discovery.py → detection_discovery/core.py} +0 -0
  130. /fides/ui-build/static/admin/_next/static/{Fb70i-8GI-owNAvgEJWhA → SZn_Fpr_qG1COMjkdloep}/_buildManifest.js +0 -0
  131. /fides/ui-build/static/admin/_next/static/{Fb70i-8GI-owNAvgEJWhA → SZn_Fpr_qG1COMjkdloep}/_ssgManifest.js +0 -0
@@ -0,0 +1,147 @@
1
+ """add_monitor_tasks
2
+
3
+ Revision ID: 29e56fa1fdb3
4
+ Revises: 5efcdf18438e
5
+ Create Date: 2025-06-11 14:40:08.384571
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from sqlalchemy.dialects import postgresql
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "29e56fa1fdb3"
15
+ down_revision = "5efcdf18438e"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ op.create_table(
22
+ "monitortask",
23
+ sa.Column("id", sa.String(length=255), nullable=False),
24
+ sa.Column(
25
+ "created_at",
26
+ sa.DateTime(timezone=True),
27
+ server_default=sa.text("now()"),
28
+ nullable=True,
29
+ ),
30
+ sa.Column(
31
+ "updated_at",
32
+ sa.DateTime(timezone=True),
33
+ server_default=sa.text("now()"),
34
+ nullable=True,
35
+ ),
36
+ sa.Column("action_type", sa.String(), nullable=False),
37
+ sa.Column(
38
+ "status",
39
+ sa.Enum(
40
+ "in_processing",
41
+ "pending",
42
+ "complete",
43
+ "error",
44
+ "paused",
45
+ "retrying",
46
+ "skipped",
47
+ name="executionlogstatus",
48
+ native_enum=False,
49
+ ),
50
+ nullable=False,
51
+ ),
52
+ sa.Column("celery_id", sa.String(length=255), nullable=False),
53
+ sa.Column(
54
+ "task_arguments", postgresql.JSONB(astext_type=sa.Text()), nullable=True
55
+ ),
56
+ sa.Column("message", sa.String(), nullable=True),
57
+ sa.Column("monitor_config_id", sa.String(), nullable=False),
58
+ sa.Column("staged_resource_urns", sa.ARRAY(sa.String()), nullable=True),
59
+ sa.Column("child_resource_urns", sa.ARRAY(sa.String()), nullable=True),
60
+ sa.ForeignKeyConstraint(
61
+ ["monitor_config_id"], ["monitorconfig.id"], ondelete="CASCADE"
62
+ ),
63
+ sa.PrimaryKeyConstraint("id"),
64
+ sa.UniqueConstraint("celery_id"),
65
+ )
66
+ op.create_index(
67
+ op.f("ix_monitortask_action_type"), "monitortask", ["action_type"], unique=False
68
+ )
69
+ op.create_index(op.f("ix_monitortask_id"), "monitortask", ["id"], unique=False)
70
+ op.create_index(
71
+ op.f("ix_monitortask_monitor_config_id"),
72
+ "monitortask",
73
+ ["monitor_config_id"],
74
+ unique=False,
75
+ )
76
+ op.create_index(
77
+ op.f("ix_monitortask_status"), "monitortask", ["status"], unique=False
78
+ )
79
+ op.create_table(
80
+ "monitortaskexecutionlog",
81
+ sa.Column("id", sa.String(length=255), nullable=False),
82
+ sa.Column(
83
+ "status",
84
+ postgresql.ENUM(name="executionlogstatus", create_type=False),
85
+ nullable=False,
86
+ ),
87
+ sa.Column("message", sa.String(), nullable=True),
88
+ sa.Column(
89
+ "created_at",
90
+ sa.DateTime(timezone=True),
91
+ server_default=sa.text("clock_timestamp()"),
92
+ nullable=True,
93
+ ),
94
+ sa.Column(
95
+ "updated_at",
96
+ sa.DateTime(timezone=True),
97
+ server_default=sa.text("clock_timestamp()"),
98
+ nullable=True,
99
+ ),
100
+ sa.Column("celery_id", sa.String(length=255), nullable=False),
101
+ sa.Column("monitor_task_id", sa.String(), nullable=False),
102
+ sa.Column(
103
+ "run_type", sa.Enum("MANUAL", "SYSTEM", name="taskruntype"), nullable=False
104
+ ),
105
+ sa.ForeignKeyConstraint(
106
+ ["monitor_task_id"], ["monitortask.id"], ondelete="CASCADE"
107
+ ),
108
+ sa.PrimaryKeyConstraint("id"),
109
+ )
110
+ op.create_index(
111
+ op.f("ix_monitortaskexecutionlog_id"),
112
+ "monitortaskexecutionlog",
113
+ ["id"],
114
+ unique=False,
115
+ )
116
+ op.create_index(
117
+ op.f("ix_monitortaskexecutionlog_monitor_task_id"),
118
+ "monitortaskexecutionlog",
119
+ ["monitor_task_id"],
120
+ unique=False,
121
+ )
122
+ op.create_index(
123
+ op.f("ix_monitortaskexecutionlog_status"),
124
+ "monitortaskexecutionlog",
125
+ ["status"],
126
+ unique=False,
127
+ )
128
+
129
+
130
+ def downgrade():
131
+ op.drop_index(
132
+ op.f("ix_monitortaskexecutionlog_status"), table_name="monitortaskexecutionlog"
133
+ )
134
+ op.drop_index(
135
+ op.f("ix_monitortaskexecutionlog_monitor_task_id"),
136
+ table_name="monitortaskexecutionlog",
137
+ )
138
+ op.drop_index(
139
+ op.f("ix_monitortaskexecutionlog_id"), table_name="monitortaskexecutionlog"
140
+ )
141
+ op.drop_table("monitortaskexecutionlog")
142
+ op.drop_index(op.f("ix_monitortask_status"), table_name="monitortask")
143
+ op.drop_index(op.f("ix_monitortask_monitor_config_id"), table_name="monitortask")
144
+ op.drop_index(op.f("ix_monitortask_id"), table_name="monitortask")
145
+ op.drop_index(op.f("ix_monitortask_action_type"), table_name="monitortask")
146
+ op.drop_table("monitortask")
147
+ op.execute("DROP TYPE IF EXISTS taskruntype")
@@ -0,0 +1,160 @@
1
+ """add manual task tables
2
+
3
+ Revision ID: 5efcdf18438e
4
+ Revises: c586a56c25e7
5
+ Create Date: 2025-06-04 17:24:00.300170
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from sqlalchemy.dialects import postgresql
12
+ from sqlalchemy.sql import func
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = "5efcdf18438e"
16
+ down_revision = "c586a56c25e7"
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ def upgrade():
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.create_table(
24
+ "manual_task",
25
+ sa.Column("id", sa.String(), nullable=False),
26
+ sa.Column(
27
+ "created_at",
28
+ sa.DateTime(timezone=True),
29
+ nullable=False,
30
+ server_default=func.now(),
31
+ ),
32
+ sa.Column(
33
+ "updated_at",
34
+ sa.DateTime(timezone=True),
35
+ nullable=False,
36
+ server_default=func.now(),
37
+ ),
38
+ sa.Column(
39
+ "task_type", sa.String(), nullable=False, server_default="privacy_request"
40
+ ),
41
+ sa.Column("parent_entity_id", sa.String(), nullable=False),
42
+ sa.Column("parent_entity_type", sa.String(), nullable=False),
43
+ sa.Column("due_date", sa.DateTime(timezone=True), nullable=True),
44
+ sa.PrimaryKeyConstraint("id"),
45
+ sa.UniqueConstraint(
46
+ "parent_entity_id",
47
+ "parent_entity_type",
48
+ name="uq_manual_task_parent_entity",
49
+ ),
50
+ )
51
+
52
+ op.create_table(
53
+ "manual_task_reference",
54
+ sa.Column("id", sa.String(), nullable=False),
55
+ sa.Column(
56
+ "created_at",
57
+ sa.DateTime(timezone=True),
58
+ nullable=False,
59
+ server_default=func.now(),
60
+ ),
61
+ sa.Column(
62
+ "updated_at",
63
+ sa.DateTime(timezone=True),
64
+ nullable=False,
65
+ server_default=func.now(),
66
+ ),
67
+ sa.Column("task_id", sa.String(), nullable=False),
68
+ sa.Column("reference_id", sa.String(), nullable=False),
69
+ sa.Column("reference_type", sa.String(), nullable=False),
70
+ sa.ForeignKeyConstraint(
71
+ ["task_id"],
72
+ ["manual_task.id"],
73
+ ondelete="CASCADE",
74
+ ),
75
+ sa.PrimaryKeyConstraint("id"),
76
+ )
77
+
78
+ op.create_table(
79
+ "manual_task_log",
80
+ sa.Column("id", sa.String(), nullable=False),
81
+ sa.Column(
82
+ "created_at",
83
+ sa.DateTime(timezone=True),
84
+ nullable=False,
85
+ server_default=func.now(),
86
+ ),
87
+ sa.Column(
88
+ "updated_at",
89
+ sa.DateTime(timezone=True),
90
+ nullable=False,
91
+ server_default=func.now(),
92
+ ),
93
+ sa.Column("task_id", sa.String(), nullable=False),
94
+ sa.Column("config_id", sa.String(), nullable=True),
95
+ sa.Column("instance_id", sa.String(), nullable=True),
96
+ sa.Column("status", sa.String(), nullable=False),
97
+ sa.Column("message", sa.String(), nullable=True),
98
+ sa.Column("details", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
99
+ sa.ForeignKeyConstraint(
100
+ ["task_id"],
101
+ ["manual_task.id"],
102
+ ondelete="CASCADE",
103
+ ),
104
+ sa.PrimaryKeyConstraint("id"),
105
+ )
106
+
107
+ # Create indexes for manual_task
108
+ op.create_index("ix_manual_task_task_type", "manual_task", ["task_type"])
109
+ op.create_index(
110
+ "ix_manual_task_parent_entity",
111
+ "manual_task",
112
+ ["parent_entity_type", "parent_entity_id"],
113
+ )
114
+ op.create_index("ix_manual_task_due_date", "manual_task", ["due_date"])
115
+
116
+ # Create indexes for manual_task_reference
117
+ op.create_index(
118
+ "ix_manual_task_reference_task_id", "manual_task_reference", ["task_id"]
119
+ )
120
+ op.create_index(
121
+ "ix_manual_task_reference_reference",
122
+ "manual_task_reference",
123
+ ["reference_id", "reference_type"],
124
+ )
125
+
126
+ # Create indexes for manual_task_log
127
+ op.create_index("ix_manual_task_log_task_id", "manual_task_log", ["task_id"])
128
+ op.create_index("ix_manual_task_log_config_id", "manual_task_log", ["config_id"])
129
+ op.create_index(
130
+ "ix_manual_task_log_instance_id", "manual_task_log", ["instance_id"]
131
+ )
132
+ op.create_index("ix_manual_task_log_status", "manual_task_log", ["status"])
133
+ op.create_index("ix_manual_task_log_created_at", "manual_task_log", ["created_at"])
134
+
135
+ # ### end Alembic commands ###
136
+
137
+
138
+ def downgrade():
139
+ # ### commands auto generated by Alembic - please adjust! ###
140
+ # Drop indexes first
141
+ op.drop_index("ix_manual_task_log_created_at", table_name="manual_task_log")
142
+ op.drop_index("ix_manual_task_log_status", table_name="manual_task_log")
143
+ op.drop_index("ix_manual_task_log_instance_id", table_name="manual_task_log")
144
+ op.drop_index("ix_manual_task_log_config_id", table_name="manual_task_log")
145
+ op.drop_index("ix_manual_task_log_task_id", table_name="manual_task_log")
146
+ op.drop_index(
147
+ "ix_manual_task_reference_reference", table_name="manual_task_reference"
148
+ )
149
+ op.drop_index(
150
+ "ix_manual_task_reference_task_id", table_name="manual_task_reference"
151
+ )
152
+ op.drop_index("ix_manual_task_due_date", table_name="manual_task")
153
+ op.drop_index("ix_manual_task_parent_entity", table_name="manual_task")
154
+ op.drop_index("ix_manual_task_task_type", table_name="manual_task")
155
+
156
+ # Then drop tables
157
+ op.drop_table("manual_task_log")
158
+ op.drop_table("manual_task_reference")
159
+ op.drop_table("manual_task")
160
+ # ### end Alembic commands ###
@@ -74,6 +74,11 @@ def upgrade():
74
74
  children = result.children
75
75
  if children:
76
76
  resource_children[urn] = children
77
+ else:
78
+ # even if no children, we still add the resource to the map
79
+ # so that we maintain a record of all resources and can
80
+ # check for orphaned descendant records later
81
+ resource_children[urn] = []
77
82
 
78
83
  # Build list of ancestor-descendant pairs
79
84
  ancestor_links = []
@@ -86,17 +91,21 @@ def upgrade():
86
91
  for child_urn in children:
87
92
  if child_urn not in visited:
88
93
  visited.add(child_urn)
89
- # Add direct ancestor link
90
- ancestor_links.append(
91
- {
92
- "id": f"srl_{uuid.uuid4()}",
93
- "ancestor_urn": ancestor_urn,
94
- "descendant_urn": child_urn,
95
- }
96
- )
97
-
98
- # Recursively process this child's children
99
- if child_urn in resource_children:
94
+ if child_urn not in resource_children:
95
+ logger.warning(
96
+ f"Found orphaned descendant URN: {child_urn}, not adding ancestor link"
97
+ )
98
+ else:
99
+ # Add direct ancestor link
100
+ ancestor_links.append(
101
+ {
102
+ "id": f"srl_{uuid.uuid4()}",
103
+ "ancestor_urn": ancestor_urn,
104
+ "descendant_urn": child_urn,
105
+ }
106
+ )
107
+
108
+ # Recursively process this child's children
100
109
  process_children(
101
110
  ancestor_urn, resource_children[child_urn], visited
102
111
  )
@@ -75,6 +75,7 @@ from fides.api.models.privacy_request import (
75
75
  ProvidedIdentity,
76
76
  RequestTask,
77
77
  )
78
+ from fides.api.models.worker_task import ExecutionLogStatus
78
79
  from fides.api.oauth.utils import (
79
80
  verify_callback_oauth_policy_pre_webhook,
80
81
  verify_callback_oauth_pre_approval_webhook,
@@ -91,7 +92,6 @@ from fides.api.schemas.privacy_request import (
91
92
  CheckpointActionRequired,
92
93
  DenyPrivacyRequests,
93
94
  ExecutionLogDetailResponse,
94
- ExecutionLogStatus,
95
95
  FilteredPrivacyRequestResults,
96
96
  LogEntry,
97
97
  ManualWebhookData,
@@ -1940,16 +1940,16 @@ def request_task_async_callback(
1940
1940
  ]:
1941
1941
  raise HTTPException(
1942
1942
  status_code=HTTP_400_BAD_REQUEST,
1943
- detail=f"Callback failed. Cannot queue {request_task.action_type.value} task '{request_task.id}' with privacy request status '{privacy_request.status.value}'",
1943
+ detail=f"Callback failed. Cannot queue {request_task.action_type} task '{request_task.id}' with privacy request status '{privacy_request.status.value}'",
1944
1944
  )
1945
1945
  if request_task.status != ExecutionLogStatus.awaiting_processing:
1946
1946
  raise HTTPException(
1947
1947
  status_code=HTTP_400_BAD_REQUEST,
1948
- detail=f"Callback failed. Cannot queue {request_task.action_type.value} task '{request_task.id}' with request task status '{request_task.status.value}'",
1948
+ detail=f"Callback failed. Cannot queue {request_task.action_type} task '{request_task.id}' with request task status '{request_task.status.value}'",
1949
1949
  )
1950
1950
  logger.info(
1951
1951
  "Callback received for {} task {} {}",
1952
- request_task.action_type.value,
1952
+ request_task.action_type,
1953
1953
  request_task.collection_address,
1954
1954
  request_task.id,
1955
1955
  )
fides/api/db/base.py CHANGED
@@ -16,7 +16,11 @@ from fides.api.models.custom_connector_template import CustomConnectorTemplate
16
16
  from fides.api.models.custom_report import CustomReport
17
17
  from fides.api.models.datasetconfig import DatasetConfig
18
18
  from fides.api.models.db_cache import DBCache
19
- from fides.api.models.detection_discovery import MonitorConfig, StagedResource
19
+ from fides.api.models.detection_discovery.core import MonitorConfig, StagedResource
20
+ from fides.api.models.detection_discovery.monitor_task import (
21
+ MonitorTask,
22
+ MonitorTaskExecutionLog,
23
+ )
20
24
  from fides.api.models.experience_notices import ExperienceNotices
21
25
  from fides.api.models.fides_cloud import FidesCloud
22
26
  from fides.api.models.fides_user import FidesUser
@@ -27,6 +31,8 @@ from fides.api.models.fides_user_respondent_email_verification import (
27
31
  )
28
32
  from fides.api.models.identity_salt import IdentitySalt
29
33
  from fides.api.models.location_regulation_selections import LocationRegulationSelections
34
+ from fides.api.models.manual_tasks.manual_task import ManualTask, ManualTaskReference
35
+ from fides.api.models.manual_tasks.manual_task_log import ManualTaskLog
30
36
  from fides.api.models.manual_webhook import AccessManualWebhook
31
37
  from fides.api.models.messaging import MessagingConfig
32
38
  from fides.api.models.messaging_template import MessagingTemplate
@@ -182,7 +182,7 @@ def create_object(db: Session, object_statement: str, object_name: str) -> None:
182
182
  def check_and_create_objects(
183
183
  db: Session, table_object_map: Dict[str, List[Dict[str, str]]], lock: Lock
184
184
  ) -> Dict[str, str]:
185
- """Returns a dictionary of any indices or constraints that are in the process of being created."""
185
+ """Returns a dictionary of any indices or constraints that were created."""
186
186
  object_info: Dict[str, str] = {}
187
187
  for _, objects in table_object_map.items():
188
188
  for object_data in objects:
@@ -203,7 +203,7 @@ def check_and_create_objects(
203
203
  continue
204
204
 
205
205
  create_object(db, object_statement, object_name)
206
- object_info[object_name] = "in progress"
206
+ object_info[object_name] = "created"
207
207
  else:
208
208
  logger.debug(
209
209
  f"Object {object_name} already exists, skipping index/constraint creation"
@@ -248,7 +248,7 @@ def post_upgrade_index_creation_task() -> None:
248
248
  f"Post upgrade index creation output: {json.dumps(object_info)}"
249
249
  )
250
250
  else:
251
- logger.debug("All indices and constraints created")
251
+ logger.debug("All indices and constraints already created")
252
252
 
253
253
 
254
254
  def initiate_post_upgrade_index_creation() -> None:
@@ -23,7 +23,7 @@ from fides.api.schemas.saas.saas_config import SaaSConfig
23
23
  from fides.config import CONFIG
24
24
 
25
25
  if TYPE_CHECKING:
26
- from fides.api.models.detection_discovery import MonitorConfig
26
+ from fides.api.models.detection_discovery.core import MonitorConfig
27
27
  from fides.api.schemas.connection_configuration.enums.system_type import SystemType
28
28
 
29
29
 
@@ -0,0 +1,35 @@
1
+ from .core import (
2
+ DiffStatus,
3
+ MonitorConfig,
4
+ MonitorExecution,
5
+ MonitorFrequency,
6
+ SharedMonitorConfig,
7
+ StagedResource,
8
+ StagedResourceAncestor,
9
+ fetch_staged_resources_by_type_query,
10
+ )
11
+ from .monitor_task import (
12
+ MonitorTask,
13
+ MonitorTaskExecutionLog,
14
+ MonitorTaskType,
15
+ TaskRunType,
16
+ create_monitor_task_with_execution_log,
17
+ update_monitor_task_with_execution_log,
18
+ )
19
+
20
+ __all__ = [
21
+ "DiffStatus",
22
+ "MonitorConfig",
23
+ "MonitorExecution",
24
+ "MonitorFrequency",
25
+ "SharedMonitorConfig",
26
+ "StagedResource",
27
+ "StagedResourceAncestor",
28
+ "fetch_staged_resources_by_type_query",
29
+ "MonitorTask",
30
+ "MonitorTaskExecutionLog",
31
+ "MonitorTaskType",
32
+ "TaskRunType",
33
+ "create_monitor_task_with_execution_log",
34
+ "update_monitor_task_with_execution_log",
35
+ ]
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import List, Optional
5
+
6
+ from sqlalchemy import ARRAY, Column
7
+ from sqlalchemy import Enum as SQLAlchemyEnum
8
+ from sqlalchemy import ForeignKey, String
9
+ from sqlalchemy.dialects.postgresql import JSONB
10
+ from sqlalchemy.orm import Session, relationship
11
+
12
+ from fides.api.db.base_class import Base, FidesBase # type: ignore[attr-defined]
13
+ from fides.api.models.detection_discovery.core import MonitorConfig
14
+ from fides.api.models.worker_task import (
15
+ ExecutionLogStatus,
16
+ TaskExecutionLog,
17
+ WorkerTask,
18
+ )
19
+
20
+
21
+ class MonitorTaskType(Enum):
22
+ """
23
+ Types of tasks that can be executed by a worker.
24
+ """
25
+
26
+ DETECTION = "detection"
27
+ CLASSIFICATION = "classification"
28
+ PROMOTION = "promotion"
29
+ REMOVAL_PROMOTION = "removal_promotion"
30
+
31
+
32
+ class MonitorTask(WorkerTask, Base):
33
+ """
34
+ A monitor task executed by a worker.
35
+ """
36
+
37
+ # celery_id is used to track task executions. While MonitorTask.id remains constant,
38
+ # celery_id changes with each execution or retry of the task, allowing us to track
39
+ # the current execution state while maintaining a stable reference to the original task.
40
+ celery_id = Column(
41
+ String(255), unique=True, nullable=False, default=FidesBase.generate_uuid
42
+ )
43
+ task_arguments = Column(JSONB, nullable=True) # To be able to rerun the task
44
+ # Contains info, warning, or error messages
45
+ message = Column(String)
46
+ monitor_config_id = Column(
47
+ String,
48
+ ForeignKey(MonitorConfig.id_field_path, ondelete="CASCADE"),
49
+ index=True,
50
+ nullable=False,
51
+ )
52
+ staged_resource_urns = Column(ARRAY(String), nullable=True)
53
+ child_resource_urns = Column(ARRAY(String), nullable=True)
54
+
55
+ monitor_config = relationship(MonitorConfig, cascade="all, delete")
56
+ execution_logs = relationship(
57
+ "MonitorTaskExecutionLog", back_populates="monitor_task", cascade="all, delete"
58
+ )
59
+
60
+ @classmethod
61
+ def allowed_action_types(cls) -> List[str]:
62
+ return [e.value for e in MonitorTaskType]
63
+
64
+
65
+ class TaskRunType(Enum):
66
+ """
67
+ Type of task run.
68
+ """
69
+
70
+ MANUAL = "manual"
71
+ SYSTEM = "system"
72
+
73
+
74
+ class MonitorTaskExecutionLog(TaskExecutionLog, Base):
75
+ """
76
+ Stores the individual execution logs associated with a MonitorTask.
77
+ """
78
+
79
+ # This celery_id preserves the specific execution ID for historical tracking,
80
+ # unlike MonitorTask.celery_id which is updated with each execution.
81
+ # This allows us to maintain a complete history of all task execution attempts.
82
+ celery_id = Column(String(255), nullable=False)
83
+ monitor_task_id = Column(
84
+ String,
85
+ ForeignKey(MonitorTask.id_field_path, ondelete="CASCADE"),
86
+ index=True,
87
+ nullable=False,
88
+ )
89
+ run_type = Column(
90
+ SQLAlchemyEnum(TaskRunType), nullable=False, default=TaskRunType.SYSTEM
91
+ )
92
+
93
+ monitor_task = relationship("MonitorTask", back_populates="execution_logs")
94
+
95
+
96
+ def create_monitor_task_with_execution_log(
97
+ db: Session, monitor_task_data: dict
98
+ ) -> MonitorTask:
99
+ """
100
+ Creates a monitor task with an execution log.
101
+ The default status is pending for the task and pending for the execution log.
102
+ """
103
+ status = ExecutionLogStatus.pending
104
+ task_record = MonitorTask( # type: ignore
105
+ status=status.value,
106
+ **monitor_task_data,
107
+ )
108
+ db.add(task_record)
109
+ db.flush()
110
+
111
+ execution_log = MonitorTaskExecutionLog( # type: ignore
112
+ monitor_task=task_record, celery_id=task_record.celery_id, status=status
113
+ )
114
+ db.add(execution_log)
115
+
116
+ db.commit()
117
+ db.refresh(task_record)
118
+ return task_record
119
+
120
+
121
+ def update_monitor_task_with_execution_log(
122
+ db: Session,
123
+ status: ExecutionLogStatus,
124
+ task_record: Optional[MonitorTask] = None,
125
+ celery_id: Optional[str] = None,
126
+ message: Optional[str] = None,
127
+ run_type: TaskRunType = TaskRunType.SYSTEM,
128
+ ) -> MonitorTask:
129
+ """
130
+ Updates a monitor task with an execution log.
131
+
132
+ It must be either celery_id or task_record. If it doesn't receive a celery_id, it's assumed a new one needs to be created because a new run is about to be performed.
133
+ If it receives a celery_id, it means it only needs to update the status of an existing run. It can receive task_record to avoid querying the database again to get it.
134
+ """
135
+ if not celery_id and not task_record:
136
+ raise ValueError("Either celery_id or task_record must be provided")
137
+
138
+ if celery_id and not task_record:
139
+ task_record = MonitorTask.get_by(db=db, field="celery_id", value=celery_id)
140
+ if not task_record:
141
+ raise ValueError(f"Could not find MonitorTask with celery_id {celery_id}")
142
+
143
+ assert task_record is not None # help type checker understand the control flow
144
+
145
+ if not celery_id:
146
+ celery_id = task_record.generate_uuid()
147
+ task_record.celery_id = celery_id
148
+
149
+ task_record.status = status.value # type: ignore
150
+ task_record.message = message
151
+
152
+ MonitorTaskExecutionLog( # type: ignore
153
+ monitor_task=task_record,
154
+ status=status,
155
+ message=message,
156
+ celery_id=celery_id,
157
+ run_type=run_type,
158
+ )
159
+
160
+ db.commit()
161
+ db.refresh(task_record)
162
+ return task_record
@@ -0,0 +1,5 @@
1
+ from .encrypted_large_data import EncryptedLargeDataDescriptor
2
+
3
+ __all__ = [
4
+ "EncryptedLargeDataDescriptor",
5
+ ]