ethyca-fides 2.64.1b0__py2.py3-none-any.whl → 2.64.1b2__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 (106) hide show
  1. {ethyca_fides-2.64.1b0.dist-info → ethyca_fides-2.64.1b2.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.64.1b0.dist-info → ethyca_fides-2.64.1b2.dist-info}/RECORD +106 -102
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/6a76a1fa4f3f_add_manual_task_instance_table.py +256 -0
  5. fides/api/alembic/migrations/versions/aadfe83c5644_add_manual_task_to_connectiontype_enum.py +46 -0
  6. fides/api/db/base.py +4 -0
  7. fides/api/models/connectionconfig.py +11 -0
  8. fides/api/models/manual_tasks/__init__.py +7 -1
  9. fides/api/models/manual_tasks/manual_task.py +19 -3
  10. fides/api/models/manual_tasks/manual_task_config.py +39 -6
  11. fides/api/models/manual_tasks/manual_task_instance.py +187 -0
  12. fides/api/models/manual_tasks/manual_task_log.py +20 -7
  13. fides/api/schemas/manual_tasks/manual_task_schemas.py +42 -0
  14. fides/api/schemas/manual_tasks/manual_task_status.py +107 -46
  15. fides/api/service/connectors/postgres_connector.py +2 -2
  16. fides/common/api/v1/urn_registry.py +4 -0
  17. fides/service/manual_tasks/manual_task_config_service.py +17 -5
  18. fides/service/manual_tasks/manual_task_instance_service.py +285 -0
  19. fides/service/manual_tasks/manual_task_service.py +66 -10
  20. fides/ui-build/static/admin/404.html +1 -1
  21. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  22. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  23. fides/ui-build/static/admin/add-systems.html +1 -1
  24. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  25. fides/ui-build/static/admin/consent/configure.html +1 -1
  26. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  27. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  28. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  29. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  30. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  31. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  32. fides/ui-build/static/admin/consent/properties.html +1 -1
  33. fides/ui-build/static/admin/consent/reporting.html +1 -1
  34. fides/ui-build/static/admin/consent.html +1 -1
  35. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  36. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  37. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  38. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  39. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  40. fides/ui-build/static/admin/data-catalog.html +1 -1
  41. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  42. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  43. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  44. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  45. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  46. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  47. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  48. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  49. fides/ui-build/static/admin/datamap.html +1 -1
  50. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  51. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  52. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  53. fides/ui-build/static/admin/dataset/new.html +1 -1
  54. fides/ui-build/static/admin/dataset.html +1 -1
  55. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  56. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  57. fides/ui-build/static/admin/datastore-connection.html +1 -1
  58. fides/ui-build/static/admin/index.html +1 -1
  59. fides/ui-build/static/admin/integrations/[id].html +1 -1
  60. fides/ui-build/static/admin/integrations.html +1 -1
  61. fides/ui-build/static/admin/login/[provider].html +1 -1
  62. fides/ui-build/static/admin/login.html +1 -1
  63. fides/ui-build/static/admin/messaging/[id].html +1 -1
  64. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  65. fides/ui-build/static/admin/messaging.html +1 -1
  66. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  67. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  68. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  69. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  70. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  71. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  72. fides/ui-build/static/admin/poc/forms.html +1 -1
  73. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  74. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  75. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  76. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  77. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  78. fides/ui-build/static/admin/privacy-requests.html +1 -1
  79. fides/ui-build/static/admin/properties/[id].html +1 -1
  80. fides/ui-build/static/admin/properties/add-property.html +1 -1
  81. fides/ui-build/static/admin/properties.html +1 -1
  82. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  83. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  84. fides/ui-build/static/admin/settings/about.html +1 -1
  85. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  86. fides/ui-build/static/admin/settings/consent.html +1 -1
  87. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  88. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  89. fides/ui-build/static/admin/settings/domains.html +1 -1
  90. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  91. fides/ui-build/static/admin/settings/locations.html +1 -1
  92. fides/ui-build/static/admin/settings/organization.html +1 -1
  93. fides/ui-build/static/admin/settings/regulations.html +1 -1
  94. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  95. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  96. fides/ui-build/static/admin/systems.html +1 -1
  97. fides/ui-build/static/admin/taxonomy.html +1 -1
  98. fides/ui-build/static/admin/user-management/new.html +1 -1
  99. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  100. fides/ui-build/static/admin/user-management.html +1 -1
  101. {ethyca_fides-2.64.1b0.dist-info → ethyca_fides-2.64.1b2.dist-info}/WHEEL +0 -0
  102. {ethyca_fides-2.64.1b0.dist-info → ethyca_fides-2.64.1b2.dist-info}/entry_points.txt +0 -0
  103. {ethyca_fides-2.64.1b0.dist-info → ethyca_fides-2.64.1b2.dist-info}/licenses/LICENSE +0 -0
  104. {ethyca_fides-2.64.1b0.dist-info → ethyca_fides-2.64.1b2.dist-info}/top_level.txt +0 -0
  105. /fides/ui-build/static/admin/_next/static/{nRQ3pmK_d3F5PJE39rP2h → xYqpgK9yFhQK_wL_F_kAF}/_buildManifest.js +0 -0
  106. /fides/ui-build/static/admin/_next/static/{nRQ3pmK_d3F5PJE39rP2h → xYqpgK9yFhQK_wL_F_kAF}/_ssgManifest.js +0 -0
@@ -0,0 +1,46 @@
1
+ """add_manual_task_to_connectiontype_enum
2
+
3
+ Revision ID: aadfe83c5644
4
+ Revises: 6a76a1fa4f3f
5
+ Create Date: 2025-06-19 18:55:08.131278
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "aadfe83c5644"
14
+ down_revision = "6a76a1fa4f3f"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # Add manual_task to ConnectionType enum
21
+ op.execute("alter type connectiontype rename to connectiontype_old")
22
+ op.execute(
23
+ "create type connectiontype as enum('attentive_email', 'bigquery', 'datahub', 'dynamodb', 'fides', 'generic_consent_email', 'generic_erasure_email', 'dynamic_erasure_email', 'google_cloud_sql_mysql', 'google_cloud_sql_postgres', 'https', 'manual', 'manual_webhook', 'manual_task', 'mariadb', 'mongodb', 'mssql', 'mysql', 'okta', 'postgres', 'rds_mysql', 'rds_postgres', 'redshift', 's3', 'saas', 'scylla', 'snowflake', 'sovrn', 'timescale', 'website')"
24
+ )
25
+ op.execute(
26
+ (
27
+ "alter table connectionconfig alter column connection_type type connectiontype using "
28
+ "connection_type::text::connectiontype"
29
+ )
30
+ )
31
+ op.execute("drop type connectiontype_old")
32
+
33
+
34
+ def downgrade():
35
+ # Remove manual_task from ConnectionType enum
36
+ op.execute("alter type connectiontype rename to connectiontype_old")
37
+ op.execute(
38
+ "create type connectiontype as enum('attentive_email', 'bigquery', 'datahub', 'dynamodb', 'fides', 'generic_consent_email', 'generic_erasure_email', 'dynamic_erasure_email', 'google_cloud_sql_mysql', 'google_cloud_sql_postgres', 'https', 'manual', 'manual_webhook', 'mariadb', 'mongodb', 'mssql', 'mysql', 'okta', 'postgres', 'rds_mysql', 'rds_postgres', 'redshift', 's3', 'saas', 'scylla', 'snowflake', 'sovrn', 'timescale', 'website')"
39
+ )
40
+ op.execute(
41
+ (
42
+ "alter table connectionconfig alter column connection_type type connectiontype using "
43
+ "connection_type::text::connectiontype"
44
+ )
45
+ )
46
+ op.execute("drop type connectiontype_old")
fides/api/db/base.py CHANGED
@@ -36,6 +36,10 @@ from fides.api.models.manual_tasks.manual_task_config import (
36
36
  ManualTaskConfig,
37
37
  ManualTaskConfigField,
38
38
  )
39
+ from fides.api.models.manual_tasks.manual_task_instance import (
40
+ ManualTaskInstance,
41
+ ManualTaskSubmission,
42
+ )
39
43
  from fides.api.models.manual_tasks.manual_task_log import ManualTaskLog
40
44
  from fides.api.models.manual_webhook import AccessManualWebhook
41
45
  from fides.api.models.messaging import MessagingConfig
@@ -53,6 +53,7 @@ class ConnectionType(enum.Enum):
53
53
  https = "https"
54
54
  manual = "manual" # Deprecated - use manual_webhook instead
55
55
  manual_webhook = "manual_webhook" # Runs upfront before the traversal
56
+ manual_task = "manual_task" # Manual task integration
56
57
  mariadb = "mariadb"
57
58
  mongodb = "mongodb"
58
59
  mssql = "mssql"
@@ -88,6 +89,7 @@ class ConnectionType(enum.Enum):
88
89
  ConnectionType.google_cloud_sql_postgres.value: "Google Cloud SQL for Postgres",
89
90
  ConnectionType.https.value: "Policy Webhook",
90
91
  ConnectionType.manual_webhook.value: "Manual Process",
92
+ ConnectionType.manual_task.value: "Manual Task",
91
93
  ConnectionType.manual.value: "Manual Connector",
92
94
  ConnectionType.mariadb.value: "MariaDB",
93
95
  ConnectionType.mongodb.value: "MongoDB",
@@ -132,6 +134,7 @@ class ConnectionType(enum.Enum):
132
134
  ConnectionType.google_cloud_sql_postgres.value: SystemType.database,
133
135
  ConnectionType.https.value: SystemType.manual,
134
136
  ConnectionType.manual_webhook.value: SystemType.manual,
137
+ ConnectionType.manual_task.value: SystemType.manual,
135
138
  ConnectionType.manual.value: SystemType.manual,
136
139
  ConnectionType.mariadb.value: SystemType.database,
137
140
  ConnectionType.mongodb.value: SystemType.database,
@@ -229,6 +232,14 @@ class ConnectionConfig(Base):
229
232
  uselist=False,
230
233
  )
231
234
 
235
+ manual_task = relationship( # type: ignore[misc]
236
+ "ManualTask",
237
+ primaryjoin="and_(ConnectionConfig.id == foreign(ManualTask.parent_entity_id), "
238
+ "ManualTask.parent_entity_type == 'connection_config')",
239
+ cascade="delete",
240
+ uselist=False,
241
+ )
242
+
232
243
  pre_approval_webhooks = relationship( # type: ignore[misc]
233
244
  "PreApprovalWebhook",
234
245
  back_populates="connection_config",
@@ -3,12 +3,18 @@ from fides.api.models.manual_tasks.manual_task_config import (
3
3
  ManualTaskConfig,
4
4
  ManualTaskConfigField,
5
5
  )
6
+ from fides.api.models.manual_tasks.manual_task_instance import (
7
+ ManualTaskInstance,
8
+ ManualTaskSubmission,
9
+ )
6
10
  from fides.api.models.manual_tasks.manual_task_log import ManualTaskLog
7
11
 
8
12
  __all__ = [
9
13
  "ManualTask",
10
14
  "ManualTaskConfig",
11
15
  "ManualTaskConfigField",
12
- "ManualTaskReference",
16
+ "ManualTaskInstance",
13
17
  "ManualTaskLog",
18
+ "ManualTaskReference",
19
+ "ManualTaskSubmission",
14
20
  ]
@@ -1,6 +1,6 @@
1
1
  from typing import TYPE_CHECKING, Any
2
2
 
3
- from sqlalchemy import Column, DateTime, ForeignKey, String
3
+ from sqlalchemy import Column, ForeignKey, String
4
4
  from sqlalchemy.ext.declarative import declared_attr
5
5
  from sqlalchemy.orm import Session, relationship
6
6
 
@@ -18,6 +18,10 @@ if TYPE_CHECKING: # pragma: no cover
18
18
  from fides.api.models.manual_tasks.manual_task_config import ( # pragma: no cover
19
19
  ManualTaskConfig,
20
20
  )
21
+ from fides.api.models.manual_tasks.manual_task_instance import (
22
+ ManualTaskInstance,
23
+ ManualTaskSubmission,
24
+ )
21
25
 
22
26
 
23
27
  class ManualTask(Base):
@@ -48,7 +52,6 @@ class ManualTask(Base):
48
52
  nullable=False,
49
53
  default=ManualTaskParentEntityType.connection_config,
50
54
  )
51
- due_date = Column(DateTime, nullable=True)
52
55
 
53
56
  # Relationships
54
57
  references = relationship(
@@ -68,6 +71,19 @@ class ManualTask(Base):
68
71
  "ManualTaskConfig",
69
72
  back_populates="task",
70
73
  cascade="all, delete-orphan",
74
+ uselist=True,
75
+ )
76
+ instances = relationship(
77
+ "ManualTaskInstance",
78
+ back_populates="task",
79
+ viewonly=True,
80
+ uselist=True,
81
+ )
82
+ submissions = relationship(
83
+ "ManualTaskSubmission",
84
+ back_populates="task",
85
+ uselist=True,
86
+ viewonly=True,
71
87
  )
72
88
 
73
89
  # Properties
@@ -117,4 +133,4 @@ class ManualTaskReference(Base):
117
133
  reference_type = Column(EnumColumn(ManualTaskReferenceType), nullable=False)
118
134
 
119
135
  # Relationships
120
- task = relationship("ManualTask", back_populates="references")
136
+ task = relationship("ManualTask", back_populates="references", viewonly=True)
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Any, Optional
1
+ from typing import TYPE_CHECKING, Any, Optional, cast
2
2
 
3
3
  from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
4
4
  from sqlalchemy.dialects.postgresql import JSONB
@@ -13,10 +13,19 @@ from fides.api.schemas.manual_tasks.manual_task_config import (
13
13
  ManualTaskFieldMetadata,
14
14
  ManualTaskFieldType,
15
15
  )
16
- from fides.api.schemas.manual_tasks.manual_task_schemas import ManualTaskLogStatus
16
+ from fides.api.schemas.manual_tasks.manual_task_schemas import (
17
+ ManualTaskExecutionTiming,
18
+ ManualTaskLogStatus,
19
+ )
17
20
 
18
21
  if TYPE_CHECKING: # pragma: no cover
19
22
  from fides.api.models.manual_tasks.manual_task import ManualTask # pragma: no cover
23
+ from fides.api.models.manual_tasks.manual_task_instance import (
24
+ ManualTaskInstance, # pragma: no cover
25
+ )
26
+ from fides.api.models.manual_tasks.manual_task_instance import (
27
+ ManualTaskSubmission, # pragma: no cover
28
+ )
20
29
 
21
30
 
22
31
  class ManualTaskConfig(Base):
@@ -32,9 +41,20 @@ class ManualTaskConfig(Base):
32
41
  config_type = Column(EnumColumn(ManualTaskConfigurationType), nullable=False)
33
42
  version = Column(Integer, nullable=False, default=1)
34
43
  is_current = Column(Boolean, nullable=False, default=True)
44
+ execution_timing = Column(
45
+ EnumColumn(ManualTaskExecutionTiming),
46
+ nullable=False,
47
+ default=ManualTaskExecutionTiming.pre_execution,
48
+ )
35
49
 
36
50
  # Relationships
37
- task = relationship("ManualTask", back_populates="configs")
51
+ task = relationship("ManualTask", back_populates="configs", viewonly=True)
52
+ instances = relationship(
53
+ "ManualTaskInstance", back_populates="config", uselist=True, viewonly=True
54
+ )
55
+ submissions = relationship(
56
+ "ManualTaskSubmission", back_populates="config", uselist=True, viewonly=True
57
+ )
38
58
  field_definitions = relationship(
39
59
  "ManualTaskConfigField",
40
60
  back_populates="config",
@@ -45,7 +65,7 @@ class ManualTaskConfig(Base):
45
65
  "ManualTaskLog",
46
66
  back_populates="config",
47
67
  primaryjoin="ManualTaskConfig.id == ManualTaskLog.config_id",
48
- viewonly=True,
68
+ cascade="all, delete-orphan",
49
69
  )
50
70
 
51
71
  @classmethod
@@ -95,14 +115,27 @@ class ManualTaskConfigField(Base):
95
115
  field_type = Column(
96
116
  EnumColumn(ManualTaskFieldType), nullable=False
97
117
  ) # Using ManualTaskFieldType
98
- field_metadata = Column(JSONB, nullable=False, default={})
118
+ field_metadata: dict[str, Any] = cast(
119
+ dict[str, Any], Column(JSONB, nullable=False, default={})
120
+ )
99
121
 
100
122
  # Relationships
101
- config = relationship("ManualTaskConfig", back_populates="field_definitions")
123
+ config = relationship(
124
+ "ManualTaskConfig", back_populates="field_definitions", viewonly=True
125
+ )
126
+ submissions = relationship(
127
+ "ManualTaskSubmission",
128
+ back_populates="field",
129
+ uselist=True,
130
+ cascade="all, delete-orphan",
131
+ )
102
132
 
103
133
  @property
104
134
  def field_metadata_model(self) -> ManualTaskFieldMetadata:
105
135
  """Get the field metadata as a Pydantic model."""
136
+ assert isinstance(
137
+ self.field_metadata, dict
138
+ ), "field_metadata must be a dictionary"
106
139
  return ManualTaskFieldMetadata.model_validate(self.field_metadata)
107
140
 
108
141
  @classmethod
@@ -0,0 +1,187 @@
1
+ from datetime import datetime, timezone
2
+ from typing import TYPE_CHECKING, Optional
3
+
4
+ from sqlalchemy import Column, DateTime, ForeignKey, String
5
+ from sqlalchemy.dialects.postgresql import JSONB
6
+ from sqlalchemy.ext.declarative import declared_attr
7
+ from sqlalchemy.orm import relationship
8
+
9
+ from fides.api.db.base_class import Base
10
+ from fides.api.db.util import EnumColumn
11
+ from fides.api.models.manual_tasks.manual_task_config import ManualTaskConfigField
12
+ from fides.api.schemas.manual_tasks.manual_task_schemas import ManualTaskEntityType
13
+ from fides.api.schemas.manual_tasks.manual_task_status import (
14
+ StatusTransitionMixin,
15
+ StatusType,
16
+ )
17
+
18
+ if TYPE_CHECKING: # pragma: no cover
19
+ from fides.api.models.attachment import Attachment # pragma: no cover
20
+ from fides.api.models.fides_user import FidesUser # pragma: no cover
21
+ from fides.api.models.manual_tasks.manual_task import ManualTask # pragma: no cover
22
+ from fides.api.models.manual_tasks.manual_task_config import (
23
+ ManualTaskConfig, # pragma: no cover
24
+ )
25
+ from fides.api.models.manual_tasks.manual_task_log import (
26
+ ManualTaskLog, # pragma: no cover; pragma: no cover
27
+ )
28
+
29
+
30
+ class ManualTaskInstance(Base, StatusTransitionMixin):
31
+ """Model for tracking task status per entity instance.
32
+
33
+ This model implements StatusTransitionProtocol through the StatusTransitionMixin.
34
+ """
35
+
36
+ @declared_attr
37
+ def __tablename__(cls) -> str:
38
+ """Overriding base class method to set the table name."""
39
+ return "manual_task_instance"
40
+
41
+ # Database columns
42
+ task_id: Column[str] = Column(String, ForeignKey("manual_task.id"), nullable=False)
43
+ config_id: Column[str] = Column(
44
+ String, ForeignKey("manual_task_config.id"), nullable=False
45
+ )
46
+ # entity id is the entity that the instance relates to
47
+ # (e.g. a privacy request is an entity that has its own manual task instance)
48
+ entity_id: Column[str] = Column(String, nullable=False)
49
+ entity_type: Column[ManualTaskEntityType] = Column(
50
+ EnumColumn(ManualTaskEntityType), nullable=False
51
+ )
52
+ # ingnore[assignment] because the mypy and sqlalchemy types mismatch
53
+ # upgrading to 2.0 allows mapping which provides better type safety visibility.
54
+ status: Column[StatusType] = Column(EnumColumn(StatusType), nullable=False, default=StatusType.pending) # type: ignore[assignment]
55
+ completed_at: Column[Optional[datetime]] = Column(DateTime, nullable=True) # type: ignore[assignment]
56
+ completed_by_id: Column[Optional[str]] = Column(String, nullable=True) # type: ignore[assignment]
57
+ due_date: Column[Optional[datetime]] = Column(DateTime, nullable=True)
58
+
59
+ # Relationships
60
+ task = relationship("ManualTask", back_populates="instances")
61
+ config = relationship("ManualTaskConfig", back_populates="instances")
62
+ submissions = relationship(
63
+ "ManualTaskSubmission",
64
+ back_populates="instance",
65
+ cascade="all, delete-orphan",
66
+ uselist=True,
67
+ )
68
+ logs = relationship(
69
+ "ManualTaskLog",
70
+ back_populates="instance",
71
+ primaryjoin="ManualTaskInstance.id == ManualTaskLog.instance_id",
72
+ cascade="all, delete-orphan",
73
+ order_by="ManualTaskLog.created_at",
74
+ uselist=True,
75
+ )
76
+ attachments = relationship(
77
+ "Attachment",
78
+ secondary="attachment_reference",
79
+ primaryjoin="and_(ManualTaskInstance.id == ManualTaskSubmission.instance_id, "
80
+ "ManualTaskSubmission.id == AttachmentReference.reference_id, "
81
+ "AttachmentReference.reference_type == 'manual_task_submission')",
82
+ secondaryjoin="Attachment.id == AttachmentReference.attachment_id",
83
+ order_by="Attachment.created_at",
84
+ viewonly=True,
85
+ uselist=True,
86
+ )
87
+
88
+ @property
89
+ def required_fields(self) -> list["ManualTaskConfigField"]:
90
+ """Get all required fields."""
91
+ return [
92
+ field
93
+ for field in self.config.field_definitions
94
+ if field.field_metadata.get("required", False)
95
+ ]
96
+
97
+ @property
98
+ def incomplete_fields(self) -> list["ManualTaskConfigField"]:
99
+ """Get all fields that haven't been completed yet.
100
+ A field is considered incomplete if:
101
+ 1. It's required and has no submission
102
+ Returns:
103
+ list[ManualTaskConfigField]: List of incomplete fields
104
+ """
105
+ return [
106
+ field
107
+ for field in self.required_fields
108
+ if not self.get_submission_for_field(field.id)
109
+ ]
110
+
111
+ @property
112
+ def completed_fields(self) -> list["ManualTaskConfigField"]:
113
+ """Get all fields that have been completed."""
114
+ return [
115
+ field
116
+ for field in self.config.field_definitions
117
+ if field.field_metadata.get("required", False)
118
+ and self.get_submission_for_field(field.id)
119
+ ]
120
+
121
+ def get_submission_for_field(
122
+ self, field_id: str
123
+ ) -> Optional["ManualTaskSubmission"]:
124
+ """Get the submission for a specific field.
125
+
126
+ Args:
127
+ field_id: The ID of the field to get the submission for
128
+
129
+ Returns:
130
+ Optional[ManualTaskSubmission]: The submission for the field, or None if no submission exists
131
+ """
132
+ return next(
133
+ (
134
+ submission
135
+ for submission in self.submissions
136
+ if submission.field_id == field_id
137
+ ),
138
+ None,
139
+ )
140
+
141
+
142
+ class ManualTaskSubmission(Base):
143
+ """Model for storing user submissions.
144
+ Each submission represents data for a single field.
145
+ """
146
+
147
+ @declared_attr
148
+ def __tablename__(cls) -> str:
149
+ """Overriding base class method to set the table name."""
150
+ return "manual_task_submission"
151
+
152
+ # Database columns
153
+ task_id = Column(String, ForeignKey("manual_task.id"))
154
+ config_id = Column(String, ForeignKey("manual_task_config.id"))
155
+ field_id = Column(String, ForeignKey("manual_task_config_field.id"))
156
+ instance_id = Column(String, ForeignKey("manual_task_instance.id"), nullable=False)
157
+ submitted_by = Column(String, ForeignKey("fidesuser.id"), nullable=True)
158
+ submitted_at = Column(DateTime, default=datetime.now(timezone.utc), nullable=False)
159
+ data = Column(JSONB, nullable=False)
160
+
161
+ # Relationships
162
+ task = relationship("ManualTask", back_populates="submissions", viewonly=True)
163
+ config = relationship(
164
+ "ManualTaskConfig", back_populates="submissions", viewonly=True
165
+ )
166
+ field = relationship(
167
+ "ManualTaskConfigField", back_populates="submissions", viewonly=True
168
+ )
169
+ instance = relationship(
170
+ "ManualTaskInstance", back_populates="submissions", viewonly=True
171
+ )
172
+ attachments = relationship(
173
+ "Attachment",
174
+ secondary="attachment_reference",
175
+ primaryjoin="and_(ManualTaskSubmission.id == AttachmentReference.reference_id, "
176
+ "AttachmentReference.reference_type == 'manual_task_submission')",
177
+ secondaryjoin="Attachment.id == AttachmentReference.attachment_id",
178
+ order_by="Attachment.created_at",
179
+ viewonly=True,
180
+ uselist=True,
181
+ )
182
+
183
+ user = relationship(
184
+ "FidesUser",
185
+ primaryjoin="FidesUser.id == ManualTaskSubmission.submitted_by",
186
+ viewonly=True,
187
+ )
@@ -13,6 +13,9 @@ if TYPE_CHECKING: # pragma: no cover
13
13
  from fides.api.models.manual_tasks.manual_task_config import (
14
14
  ManualTaskConfig, # pragma: no cover
15
15
  )
16
+ from fides.api.models.manual_tasks.manual_task_instance import ( # pragma: no cover
17
+ ManualTaskInstance,
18
+ )
16
19
 
17
20
 
18
21
  class ManualTaskLog(Base):
@@ -26,19 +29,29 @@ class ManualTaskLog(Base):
26
29
  task_id = Column(
27
30
  String, ForeignKey("manual_task.id", ondelete="CASCADE"), nullable=False
28
31
  )
29
- config_id = Column(String, ForeignKey("manual_task_config.id"), nullable=True)
30
- instance_id = Column(String, nullable=True)
32
+ config_id = Column(
33
+ String, ForeignKey("manual_task_config.id", ondelete="CASCADE"), nullable=True
34
+ )
35
+ instance_id = Column(
36
+ String,
37
+ ForeignKey("manual_task_instance.id", ondelete="CASCADE"),
38
+ nullable=True,
39
+ )
31
40
  status = Column(String, nullable=False)
32
41
  message = Column(String, nullable=False)
33
42
  details = Column(JSONB, nullable=True)
34
43
 
35
- # Relationships - using string references to avoid circular imports
36
- task = relationship("ManualTask", back_populates="logs", foreign_keys=[task_id])
44
+ # Relationships
45
+ task = relationship(
46
+ "ManualTask", back_populates="logs", foreign_keys=[task_id], viewonly=True
47
+ )
37
48
  config = relationship(
38
- "ManualTaskConfig", back_populates="logs", foreign_keys=[config_id]
49
+ "ManualTaskConfig",
50
+ back_populates="logs",
51
+ foreign_keys=[config_id],
52
+ viewonly=True,
39
53
  )
40
- # TODO: Add instance relationship when it is implemented
41
- # instance = relationship("ManualTaskInstance", back_populates="logs")
54
+ instance = relationship("ManualTaskInstance", back_populates="logs", viewonly=True)
42
55
 
43
56
  @classmethod
44
57
  def create_log(
@@ -5,6 +5,15 @@ from typing import Annotated, Any, Optional
5
5
  from pydantic import ConfigDict, Field
6
6
 
7
7
  from fides.api.schemas.base_class import FidesSchema
8
+ from fides.api.schemas.manual_tasks.manual_task_status import StatusType
9
+
10
+
11
+ class ManualTaskExecutionTiming(str, Enum):
12
+ """Enum for when a manual task should be executed in the privacy request DAG."""
13
+
14
+ pre_execution = "pre_execution" # Execute before the main DAG
15
+ post_execution = "post_execution" # Execute after the main DAG
16
+ parallel = "parallel" # Execute in parallel with the main DAG
8
17
 
9
18
 
10
19
  class ManualTaskType(str, Enum):
@@ -23,6 +32,13 @@ class ManualTaskParentEntityType(str, Enum):
23
32
  # Add more parent entity types as needed
24
33
 
25
34
 
35
+ class ManualTaskEntityType(str, Enum):
36
+ """Enum for manual task entity types."""
37
+
38
+ privacy_request = "privacy_request"
39
+ # Add more entity types as needed
40
+
41
+
26
42
  class ManualTaskReferenceType(str, Enum):
27
43
  """Enum for manual task reference types."""
28
44
 
@@ -46,6 +62,32 @@ class ManualTaskLogStatus(str, Enum):
46
62
  awaiting_input = "awaiting_input"
47
63
 
48
64
 
65
+ class ManualTaskResponse(FidesSchema):
66
+ """Schema for manual task response."""
67
+
68
+ model_config = ConfigDict(extra="forbid")
69
+
70
+ id: Annotated[str, Field(..., description="Task ID")]
71
+ parent_entity_id: Annotated[str, Field(..., description="Parent entity ID")]
72
+ parent_entity_type: Annotated[
73
+ ManualTaskParentEntityType, Field(..., description="Parent entity type")
74
+ ]
75
+ status: Annotated[StatusType, Field(..., description="Task status")]
76
+ created_at: Annotated[datetime, Field(..., description="Creation timestamp")]
77
+ updated_at: Annotated[datetime, Field(..., description="Last update timestamp")]
78
+
79
+
80
+ class ManualTaskCreate(FidesSchema):
81
+ """Schema for creating a manual task."""
82
+
83
+ model_config = ConfigDict(extra="forbid")
84
+
85
+ parent_entity_id: Annotated[str, Field(..., description="Parent entity ID")]
86
+ parent_entity_type: Annotated[
87
+ ManualTaskParentEntityType, Field(..., description="Parent entity type")
88
+ ]
89
+
90
+
49
91
  class ManualTaskLogCreate(FidesSchema):
50
92
  """Schema for creating a manual task log entry."""
51
93