truthound-dashboard 1.4.3__py3-none-any.whl → 1.5.0__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 (205) hide show
  1. truthound_dashboard/api/alerts.py +75 -86
  2. truthound_dashboard/api/anomaly.py +7 -13
  3. truthound_dashboard/api/cross_alerts.py +38 -52
  4. truthound_dashboard/api/drift.py +49 -59
  5. truthound_dashboard/api/drift_monitor.py +234 -79
  6. truthound_dashboard/api/enterprise_sampling.py +498 -0
  7. truthound_dashboard/api/history.py +57 -5
  8. truthound_dashboard/api/lineage.py +3 -48
  9. truthound_dashboard/api/maintenance.py +104 -49
  10. truthound_dashboard/api/mask.py +1 -2
  11. truthound_dashboard/api/middleware.py +2 -1
  12. truthound_dashboard/api/model_monitoring.py +435 -311
  13. truthound_dashboard/api/notifications.py +227 -191
  14. truthound_dashboard/api/notifications_advanced.py +21 -20
  15. truthound_dashboard/api/observability.py +586 -0
  16. truthound_dashboard/api/plugins.py +2 -433
  17. truthound_dashboard/api/profile.py +199 -37
  18. truthound_dashboard/api/quality_reporter.py +701 -0
  19. truthound_dashboard/api/reports.py +7 -16
  20. truthound_dashboard/api/router.py +66 -0
  21. truthound_dashboard/api/rule_suggestions.py +5 -5
  22. truthound_dashboard/api/scan.py +17 -19
  23. truthound_dashboard/api/schedules.py +85 -50
  24. truthound_dashboard/api/schema_evolution.py +6 -6
  25. truthound_dashboard/api/schema_watcher.py +667 -0
  26. truthound_dashboard/api/sources.py +98 -27
  27. truthound_dashboard/api/tiering.py +1323 -0
  28. truthound_dashboard/api/triggers.py +14 -11
  29. truthound_dashboard/api/validations.py +12 -11
  30. truthound_dashboard/api/versioning.py +1 -6
  31. truthound_dashboard/core/__init__.py +129 -3
  32. truthound_dashboard/core/actions/__init__.py +62 -0
  33. truthound_dashboard/core/actions/custom.py +426 -0
  34. truthound_dashboard/core/actions/notifications.py +910 -0
  35. truthound_dashboard/core/actions/storage.py +472 -0
  36. truthound_dashboard/core/actions/webhook.py +281 -0
  37. truthound_dashboard/core/anomaly.py +262 -67
  38. truthound_dashboard/core/anomaly_explainer.py +4 -3
  39. truthound_dashboard/core/backends/__init__.py +67 -0
  40. truthound_dashboard/core/backends/base.py +299 -0
  41. truthound_dashboard/core/backends/errors.py +191 -0
  42. truthound_dashboard/core/backends/factory.py +423 -0
  43. truthound_dashboard/core/backends/mock_backend.py +451 -0
  44. truthound_dashboard/core/backends/truthound_backend.py +718 -0
  45. truthound_dashboard/core/checkpoint/__init__.py +87 -0
  46. truthound_dashboard/core/checkpoint/adapters.py +814 -0
  47. truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
  48. truthound_dashboard/core/checkpoint/runner.py +270 -0
  49. truthound_dashboard/core/connections.py +437 -10
  50. truthound_dashboard/core/converters/__init__.py +14 -0
  51. truthound_dashboard/core/converters/truthound.py +620 -0
  52. truthound_dashboard/core/cross_alerts.py +540 -320
  53. truthound_dashboard/core/datasource_factory.py +1672 -0
  54. truthound_dashboard/core/drift_monitor.py +216 -20
  55. truthound_dashboard/core/enterprise_sampling.py +1291 -0
  56. truthound_dashboard/core/interfaces/__init__.py +225 -0
  57. truthound_dashboard/core/interfaces/actions.py +652 -0
  58. truthound_dashboard/core/interfaces/base.py +247 -0
  59. truthound_dashboard/core/interfaces/checkpoint.py +676 -0
  60. truthound_dashboard/core/interfaces/protocols.py +664 -0
  61. truthound_dashboard/core/interfaces/reporters.py +650 -0
  62. truthound_dashboard/core/interfaces/routing.py +646 -0
  63. truthound_dashboard/core/interfaces/triggers.py +619 -0
  64. truthound_dashboard/core/lineage.py +407 -71
  65. truthound_dashboard/core/model_monitoring.py +431 -3
  66. truthound_dashboard/core/notifications/base.py +4 -0
  67. truthound_dashboard/core/notifications/channels.py +501 -1203
  68. truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
  69. truthound_dashboard/core/notifications/deduplication/service.py +131 -348
  70. truthound_dashboard/core/notifications/dispatcher.py +202 -11
  71. truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
  72. truthound_dashboard/core/notifications/escalation/engine.py +168 -358
  73. truthound_dashboard/core/notifications/routing/__init__.py +88 -128
  74. truthound_dashboard/core/notifications/routing/engine.py +90 -317
  75. truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
  76. truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
  77. truthound_dashboard/core/notifications/throttling/builder.py +117 -255
  78. truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
  79. truthound_dashboard/core/phase5/collaboration.py +1 -1
  80. truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
  81. truthound_dashboard/core/quality_reporter.py +1359 -0
  82. truthound_dashboard/core/report_history.py +0 -6
  83. truthound_dashboard/core/reporters/__init__.py +175 -14
  84. truthound_dashboard/core/reporters/adapters.py +943 -0
  85. truthound_dashboard/core/reporters/base.py +0 -3
  86. truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
  87. truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
  88. truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
  89. truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
  90. truthound_dashboard/core/reporters/compat.py +266 -0
  91. truthound_dashboard/core/reporters/csv_reporter.py +2 -35
  92. truthound_dashboard/core/reporters/factory.py +526 -0
  93. truthound_dashboard/core/reporters/interfaces.py +745 -0
  94. truthound_dashboard/core/reporters/registry.py +1 -10
  95. truthound_dashboard/core/scheduler.py +165 -0
  96. truthound_dashboard/core/schema_evolution.py +3 -3
  97. truthound_dashboard/core/schema_watcher.py +1528 -0
  98. truthound_dashboard/core/services.py +595 -76
  99. truthound_dashboard/core/store_manager.py +810 -0
  100. truthound_dashboard/core/streaming_anomaly.py +169 -4
  101. truthound_dashboard/core/tiering.py +1309 -0
  102. truthound_dashboard/core/triggers/evaluators.py +178 -8
  103. truthound_dashboard/core/truthound_adapter.py +2620 -197
  104. truthound_dashboard/core/unified_alerts.py +23 -20
  105. truthound_dashboard/db/__init__.py +8 -0
  106. truthound_dashboard/db/database.py +8 -2
  107. truthound_dashboard/db/models.py +944 -25
  108. truthound_dashboard/db/repository.py +2 -0
  109. truthound_dashboard/main.py +11 -0
  110. truthound_dashboard/schemas/__init__.py +177 -16
  111. truthound_dashboard/schemas/base.py +44 -23
  112. truthound_dashboard/schemas/collaboration.py +19 -6
  113. truthound_dashboard/schemas/cross_alerts.py +19 -3
  114. truthound_dashboard/schemas/drift.py +61 -55
  115. truthound_dashboard/schemas/drift_monitor.py +67 -23
  116. truthound_dashboard/schemas/enterprise_sampling.py +653 -0
  117. truthound_dashboard/schemas/lineage.py +0 -33
  118. truthound_dashboard/schemas/mask.py +10 -8
  119. truthound_dashboard/schemas/model_monitoring.py +89 -10
  120. truthound_dashboard/schemas/notifications_advanced.py +13 -0
  121. truthound_dashboard/schemas/observability.py +453 -0
  122. truthound_dashboard/schemas/plugins.py +0 -280
  123. truthound_dashboard/schemas/profile.py +154 -247
  124. truthound_dashboard/schemas/quality_reporter.py +403 -0
  125. truthound_dashboard/schemas/reports.py +2 -2
  126. truthound_dashboard/schemas/rule_suggestion.py +8 -1
  127. truthound_dashboard/schemas/scan.py +4 -24
  128. truthound_dashboard/schemas/schedule.py +11 -3
  129. truthound_dashboard/schemas/schema_watcher.py +727 -0
  130. truthound_dashboard/schemas/source.py +17 -2
  131. truthound_dashboard/schemas/tiering.py +822 -0
  132. truthound_dashboard/schemas/triggers.py +16 -0
  133. truthound_dashboard/schemas/unified_alerts.py +7 -0
  134. truthound_dashboard/schemas/validation.py +0 -13
  135. truthound_dashboard/schemas/validators/base.py +41 -21
  136. truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
  137. truthound_dashboard/schemas/validators/localization_validators.py +273 -0
  138. truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
  139. truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
  140. truthound_dashboard/schemas/validators/referential_validators.py +312 -0
  141. truthound_dashboard/schemas/validators/registry.py +93 -8
  142. truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
  143. truthound_dashboard/schemas/versioning.py +1 -6
  144. truthound_dashboard/static/index.html +2 -2
  145. truthound_dashboard-1.5.0.dist-info/METADATA +309 -0
  146. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/RECORD +149 -148
  147. truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
  148. truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
  149. truthound_dashboard/core/plugins/hooks/manager.py +0 -403
  150. truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
  151. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
  152. truthound_dashboard/core/reporters/junit_reporter.py +0 -233
  153. truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
  154. truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
  155. truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
  156. truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
  157. truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
  158. truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
  159. truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
  160. truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
  161. truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
  162. truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
  163. truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
  164. truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
  165. truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
  166. truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
  167. truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
  168. truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
  169. truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
  170. truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
  171. truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
  172. truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
  173. truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
  174. truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
  175. truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
  176. truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
  177. truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
  178. truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
  179. truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
  180. truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
  181. truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
  182. truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
  183. truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
  184. truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
  185. truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
  186. truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
  187. truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
  188. truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
  189. truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
  190. truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
  191. truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
  192. truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
  193. truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
  194. truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
  195. truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
  196. truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
  197. truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
  198. truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
  199. truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
  200. truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
  201. truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
  202. truthound_dashboard-1.4.3.dist-info/METADATA +0 -505
  203. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
  204. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
  205. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -30,18 +30,39 @@ from urllib.parse import quote_plus
30
30
  class SourceType(str, Enum):
31
31
  """Supported data source types."""
32
32
 
33
+ # File-based
33
34
  FILE = "file"
35
+ CSV = "csv"
36
+ PARQUET = "parquet"
37
+ JSON = "json"
38
+ NDJSON = "ndjson"
39
+ JSONL = "jsonl"
40
+
41
+ # Core SQL
34
42
  POSTGRESQL = "postgresql"
35
43
  MYSQL = "mysql"
36
44
  SQLITE = "sqlite"
45
+
46
+ # Cloud Data Warehouses
37
47
  SNOWFLAKE = "snowflake"
38
48
  BIGQUERY = "bigquery"
39
49
  REDSHIFT = "redshift"
40
50
  DATABRICKS = "databricks"
51
+
52
+ # Enterprise
41
53
  ORACLE = "oracle"
42
54
  SQLSERVER = "sqlserver"
55
+
56
+ # Big Data
43
57
  SPARK = "spark"
44
58
 
59
+ # NoSQL
60
+ MONGODB = "mongodb"
61
+ ELASTICSEARCH = "elasticsearch"
62
+
63
+ # Streaming
64
+ KAFKA = "kafka"
65
+
45
66
 
46
67
  class FieldType(str, Enum):
47
68
  """Field types for configuration forms."""
@@ -104,7 +125,7 @@ class SourceTypeDefinition:
104
125
  name: str
105
126
  description: str
106
127
  icon: str
107
- category: Literal["file", "database", "warehouse", "bigdata"]
128
+ category: Literal["file", "database", "warehouse", "bigdata", "nosql", "streaming"]
108
129
  fields: list[FieldDefinition]
109
130
  docs_url: str = ""
110
131
 
@@ -1195,19 +1216,395 @@ class SparkConnectionBuilder(ConnectionBuilder):
1195
1216
  )
1196
1217
 
1197
1218
 
1219
+ class MongoDBConnectionBuilder(ConnectionBuilder):
1220
+ """Connection builder for MongoDB."""
1221
+
1222
+ source_type = SourceType.MONGODB
1223
+
1224
+ def build(self, config: dict[str, Any]) -> str:
1225
+ """Build MongoDB connection string."""
1226
+ # Support direct connection string
1227
+ if config.get("connection_string"):
1228
+ return config["connection_string"]
1229
+
1230
+ host = config.get("host", "localhost")
1231
+ port = config.get("port", 27017)
1232
+ database = config.get("database", "")
1233
+ username = config.get("username", "")
1234
+ password = quote_plus(config.get("password", ""))
1235
+
1236
+ # Build connection string
1237
+ if username and password:
1238
+ conn = f"mongodb://{username}:{password}@{host}:{port}/{database}"
1239
+ else:
1240
+ conn = f"mongodb://{host}:{port}/{database}"
1241
+
1242
+ # Add options
1243
+ options = []
1244
+ if config.get("auth_source"):
1245
+ options.append(f"authSource={config['auth_source']}")
1246
+ if config.get("replica_set"):
1247
+ options.append(f"replicaSet={config['replica_set']}")
1248
+ if config.get("ssl"):
1249
+ options.append("ssl=true")
1250
+
1251
+ if options:
1252
+ conn += "?" + "&".join(options)
1253
+
1254
+ return conn
1255
+
1256
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
1257
+ """Validate MongoDB configuration."""
1258
+ errors = []
1259
+
1260
+ # Either connection_string or host is required
1261
+ if not config.get("connection_string") and not config.get("host"):
1262
+ errors.append("Either connection_string or host is required")
1263
+
1264
+ if not config.get("database"):
1265
+ errors.append("database is required")
1266
+
1267
+ if not config.get("collection"):
1268
+ errors.append("collection is required")
1269
+
1270
+ return errors
1271
+
1272
+ @classmethod
1273
+ def get_definition(cls) -> SourceTypeDefinition:
1274
+ """Get the source type definition for UI rendering."""
1275
+ return SourceTypeDefinition(
1276
+ type=SourceType.MONGODB.value,
1277
+ name="MongoDB",
1278
+ description="MongoDB document database",
1279
+ icon="database",
1280
+ category="nosql",
1281
+ docs_url="https://www.mongodb.com/docs/",
1282
+ fields=[
1283
+ FieldDefinition(
1284
+ name="connection_string",
1285
+ label="Connection String",
1286
+ placeholder="mongodb://localhost:27017/mydb",
1287
+ description="Full MongoDB connection URI (alternative to individual fields)",
1288
+ ),
1289
+ FieldDefinition(
1290
+ name="host",
1291
+ label="Host",
1292
+ placeholder="localhost",
1293
+ description="MongoDB server hostname or IP",
1294
+ ),
1295
+ FieldDefinition(
1296
+ name="port",
1297
+ label="Port",
1298
+ type=FieldType.NUMBER,
1299
+ default=27017,
1300
+ min_value=1,
1301
+ max_value=65535,
1302
+ description="MongoDB server port",
1303
+ ),
1304
+ FieldDefinition(
1305
+ name="database",
1306
+ label="Database",
1307
+ required=True,
1308
+ placeholder="mydb",
1309
+ description="Database name",
1310
+ ),
1311
+ FieldDefinition(
1312
+ name="collection",
1313
+ label="Collection",
1314
+ required=True,
1315
+ placeholder="users",
1316
+ description="Collection name to validate",
1317
+ ),
1318
+ FieldDefinition(
1319
+ name="username",
1320
+ label="Username",
1321
+ description="Database username",
1322
+ ),
1323
+ FieldDefinition(
1324
+ name="password",
1325
+ label="Password",
1326
+ type=FieldType.PASSWORD,
1327
+ description="Database password",
1328
+ ),
1329
+ FieldDefinition(
1330
+ name="auth_source",
1331
+ label="Auth Source",
1332
+ placeholder="admin",
1333
+ default="admin",
1334
+ description="Authentication database",
1335
+ ),
1336
+ FieldDefinition(
1337
+ name="replica_set",
1338
+ label="Replica Set",
1339
+ placeholder="rs0",
1340
+ description="Replica set name (for replica set connections)",
1341
+ ),
1342
+ FieldDefinition(
1343
+ name="ssl",
1344
+ label="Use SSL/TLS",
1345
+ type=FieldType.BOOLEAN,
1346
+ default=False,
1347
+ description="Enable SSL/TLS connection",
1348
+ ),
1349
+ ],
1350
+ )
1351
+
1352
+
1353
+ class ElasticsearchConnectionBuilder(ConnectionBuilder):
1354
+ """Connection builder for Elasticsearch."""
1355
+
1356
+ source_type = SourceType.ELASTICSEARCH
1357
+
1358
+ def build(self, config: dict[str, Any]) -> str:
1359
+ """Build Elasticsearch connection URL."""
1360
+ hosts = config.get("hosts", config.get("host", "localhost"))
1361
+ port = config.get("port", 9200)
1362
+ scheme = "https" if config.get("use_ssl") else "http"
1363
+
1364
+ # Handle multiple hosts
1365
+ if isinstance(hosts, list):
1366
+ return ",".join([f"{scheme}://{h}:{port}" for h in hosts])
1367
+
1368
+ return f"{scheme}://{hosts}:{port}"
1369
+
1370
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
1371
+ """Validate Elasticsearch configuration."""
1372
+ errors = []
1373
+
1374
+ if not config.get("hosts") and not config.get("host"):
1375
+ errors.append("Either hosts or host is required")
1376
+
1377
+ if not config.get("index"):
1378
+ errors.append("index is required")
1379
+
1380
+ return errors
1381
+
1382
+ @classmethod
1383
+ def get_definition(cls) -> SourceTypeDefinition:
1384
+ """Get the source type definition for UI rendering."""
1385
+ return SourceTypeDefinition(
1386
+ type=SourceType.ELASTICSEARCH.value,
1387
+ name="Elasticsearch",
1388
+ description="Elasticsearch search engine",
1389
+ icon="search",
1390
+ category="nosql",
1391
+ docs_url="https://www.elastic.co/guide/en/elasticsearch/reference/current/",
1392
+ fields=[
1393
+ FieldDefinition(
1394
+ name="host",
1395
+ label="Host",
1396
+ required=True,
1397
+ placeholder="localhost",
1398
+ description="Elasticsearch server hostname or IP",
1399
+ ),
1400
+ FieldDefinition(
1401
+ name="port",
1402
+ label="Port",
1403
+ type=FieldType.NUMBER,
1404
+ default=9200,
1405
+ min_value=1,
1406
+ max_value=65535,
1407
+ description="Elasticsearch server port",
1408
+ ),
1409
+ FieldDefinition(
1410
+ name="index",
1411
+ label="Index",
1412
+ required=True,
1413
+ placeholder="my_index",
1414
+ description="Index name to validate",
1415
+ ),
1416
+ FieldDefinition(
1417
+ name="username",
1418
+ label="Username",
1419
+ description="Elasticsearch username",
1420
+ ),
1421
+ FieldDefinition(
1422
+ name="password",
1423
+ label="Password",
1424
+ type=FieldType.PASSWORD,
1425
+ description="Elasticsearch password",
1426
+ ),
1427
+ FieldDefinition(
1428
+ name="api_key",
1429
+ label="API Key",
1430
+ type=FieldType.PASSWORD,
1431
+ description="API key for authentication (alternative to username/password)",
1432
+ ),
1433
+ FieldDefinition(
1434
+ name="use_ssl",
1435
+ label="Use SSL/TLS",
1436
+ type=FieldType.BOOLEAN,
1437
+ default=True,
1438
+ description="Enable SSL/TLS connection",
1439
+ ),
1440
+ FieldDefinition(
1441
+ name="verify_certs",
1442
+ label="Verify Certificates",
1443
+ type=FieldType.BOOLEAN,
1444
+ default=True,
1445
+ description="Verify SSL certificates",
1446
+ ),
1447
+ FieldDefinition(
1448
+ name="cloud_id",
1449
+ label="Cloud ID",
1450
+ placeholder="deployment:dXMtd2VzdC0...",
1451
+ description="Elastic Cloud deployment ID (for Elastic Cloud)",
1452
+ ),
1453
+ ],
1454
+ )
1455
+
1456
+
1457
+ class KafkaConnectionBuilder(ConnectionBuilder):
1458
+ """Connection builder for Apache Kafka."""
1459
+
1460
+ source_type = SourceType.KAFKA
1461
+
1462
+ def build(self, config: dict[str, Any]) -> str:
1463
+ """Build Kafka bootstrap servers string."""
1464
+ bootstrap_servers = config.get("bootstrap_servers", "localhost:9092")
1465
+ if isinstance(bootstrap_servers, list):
1466
+ return ",".join(bootstrap_servers)
1467
+ return bootstrap_servers
1468
+
1469
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
1470
+ """Validate Kafka configuration."""
1471
+ errors = []
1472
+
1473
+ if not config.get("bootstrap_servers"):
1474
+ errors.append("bootstrap_servers is required")
1475
+
1476
+ if not config.get("topic"):
1477
+ errors.append("topic is required")
1478
+
1479
+ return errors
1480
+
1481
+ @classmethod
1482
+ def get_definition(cls) -> SourceTypeDefinition:
1483
+ """Get the source type definition for UI rendering."""
1484
+ return SourceTypeDefinition(
1485
+ type=SourceType.KAFKA.value,
1486
+ name="Apache Kafka",
1487
+ description="Apache Kafka streaming platform",
1488
+ icon="radio",
1489
+ category="streaming",
1490
+ docs_url="https://kafka.apache.org/documentation/",
1491
+ fields=[
1492
+ FieldDefinition(
1493
+ name="bootstrap_servers",
1494
+ label="Bootstrap Servers",
1495
+ required=True,
1496
+ placeholder="localhost:9092",
1497
+ description="Comma-separated list of Kafka broker addresses",
1498
+ ),
1499
+ FieldDefinition(
1500
+ name="topic",
1501
+ label="Topic",
1502
+ required=True,
1503
+ placeholder="my_topic",
1504
+ description="Kafka topic to consume from",
1505
+ ),
1506
+ FieldDefinition(
1507
+ name="group_id",
1508
+ label="Consumer Group ID",
1509
+ placeholder="truthound-validator",
1510
+ default="truthound-validator",
1511
+ description="Consumer group identifier",
1512
+ ),
1513
+ FieldDefinition(
1514
+ name="auto_offset_reset",
1515
+ label="Auto Offset Reset",
1516
+ type=FieldType.SELECT,
1517
+ options=[
1518
+ {"value": "earliest", "label": "Earliest (from beginning)"},
1519
+ {"value": "latest", "label": "Latest (only new messages)"},
1520
+ ],
1521
+ default="earliest",
1522
+ description="Where to start consuming if no offset is stored",
1523
+ ),
1524
+ FieldDefinition(
1525
+ name="max_messages",
1526
+ label="Max Messages",
1527
+ type=FieldType.NUMBER,
1528
+ default=10000,
1529
+ min_value=1,
1530
+ max_value=1000000,
1531
+ description="Maximum number of messages to consume per batch",
1532
+ ),
1533
+ FieldDefinition(
1534
+ name="security_protocol",
1535
+ label="Security Protocol",
1536
+ type=FieldType.SELECT,
1537
+ options=[
1538
+ {"value": "PLAINTEXT", "label": "Plaintext"},
1539
+ {"value": "SSL", "label": "SSL"},
1540
+ {"value": "SASL_PLAINTEXT", "label": "SASL Plaintext"},
1541
+ {"value": "SASL_SSL", "label": "SASL SSL"},
1542
+ ],
1543
+ default="PLAINTEXT",
1544
+ description="Security protocol for broker communication",
1545
+ ),
1546
+ FieldDefinition(
1547
+ name="sasl_mechanism",
1548
+ label="SASL Mechanism",
1549
+ type=FieldType.SELECT,
1550
+ options=[
1551
+ {"value": "PLAIN", "label": "PLAIN"},
1552
+ {"value": "SCRAM-SHA-256", "label": "SCRAM-SHA-256"},
1553
+ {"value": "SCRAM-SHA-512", "label": "SCRAM-SHA-512"},
1554
+ {"value": "OAUTHBEARER", "label": "OAuth Bearer"},
1555
+ ],
1556
+ default="PLAIN",
1557
+ description="SASL authentication mechanism",
1558
+ depends_on="security_protocol",
1559
+ depends_value="SASL_SSL",
1560
+ ),
1561
+ FieldDefinition(
1562
+ name="sasl_username",
1563
+ label="SASL Username",
1564
+ description="SASL authentication username",
1565
+ depends_on="security_protocol",
1566
+ depends_value="SASL_SSL",
1567
+ ),
1568
+ FieldDefinition(
1569
+ name="sasl_password",
1570
+ label="SASL Password",
1571
+ type=FieldType.PASSWORD,
1572
+ description="SASL authentication password",
1573
+ depends_on="security_protocol",
1574
+ depends_value="SASL_SSL",
1575
+ ),
1576
+ ],
1577
+ )
1578
+
1579
+
1198
1580
  # Registry of connection builders
1199
1581
  CONNECTION_BUILDERS: dict[str, type[ConnectionBuilder]] = {
1582
+ # File-based
1200
1583
  SourceType.FILE.value: FileConnectionBuilder,
1584
+ SourceType.CSV.value: FileConnectionBuilder,
1585
+ SourceType.PARQUET.value: FileConnectionBuilder,
1586
+ SourceType.JSON.value: FileConnectionBuilder,
1587
+ SourceType.NDJSON.value: FileConnectionBuilder,
1588
+ SourceType.JSONL.value: FileConnectionBuilder,
1589
+ # Core SQL
1201
1590
  SourceType.POSTGRESQL.value: PostgreSQLConnectionBuilder,
1202
1591
  SourceType.MYSQL.value: MySQLConnectionBuilder,
1203
1592
  SourceType.SQLITE.value: SQLiteConnectionBuilder,
1593
+ # Cloud Data Warehouses
1204
1594
  SourceType.SNOWFLAKE.value: SnowflakeConnectionBuilder,
1205
1595
  SourceType.BIGQUERY.value: BigQueryConnectionBuilder,
1206
1596
  SourceType.REDSHIFT.value: RedshiftConnectionBuilder,
1207
1597
  SourceType.DATABRICKS.value: DatabricksConnectionBuilder,
1598
+ # Enterprise
1208
1599
  SourceType.ORACLE.value: OracleConnectionBuilder,
1209
1600
  SourceType.SQLSERVER.value: SQLServerConnectionBuilder,
1601
+ # Big Data
1210
1602
  SourceType.SPARK.value: SparkConnectionBuilder,
1603
+ # NoSQL
1604
+ SourceType.MONGODB.value: MongoDBConnectionBuilder,
1605
+ SourceType.ELASTICSEARCH.value: ElasticsearchConnectionBuilder,
1606
+ # Streaming
1607
+ SourceType.KAFKA.value: KafkaConnectionBuilder,
1211
1608
  }
1212
1609
 
1213
1610
 
@@ -1256,6 +1653,10 @@ def build_connection_string(source_type: str, config: dict[str, Any]) -> str:
1256
1653
  async def test_connection(source_type: str, config: dict[str, Any]) -> dict[str, Any]:
1257
1654
  """Test database connection.
1258
1655
 
1656
+ Updated for truthound 2.x API:
1657
+ - Uses DataSourceFactory with new datasource API
1658
+ - Delegates to datasource_factory.test_connection for better reuse
1659
+
1259
1660
  Args:
1260
1661
  source_type: Type of data source.
1261
1662
  config: Source-specific configuration.
@@ -1274,8 +1675,6 @@ async def test_connection(source_type: str, config: dict[str, Any]) -> dict[str,
1274
1675
  "error": f"Configuration errors: {'; '.join(errors)}",
1275
1676
  }
1276
1677
 
1277
- connection_string = builder.build(config)
1278
-
1279
1678
  if source_type == "file":
1280
1679
  # For files, just check if path exists
1281
1680
  path = Path(config["path"])
@@ -1286,15 +1685,39 @@ async def test_connection(source_type: str, config: dict[str, Any]) -> dict[str,
1286
1685
  "message": f"File exists: {path.name} ({path.stat().st_size:,} bytes)",
1287
1686
  }
1288
1687
 
1289
- # For databases, use truthound to test connection
1290
- import truthound as th
1688
+ # Use new DataSourceFactory test_connection for database sources
1689
+ from .datasource_factory import test_connection as factory_test_connection
1291
1690
 
1292
- # Quick profile to test connection
1293
- result = th.profile(connection_string)
1294
- return {
1295
- "success": True,
1296
- "message": f"Connected! Found {result.column_count} columns, {result.row_count:,} rows",
1691
+ # Build SourceConfig-compatible dict
1692
+ full_config = {"type": source_type, **config}
1693
+
1694
+ # Map field names from connections to datasource_factory
1695
+ field_mapping = {
1696
+ "username": "user", # connections uses username, factory uses user
1297
1697
  }
1698
+ for old_key, new_key in field_mapping.items():
1699
+ if old_key in full_config and new_key not in full_config:
1700
+ full_config[new_key] = full_config[old_key]
1701
+
1702
+ result = await factory_test_connection(full_config)
1703
+
1704
+ # Convert result format
1705
+ if result["success"]:
1706
+ metadata = result.get("metadata", {})
1707
+ row_count = metadata.get("row_count", "unknown")
1708
+ columns = metadata.get("columns", [])
1709
+ col_count = len(columns) if columns else "unknown"
1710
+
1711
+ return {
1712
+ "success": True,
1713
+ "message": f"Connected! Found {col_count} columns, {row_count} rows",
1714
+ "metadata": metadata,
1715
+ }
1716
+ else:
1717
+ return {
1718
+ "success": False,
1719
+ "error": result.get("message", "Connection failed"),
1720
+ }
1298
1721
 
1299
1722
  except ImportError:
1300
1723
  return {"success": False, "error": "truthound package not available"}
@@ -1331,6 +1754,8 @@ def get_source_type_categories() -> list[dict[str, str]]:
1331
1754
  {"value": "database", "label": "Databases", "description": "Relational databases"},
1332
1755
  {"value": "warehouse", "label": "Data Warehouses", "description": "Cloud data warehouses"},
1333
1756
  {"value": "bigdata", "label": "Big Data", "description": "Big data platforms"},
1757
+ {"value": "nosql", "label": "NoSQL", "description": "Document and search databases"},
1758
+ {"value": "streaming", "label": "Streaming", "description": "Streaming data platforms"},
1334
1759
  ]
1335
1760
 
1336
1761
 
@@ -1345,6 +1770,8 @@ def get_source_types_by_category() -> dict[str, list[dict[str, Any]]]:
1345
1770
  "database": [],
1346
1771
  "warehouse": [],
1347
1772
  "bigdata": [],
1773
+ "nosql": [],
1774
+ "streaming": [],
1348
1775
  }
1349
1776
 
1350
1777
  for source_type in SourceType:
@@ -0,0 +1,14 @@
1
+ """Result converters for data quality backends.
2
+
3
+ This module provides converters that transform backend-specific result
4
+ objects into dashboard-standard result models.
5
+
6
+ The converter pattern isolates backend-specific code and makes it easy
7
+ to support multiple backends or handle API changes.
8
+ """
9
+
10
+ from .truthound import TruthoundResultConverter
11
+
12
+ __all__ = [
13
+ "TruthoundResultConverter",
14
+ ]