ethyca-fides 2.67.1b1__py2.py3-none-any.whl → 2.67.1rc1__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.67.1b1.dist-info → ethyca_fides-2.67.1rc1.dist-info}/METADATA +1 -1
- {ethyca_fides-2.67.1b1.dist-info → ethyca_fides-2.67.1rc1.dist-info}/RECORD +108 -108
- fides/_version.py +3 -3
- fides/api/models/manual_task/manual_task.py +6 -3
- fides/api/models/privacy_request/privacy_request.py +0 -78
- fides/api/service/connectors/sql_connector.py +19 -3
- fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +19 -3
- fides/api/task/create_request_tasks.py +3 -5
- fides/api/task/filter_results.py +1 -1
- fides/api/task/manual/manual_task_address.py +2 -4
- fides/api/task/manual/manual_task_graph_task.py +83 -87
- fides/api/task/manual/manual_task_utils.py +140 -84
- fides/api/util/storage_util.py +0 -19
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/{sFDtXdzSc8wR5M8bdSVpg → -pDvglWsgqfWeQixOy5zJ}/_buildManifest.js +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6662-a9e54ead3dc53644.js → 6662-ef0edd3598b1d431.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-3e5725cd06d7fe6c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-e0a755c69081fffa.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 +1 -1
- 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/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-a286affa43687eb5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-8e346fb36e8034d2.js +0 -1
- {ethyca_fides-2.67.1b1.dist-info → ethyca_fides-2.67.1rc1.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.67.1b1.dist-info → ethyca_fides-2.67.1rc1.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.67.1b1.dist-info → ethyca_fides-2.67.1rc1.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.67.1b1.dist-info → ethyca_fides-2.67.1rc1.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{sFDtXdzSc8wR5M8bdSVpg → -pDvglWsgqfWeQixOy5zJ}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
1
|
from fides.api.graph.config import CollectionAddress
|
|
4
2
|
|
|
5
3
|
|
|
@@ -22,7 +20,7 @@ class ManualTaskAddress:
|
|
|
22
20
|
return collection_name == ManualTaskAddress.MANUAL_DATA_COLLECTION
|
|
23
21
|
|
|
24
22
|
@staticmethod
|
|
25
|
-
def is_manual_task_address(address:
|
|
23
|
+
def is_manual_task_address(address: CollectionAddress) -> bool:
|
|
26
24
|
"""Check if address represents manual task data"""
|
|
27
25
|
if isinstance(address, str):
|
|
28
26
|
# Handle string format "connection_key:collection_name"
|
|
@@ -35,7 +33,7 @@ class ManualTaskAddress:
|
|
|
35
33
|
return ManualTaskAddress._is_manual_data_collection(address.collection)
|
|
36
34
|
|
|
37
35
|
@staticmethod
|
|
38
|
-
def get_connection_key(address:
|
|
36
|
+
def get_connection_key(address: CollectionAddress) -> str:
|
|
39
37
|
"""Extract connection config key from manual task address"""
|
|
40
38
|
if not ManualTaskAddress.is_manual_task_address(address):
|
|
41
39
|
raise ValueError(f"Not a manual task address: {address}")
|
|
@@ -7,11 +7,11 @@ from fides.api.common_exceptions import AwaitingAsyncTaskCallback
|
|
|
7
7
|
from fides.api.models.attachment import AttachmentType
|
|
8
8
|
from fides.api.models.manual_task import (
|
|
9
9
|
ManualTask,
|
|
10
|
+
ManualTaskConfig,
|
|
10
11
|
ManualTaskConfigurationType,
|
|
11
12
|
ManualTaskEntityType,
|
|
12
13
|
ManualTaskFieldType,
|
|
13
14
|
ManualTaskInstance,
|
|
14
|
-
ManualTaskSubmission,
|
|
15
15
|
StatusType,
|
|
16
16
|
)
|
|
17
17
|
from fides.api.models.privacy_request import PrivacyRequest
|
|
@@ -23,7 +23,6 @@ from fides.api.task.manual.manual_task_utils import (
|
|
|
23
23
|
get_manual_task_for_connection_config,
|
|
24
24
|
)
|
|
25
25
|
from fides.api.util.collection_util import Row
|
|
26
|
-
from fides.api.util.storage_util import format_size
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class ManualTaskGraphTask(GraphTask):
|
|
@@ -123,36 +122,29 @@ class ManualTaskGraphTask(GraphTask):
|
|
|
123
122
|
# request has started, while allowing different config types (access vs erasure)
|
|
124
123
|
# to have separate instances.
|
|
125
124
|
# ------------------------------------------------------------------
|
|
126
|
-
existing_task_instance =
|
|
127
|
-
(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
existing_task_instance = (
|
|
126
|
+
db.query(ManualTaskInstance)
|
|
127
|
+
.join(ManualTaskInstance.config) # Join to access config information
|
|
128
|
+
.filter(
|
|
129
|
+
ManualTaskInstance.task_id == manual_task.id,
|
|
130
|
+
ManualTaskInstance.entity_id == privacy_request.id,
|
|
131
|
+
ManualTaskInstance.entity_type == ManualTaskEntityType.privacy_request,
|
|
132
|
+
# Only check for instances of the same config type
|
|
133
|
+
ManualTaskConfig.config_type == allowed_config_type,
|
|
134
|
+
)
|
|
135
|
+
.first()
|
|
134
136
|
)
|
|
135
137
|
if existing_task_instance:
|
|
136
138
|
# An instance already exists for this privacy request and config type – no need
|
|
137
139
|
# to create another one tied to a newer config version.
|
|
138
140
|
return
|
|
139
141
|
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
for config in sorted(
|
|
146
|
-
manual_task.configs,
|
|
147
|
-
key=lambda c: c.version if hasattr(c, "version") else 0,
|
|
148
|
-
reverse=True,
|
|
149
|
-
)
|
|
150
|
-
if config.is_current and config.config_type == allowed_config_type
|
|
151
|
-
),
|
|
152
|
-
None,
|
|
153
|
-
)
|
|
142
|
+
# Check each active config for instances (now we know none exist yet for this config type)
|
|
143
|
+
for config in manual_task.configs:
|
|
144
|
+
if not config.is_current or config.config_type != allowed_config_type:
|
|
145
|
+
# Skip configs that are not current or not relevant for this request type
|
|
146
|
+
continue
|
|
154
147
|
|
|
155
|
-
if config:
|
|
156
148
|
ManualTaskInstance.create(
|
|
157
149
|
db=db,
|
|
158
150
|
data={
|
|
@@ -164,6 +156,7 @@ class ManualTaskGraphTask(GraphTask):
|
|
|
164
156
|
},
|
|
165
157
|
)
|
|
166
158
|
|
|
159
|
+
# pylint: disable=too-many-branches,too-many-nested-blocks
|
|
167
160
|
def _get_submitted_data(
|
|
168
161
|
self,
|
|
169
162
|
db: Session,
|
|
@@ -175,90 +168,93 @@ class ManualTaskGraphTask(GraphTask):
|
|
|
175
168
|
Check if all manual task instances have submissions for ALL fields and return aggregated data
|
|
176
169
|
Returns None if any field submissions are missing (all fields must be completed or skipped)
|
|
177
170
|
"""
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
171
|
+
aggregated_data: dict[str, Any] = {}
|
|
172
|
+
|
|
173
|
+
def _format_size(size_bytes: int) -> str:
|
|
174
|
+
units = ["B", "KB", "MB", "GB", "TB"]
|
|
175
|
+
size = float(size_bytes)
|
|
176
|
+
for unit in units:
|
|
177
|
+
if size < 1024.0:
|
|
178
|
+
return f"{size:.1f} {unit}"
|
|
179
|
+
size /= 1024.0
|
|
180
|
+
return f"{size:.1f} PB"
|
|
181
|
+
|
|
182
|
+
candidate_instances: list[ManualTaskInstance] = (
|
|
183
|
+
db.query(ManualTaskInstance)
|
|
184
|
+
.filter(
|
|
185
|
+
ManualTaskInstance.task_id == manual_task.id,
|
|
186
|
+
ManualTaskInstance.entity_id == privacy_request.id,
|
|
187
|
+
ManualTaskInstance.entity_type == ManualTaskEntityType.privacy_request,
|
|
188
|
+
)
|
|
189
|
+
.all()
|
|
190
|
+
)
|
|
184
191
|
|
|
185
192
|
if not candidate_instances:
|
|
186
193
|
return None # No instance yet for this manual task
|
|
187
194
|
|
|
188
|
-
# Check for incomplete fields and update status in single pass
|
|
189
195
|
for inst in candidate_instances:
|
|
190
|
-
|
|
196
|
+
# Skip instances tied to other request types
|
|
197
|
+
if not inst.config or inst.config.config_type != allowed_config_type:
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
all_fields = inst.config.field_definitions or []
|
|
201
|
+
|
|
202
|
+
# Every field must have a submission
|
|
203
|
+
if not all(inst.get_submission_for_field(f.id) for f in all_fields):
|
|
191
204
|
return None # At least one instance still incomplete
|
|
192
205
|
|
|
193
|
-
#
|
|
206
|
+
# Ensure status set
|
|
194
207
|
if inst.status != StatusType.completed:
|
|
195
208
|
inst.status = StatusType.completed
|
|
196
209
|
inst.save(db)
|
|
197
210
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def _aggregate_submission_data(
|
|
203
|
-
self, instances: list[ManualTaskInstance]
|
|
204
|
-
) -> dict[str, Any]:
|
|
205
|
-
"""Aggregate submission data from all instances into a single dictionary."""
|
|
206
|
-
aggregated_data: dict[str, Any] = {}
|
|
207
|
-
|
|
208
|
-
for inst in instances:
|
|
209
|
-
# Filter valid submissions and process them
|
|
210
|
-
valid_submissions = (
|
|
211
|
-
submission
|
|
212
|
-
for submission in inst.submissions
|
|
213
|
-
if (
|
|
214
|
-
submission.field
|
|
215
|
-
and submission.field.field_key
|
|
216
|
-
and isinstance(submission.data, dict)
|
|
217
|
-
)
|
|
218
|
-
)
|
|
211
|
+
# Aggregate submission data from this instance
|
|
212
|
+
for submission in inst.submissions:
|
|
213
|
+
if not submission.field or not submission.field.field_key:
|
|
214
|
+
continue
|
|
219
215
|
|
|
220
|
-
for submission in valid_submissions:
|
|
221
216
|
field_key = submission.field.field_key
|
|
222
|
-
# We already checked isinstance(submission.data, dict) in valid_submissions
|
|
223
|
-
data_dict: dict[str, Any] = submission.data # type: ignore[assignment]
|
|
224
|
-
field_type = data_dict.get("field_type")
|
|
225
217
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
self._process_attachment_field(submission)
|
|
229
|
-
if field_type == ManualTaskFieldType.attachment.value
|
|
230
|
-
else data_dict.get("value")
|
|
231
|
-
)
|
|
218
|
+
if not isinstance(submission.data, dict):
|
|
219
|
+
continue
|
|
232
220
|
|
|
233
|
-
|
|
221
|
+
data_dict: dict[str, Any] = submission.data
|
|
234
222
|
|
|
235
|
-
|
|
236
|
-
self, submission: ManualTaskSubmission
|
|
237
|
-
) -> Optional[dict[str, dict[str, Any]]]:
|
|
238
|
-
"""Process attachment field and return attachment map or None."""
|
|
239
|
-
attachment_map: dict[str, dict[str, Any]] = {}
|
|
223
|
+
field_type = data_dict.get("field_type")
|
|
240
224
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
225
|
+
if field_type == ManualTaskFieldType.attachment.value:
|
|
226
|
+
attachment_map: dict[str, dict[str, Any]] = {}
|
|
227
|
+
for attachment in submission.attachments or []:
|
|
228
|
+
if (
|
|
229
|
+
attachment.attachment_type
|
|
230
|
+
== AttachmentType.include_with_access_package
|
|
231
|
+
):
|
|
232
|
+
try:
|
|
233
|
+
size, url = attachment.retrieve_attachment()
|
|
234
|
+
attachment_map[attachment.file_name] = {
|
|
235
|
+
"url": str(url) if url else None,
|
|
236
|
+
"size": (_format_size(size) if size else "Unknown"),
|
|
237
|
+
}
|
|
238
|
+
except (
|
|
239
|
+
Exception
|
|
240
|
+
) as exc: # pylint: disable=broad-exception-caught
|
|
241
|
+
logger.warning(
|
|
242
|
+
"Error retrieving attachment {}: {}",
|
|
243
|
+
attachment.file_name,
|
|
244
|
+
str(exc),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
aggregated_data[field_key] = attachment_map or None
|
|
248
|
+
else:
|
|
249
|
+
aggregated_data[field_key] = data_dict.get("value")
|
|
250
|
+
|
|
251
|
+
return aggregated_data if aggregated_data else None
|
|
256
252
|
|
|
257
253
|
def dry_run_task(self) -> int:
|
|
258
254
|
"""Return estimated row count for dry run - manual tasks don't have predictable counts"""
|
|
259
255
|
return 1 # Placeholder - manual tasks generate variable data
|
|
260
256
|
|
|
261
|
-
# Provide erasure support for manual tasks
|
|
257
|
+
# NEW METHOD: Provide erasure support for manual tasks
|
|
262
258
|
@retry(action_type=ActionType.erasure, default_return=0)
|
|
263
259
|
def erasure_request(
|
|
264
260
|
self,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from loguru import logger
|
|
2
1
|
from sqlalchemy.orm import Session
|
|
3
2
|
|
|
4
3
|
from fides.api.graph.config import (
|
|
@@ -13,7 +12,15 @@ from fides.api.graph.traversal import TraversalNode
|
|
|
13
12
|
from fides.api.models.connectionconfig import ConnectionConfig
|
|
14
13
|
|
|
15
14
|
# Import application models
|
|
16
|
-
from fides.api.models.manual_task import
|
|
15
|
+
from fides.api.models.manual_task import (
|
|
16
|
+
ManualTask,
|
|
17
|
+
ManualTaskConfig,
|
|
18
|
+
ManualTaskConfigurationType,
|
|
19
|
+
ManualTaskEntityType,
|
|
20
|
+
ManualTaskInstance,
|
|
21
|
+
)
|
|
22
|
+
from fides.api.models.privacy_request import PrivacyRequest
|
|
23
|
+
from fides.api.schemas.policy import ActionType
|
|
17
24
|
from fides.api.task.manual.manual_task_address import ManualTaskAddress
|
|
18
25
|
|
|
19
26
|
|
|
@@ -21,18 +28,13 @@ def get_connection_configs_with_manual_tasks(db: Session) -> list[ConnectionConf
|
|
|
21
28
|
"""
|
|
22
29
|
Get all connection configs that have manual tasks.
|
|
23
30
|
"""
|
|
24
|
-
|
|
25
|
-
connection_configs = (
|
|
31
|
+
return (
|
|
26
32
|
db.query(ConnectionConfig)
|
|
27
33
|
.join(ManualTask, ConnectionConfig.id == ManualTask.parent_entity_id)
|
|
28
34
|
.filter(ManualTask.parent_entity_type == "connection_config")
|
|
29
35
|
.filter(ConnectionConfig.disabled.is_(False))
|
|
30
36
|
.all()
|
|
31
37
|
)
|
|
32
|
-
logger.info(
|
|
33
|
-
f"Found {len(connection_configs)} connection configs with manual tasks: {[cc.key for cc in connection_configs]}"
|
|
34
|
-
)
|
|
35
|
-
return connection_configs
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
def get_manual_task_addresses(db: Session) -> list[CollectionAddress]:
|
|
@@ -45,19 +47,12 @@ def get_manual_task_addresses(db: Session) -> list[CollectionAddress]:
|
|
|
45
47
|
"""
|
|
46
48
|
# Get all connection configs that have manual tasks (excluding disabled ones)
|
|
47
49
|
connection_configs_with_manual_tasks = get_connection_configs_with_manual_tasks(db)
|
|
48
|
-
logger.debug(
|
|
49
|
-
f"Found {len(connection_configs_with_manual_tasks)} connection configs with manual tasks"
|
|
50
|
-
)
|
|
51
50
|
|
|
52
51
|
# Create addresses for all connections that have manual tasks
|
|
53
52
|
manual_task_addresses = []
|
|
54
53
|
for config in connection_configs_with_manual_tasks:
|
|
55
|
-
logger.info(f"Creating manual task address for connection config: {config.key}")
|
|
56
54
|
manual_task_addresses.append(ManualTaskAddress.create(config.key))
|
|
57
55
|
|
|
58
|
-
logger.info(
|
|
59
|
-
f"Created {len(manual_task_addresses)} manual task addresses: {manual_task_addresses}"
|
|
60
|
-
)
|
|
61
56
|
return manual_task_addresses
|
|
62
57
|
|
|
63
58
|
|
|
@@ -67,11 +62,7 @@ def get_manual_task_for_connection_config(
|
|
|
67
62
|
"""Get the ManualTask for a specific connection config,
|
|
68
63
|
the manual task/connection config relationship is 1:1.
|
|
69
64
|
"""
|
|
70
|
-
|
|
71
|
-
f"Looking for manual task for connection config: {connection_config_key}"
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
manual_task = (
|
|
65
|
+
return (
|
|
75
66
|
db.query(ManualTask)
|
|
76
67
|
.join(ConnectionConfig, ManualTask.parent_entity_id == ConnectionConfig.id)
|
|
77
68
|
.filter(
|
|
@@ -81,17 +72,6 @@ def get_manual_task_for_connection_config(
|
|
|
81
72
|
.one_or_none()
|
|
82
73
|
)
|
|
83
74
|
|
|
84
|
-
if manual_task:
|
|
85
|
-
logger.info(
|
|
86
|
-
f"Found manual task {manual_task.id} for connection {connection_config_key}"
|
|
87
|
-
)
|
|
88
|
-
else:
|
|
89
|
-
logger.warning(
|
|
90
|
-
f"No manual task found for connection config: {connection_config_key}"
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
return manual_task
|
|
94
|
-
|
|
95
75
|
|
|
96
76
|
def create_manual_data_traversal_node(
|
|
97
77
|
db: Session, address: CollectionAddress
|
|
@@ -142,6 +122,116 @@ def create_manual_data_traversal_node(
|
|
|
142
122
|
return traversal_node
|
|
143
123
|
|
|
144
124
|
|
|
125
|
+
def create_manual_task_instances_for_privacy_request(
|
|
126
|
+
db: Session, privacy_request: PrivacyRequest
|
|
127
|
+
) -> list[ManualTaskInstance]:
|
|
128
|
+
"""Create ManualTaskInstance entries for all active manual tasks relevant to a privacy request."""
|
|
129
|
+
instances = []
|
|
130
|
+
|
|
131
|
+
# Get all connection configs that have manual tasks (excluding disabled ones)
|
|
132
|
+
connection_configs_with_manual_tasks = get_connection_configs_with_manual_tasks(db)
|
|
133
|
+
|
|
134
|
+
# Determine the privacy request type based on policy rules
|
|
135
|
+
has_access_rules = bool(
|
|
136
|
+
privacy_request.policy.get_rules_for_action(action_type=ActionType.access)
|
|
137
|
+
)
|
|
138
|
+
has_erasure_rules = bool(
|
|
139
|
+
privacy_request.policy.get_rules_for_action(action_type=ActionType.erasure)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
for connection_config in connection_configs_with_manual_tasks:
|
|
143
|
+
manual_tasks = (
|
|
144
|
+
db.query(ManualTask)
|
|
145
|
+
.filter(
|
|
146
|
+
ManualTask.parent_entity_id == connection_config.id,
|
|
147
|
+
ManualTask.parent_entity_type == "connection_config",
|
|
148
|
+
)
|
|
149
|
+
.all()
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
for manual_task in manual_tasks:
|
|
153
|
+
# Get the active config for this manual task, filtered by request type
|
|
154
|
+
active_config_query = db.query(ManualTaskConfig).filter(
|
|
155
|
+
ManualTaskConfig.task_id == manual_task.id,
|
|
156
|
+
ManualTaskConfig.is_current.is_(True),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Filter by configuration type based on privacy request type
|
|
160
|
+
if has_access_rules and has_erasure_rules:
|
|
161
|
+
# If both access and erasure rules exist, include both types
|
|
162
|
+
active_config_query = active_config_query.filter(
|
|
163
|
+
ManualTaskConfig.config_type.in_(
|
|
164
|
+
[
|
|
165
|
+
ManualTaskConfigurationType.access_privacy_request,
|
|
166
|
+
ManualTaskConfigurationType.erasure_privacy_request,
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
elif has_access_rules:
|
|
171
|
+
# Only access rules - only include access configurations
|
|
172
|
+
active_config_query = active_config_query.filter(
|
|
173
|
+
ManualTaskConfig.config_type
|
|
174
|
+
== ManualTaskConfigurationType.access_privacy_request
|
|
175
|
+
)
|
|
176
|
+
elif has_erasure_rules:
|
|
177
|
+
# Only erasure rules - only include erasure configurations
|
|
178
|
+
active_config_query = active_config_query.filter(
|
|
179
|
+
ManualTaskConfig.config_type
|
|
180
|
+
== ManualTaskConfigurationType.erasure_privacy_request
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
# No relevant rules - skip this manual task
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
active_configs = active_config_query.all()
|
|
187
|
+
|
|
188
|
+
if not active_configs:
|
|
189
|
+
continue # Skip if no active configs
|
|
190
|
+
|
|
191
|
+
# Create instances for each active config
|
|
192
|
+
for active_config in active_configs:
|
|
193
|
+
# Check if instance already exists for this config
|
|
194
|
+
existing_instance = (
|
|
195
|
+
db.query(ManualTaskInstance)
|
|
196
|
+
.filter(
|
|
197
|
+
ManualTaskInstance.entity_id == privacy_request.id,
|
|
198
|
+
ManualTaskInstance.entity_type == "privacy_request",
|
|
199
|
+
ManualTaskInstance.task_id == manual_task.id,
|
|
200
|
+
ManualTaskInstance.config_id == active_config.id,
|
|
201
|
+
)
|
|
202
|
+
.first()
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if not existing_instance:
|
|
206
|
+
instance = ManualTaskInstance(
|
|
207
|
+
entity_id=privacy_request.id,
|
|
208
|
+
entity_type=ManualTaskEntityType.privacy_request,
|
|
209
|
+
task_id=manual_task.id,
|
|
210
|
+
config_id=active_config.id,
|
|
211
|
+
)
|
|
212
|
+
db.add(instance)
|
|
213
|
+
instances.append(instance)
|
|
214
|
+
|
|
215
|
+
if instances:
|
|
216
|
+
db.commit()
|
|
217
|
+
|
|
218
|
+
return instances
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get_manual_task_instances_for_privacy_request(
|
|
222
|
+
db: Session, privacy_request: PrivacyRequest
|
|
223
|
+
) -> list[ManualTaskInstance]:
|
|
224
|
+
"""Get all manual task instances for a privacy request."""
|
|
225
|
+
return (
|
|
226
|
+
db.query(ManualTaskInstance)
|
|
227
|
+
.filter(
|
|
228
|
+
ManualTaskInstance.entity_id == privacy_request.id,
|
|
229
|
+
ManualTaskInstance.entity_type == "privacy_request",
|
|
230
|
+
)
|
|
231
|
+
.all()
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
145
235
|
def create_manual_task_artificial_graphs(
|
|
146
236
|
db: Session,
|
|
147
237
|
) -> list:
|
|
@@ -164,18 +254,11 @@ def create_manual_task_artificial_graphs(
|
|
|
164
254
|
List of GraphDataset objects representing manual tasks as root nodes
|
|
165
255
|
"""
|
|
166
256
|
|
|
167
|
-
logger.debug("Creating manual task artificial graphs")
|
|
168
257
|
manual_task_graphs = []
|
|
169
258
|
manual_addresses = get_manual_task_addresses(db)
|
|
170
|
-
logger.debug(
|
|
171
|
-
f"Found {len(manual_addresses)} manual task addresses: {manual_addresses}"
|
|
172
|
-
)
|
|
173
259
|
|
|
174
260
|
for address in manual_addresses:
|
|
175
261
|
connection_key = address.dataset
|
|
176
|
-
logger.debug(
|
|
177
|
-
f"Processing manual task address: {address} for connection: {connection_key}"
|
|
178
|
-
)
|
|
179
262
|
|
|
180
263
|
# Get manual tasks for this connection to determine fields
|
|
181
264
|
manual_task = get_manual_task_for_connection_config(db, connection_key)
|
|
@@ -185,47 +268,28 @@ def create_manual_task_artificial_graphs(
|
|
|
185
268
|
|
|
186
269
|
# Manual task collections act as root nodes - they don't need identity dependencies
|
|
187
270
|
# since they provide manually-entered data rather than consuming identity data.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
for config in current_configs:
|
|
207
|
-
logger.debug(
|
|
208
|
-
f"Processing config {config.id} with {len(config.field_definitions)} fields"
|
|
271
|
+
current_configs = [
|
|
272
|
+
config for config in manual_task.configs if config.is_current
|
|
273
|
+
]
|
|
274
|
+
for config in current_configs:
|
|
275
|
+
if config.config_type not in [
|
|
276
|
+
ManualTaskConfigurationType.access_privacy_request,
|
|
277
|
+
ManualTaskConfigurationType.erasure_privacy_request,
|
|
278
|
+
]:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
for field in config.field_definitions:
|
|
282
|
+
# Create a scalar field for each manual task field
|
|
283
|
+
field_metadata = field.field_metadata or {}
|
|
284
|
+
data_categories = field_metadata.get("data_categories", [])
|
|
285
|
+
|
|
286
|
+
scalar_field = ScalarField(
|
|
287
|
+
name=field.field_key,
|
|
288
|
+
data_categories=data_categories,
|
|
209
289
|
)
|
|
210
|
-
|
|
211
|
-
# Create a scalar field for each manual task field
|
|
212
|
-
field_metadata = field.field_metadata or {}
|
|
213
|
-
data_categories = field_metadata.get("data_categories", [])
|
|
214
|
-
|
|
215
|
-
scalar_field = ScalarField(
|
|
216
|
-
name=field.field_key,
|
|
217
|
-
data_categories=data_categories,
|
|
218
|
-
)
|
|
219
|
-
fields.append(scalar_field)
|
|
220
|
-
else:
|
|
221
|
-
logger.warning(
|
|
222
|
-
f"No manual task found for connection {connection_key}, skipping"
|
|
223
|
-
)
|
|
290
|
+
fields.append(scalar_field)
|
|
224
291
|
|
|
225
292
|
if fields: # Only create graph if there are fields
|
|
226
|
-
logger.debug(
|
|
227
|
-
f"Creating graph for connection {connection_key} with {len(fields)} fields"
|
|
228
|
-
)
|
|
229
293
|
# Create a synthetic Collection
|
|
230
294
|
collection = Collection(
|
|
231
295
|
name=ManualTaskAddress.MANUAL_DATA_COLLECTION,
|
|
@@ -243,13 +307,5 @@ def create_manual_task_artificial_graphs(
|
|
|
243
307
|
)
|
|
244
308
|
|
|
245
309
|
manual_task_graphs.append(graph_dataset)
|
|
246
|
-
logger.debug(
|
|
247
|
-
f"Successfully created manual task graph for connection {connection_key}"
|
|
248
|
-
)
|
|
249
|
-
else:
|
|
250
|
-
logger.warning(
|
|
251
|
-
f"No fields found for connection {connection_key}, skipping graph creation"
|
|
252
|
-
)
|
|
253
310
|
|
|
254
|
-
logger.debug(f"Created {len(manual_task_graphs)} manual task graphs")
|
|
255
311
|
return manual_task_graphs
|
fides/api/util/storage_util.py
CHANGED
|
@@ -14,25 +14,6 @@ from fides.api.schemas.storage.storage_secrets_docs_only import possible_storage
|
|
|
14
14
|
from fides.api.util.custom_json_encoder import CustomJSONEncoder
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def format_size(size_bytes: float) -> str:
|
|
18
|
-
"""
|
|
19
|
-
Format size in bytes to human readable format.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
size_bytes: Size in bytes
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
Formatted string with appropriate unit (B, KB, MB, GB, TB, PB)
|
|
26
|
-
"""
|
|
27
|
-
units = ["B", "KB", "MB", "GB", "TB"]
|
|
28
|
-
size = float(size_bytes)
|
|
29
|
-
for unit in units:
|
|
30
|
-
if size < 1024.0:
|
|
31
|
-
return f"{size:.1f} {unit}"
|
|
32
|
-
size /= 1024.0
|
|
33
|
-
return f"{size:.1f} PB"
|
|
34
|
-
|
|
35
|
-
|
|
36
17
|
def get_schema_for_secrets(
|
|
37
18
|
storage_type: Union[StorageType, str],
|
|
38
19
|
secrets: possible_storage_secrets,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link data-next-font="" rel="preconnect" href="/" crossorigin="anonymous"/><link rel="preload" href="/_next/static/css/d9924caa849931b3.css" as="style"/><link rel="stylesheet" href="/_next/static/css/d9924caa849931b3.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-90e8ec1fc5c6455b.js" defer=""></script><script src="/_next/static/chunks/framework-c92fc3344e6fd165.js" defer=""></script><script src="/_next/static/chunks/main-090643377c8254e6.js" defer=""></script><script src="/_next/static/chunks/pages/_app-750d6bd16c971bb9.js" defer=""></script><script src="/_next/static/chunks/pages/404-2d803dab6a00f353.js" defer=""></script><script src="/_next/static/
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link data-next-font="" rel="preconnect" href="/" crossorigin="anonymous"/><link rel="preload" href="/_next/static/css/d9924caa849931b3.css" as="style"/><link rel="stylesheet" href="/_next/static/css/d9924caa849931b3.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-90e8ec1fc5c6455b.js" defer=""></script><script src="/_next/static/chunks/framework-c92fc3344e6fd165.js" defer=""></script><script src="/_next/static/chunks/main-090643377c8254e6.js" defer=""></script><script src="/_next/static/chunks/pages/_app-750d6bd16c971bb9.js" defer=""></script><script src="/_next/static/chunks/pages/404-2d803dab6a00f353.js" defer=""></script><script src="/_next/static/-pDvglWsgqfWeQixOy5zJ/_buildManifest.js" defer=""></script><script src="/_next/static/-pDvglWsgqfWeQixOy5zJ/_ssgManifest.js" defer=""></script><style>.data-ant-cssinjs-cache-path{content:"";}</style></head><body><div id="__next"><div style="height:100%;display:flex"></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/404","query":{},"buildId":"-pDvglWsgqfWeQixOy5zJ","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
|