dataops-testgen 2.2.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 (270) hide show
  1. dataops_testgen-2.2.0.dist-info/LICENSE +203 -0
  2. dataops_testgen-2.2.0.dist-info/METADATA +287 -0
  3. dataops_testgen-2.2.0.dist-info/NOTICE +5 -0
  4. dataops_testgen-2.2.0.dist-info/RECORD +270 -0
  5. dataops_testgen-2.2.0.dist-info/WHEEL +5 -0
  6. dataops_testgen-2.2.0.dist-info/entry_points.txt +2 -0
  7. dataops_testgen-2.2.0.dist-info/top_level.txt +1 -0
  8. testgen/__init__.py +0 -0
  9. testgen/__main__.py +770 -0
  10. testgen/commands/__init__.py +0 -0
  11. testgen/commands/queries/__init__.py +0 -0
  12. testgen/commands/queries/execute_cat_tests_query.py +95 -0
  13. testgen/commands/queries/execute_tests_query.py +160 -0
  14. testgen/commands/queries/generate_tests_query.py +94 -0
  15. testgen/commands/queries/profiling_query.py +366 -0
  16. testgen/commands/queries/test_parameter_validation_query.py +88 -0
  17. testgen/commands/run_execute_cat_tests.py +162 -0
  18. testgen/commands/run_execute_tests.py +168 -0
  19. testgen/commands/run_generate_tests.py +107 -0
  20. testgen/commands/run_get_entities.py +122 -0
  21. testgen/commands/run_launch_db_config.py +84 -0
  22. testgen/commands/run_observability_exporter.py +330 -0
  23. testgen/commands/run_profiling_bridge.py +495 -0
  24. testgen/commands/run_quick_start.py +168 -0
  25. testgen/commands/run_setup_profiling_tools.py +96 -0
  26. testgen/commands/run_test_definition.py +146 -0
  27. testgen/commands/run_test_parameter_validation.py +135 -0
  28. testgen/commands/run_upgrade_db_config.py +156 -0
  29. testgen/common/__init__.py +8 -0
  30. testgen/common/clean_sql.py +53 -0
  31. testgen/common/credentials.py +25 -0
  32. testgen/common/database/__init__.py +0 -0
  33. testgen/common/database/database_service.py +629 -0
  34. testgen/common/database/flavor/__init__.py +0 -0
  35. testgen/common/database/flavor/flavor_service.py +75 -0
  36. testgen/common/database/flavor/mssql_flavor_service.py +34 -0
  37. testgen/common/database/flavor/postgresql_flavor_service.py +5 -0
  38. testgen/common/database/flavor/redshift_flavor_service.py +22 -0
  39. testgen/common/database/flavor/snowflake_flavor_service.py +69 -0
  40. testgen/common/database/flavor/trino_flavor_service.py +21 -0
  41. testgen/common/date_service.py +68 -0
  42. testgen/common/display_service.py +85 -0
  43. testgen/common/docker_service.py +76 -0
  44. testgen/common/encrypt.py +55 -0
  45. testgen/common/get_pipeline_parms.py +57 -0
  46. testgen/common/logs.py +79 -0
  47. testgen/common/process_service.py +62 -0
  48. testgen/common/read_file.py +69 -0
  49. testgen/settings.py +440 -0
  50. testgen/template/dbsetup/010_create_base_schema.sql +2 -0
  51. testgen/template/dbsetup/020_create_standard_functions_sprocs.sql +179 -0
  52. testgen/template/dbsetup/030_initialize_new_schema_structure.sql +735 -0
  53. testgen/template/dbsetup/040_populate_new_schema_project.sql +59 -0
  54. testgen/template/dbsetup/050_populate_new_schema_metadata.sql +1517 -0
  55. testgen/template/dbsetup/060_create_standard_views.sql +248 -0
  56. testgen/template/dbsetup/070_create_default_users.sql +17 -0
  57. testgen/template/dbsetup/075_grant_role_rights.sql +43 -0
  58. testgen/template/dbsetup/080_set_current_revision.sql +5 -0
  59. testgen/template/dbupgrade/0100_incremental_upgrade.sql +5 -0
  60. testgen/template/dbupgrade/0101_incremental_upgrade.sql +15 -0
  61. testgen/template/dbupgrade/0102_incremental_upgrade.sql +4 -0
  62. testgen/template/dbupgrade/0103_incremental_upgrade.sql +22 -0
  63. testgen/template/dbupgrade/0104_incremental_upgrade.sql +44 -0
  64. testgen/template/dbupgrade/0105_incremental_upgrade.sql +1 -0
  65. testgen/template/dbupgrade/0106_incremental_upgrade.sql +5 -0
  66. testgen/template/dbupgrade/0107_incremental_upgrade.sql +3 -0
  67. testgen/template/dbupgrade_helpers/get_tg_revision.sql +2 -0
  68. testgen/template/exec_cat_tests/ex_cat_build_agg_table_tests.sql +116 -0
  69. testgen/template/exec_cat_tests/ex_cat_get_distinct_tables.sql +11 -0
  70. testgen/template/exec_cat_tests/ex_cat_results_parse.sql +69 -0
  71. testgen/template/exec_cat_tests/ex_cat_retrieve_agg_test_parms.sql +6 -0
  72. testgen/template/exec_cat_tests/ex_cat_test_query.sql +8 -0
  73. testgen/template/execution/ex_finalize_test_run_results.sql +37 -0
  74. testgen/template/execution/ex_get_tests_non_cat.sql +47 -0
  75. testgen/template/execution/ex_update_test_record_in_testrun_table.sql +27 -0
  76. testgen/template/execution/ex_write_test_record_to_testrun_table.sql +6 -0
  77. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_no_drops_generic.sql +48 -0
  78. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_num_incr_generic.sql +34 -0
  79. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_above_generic.sql +49 -0
  80. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_within_generic.sql +49 -0
  81. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_same_generic.sql +49 -0
  82. testgen/template/flavors/generic/exec_query_tests/ex_custom_query_generic.sql +39 -0
  83. testgen/template/flavors/generic/exec_query_tests/ex_data_match_2way_generic.sql +58 -0
  84. testgen/template/flavors/generic/exec_query_tests/ex_data_match_generic.sql +44 -0
  85. testgen/template/flavors/generic/exec_query_tests/ex_prior_match_generic.sql +37 -0
  86. testgen/template/flavors/generic/exec_query_tests/ex_relative_entropy_generic.sql +53 -0
  87. testgen/template/flavors/generic/exec_query_tests/ex_window_match_no_drops_generic.sql +46 -0
  88. testgen/template/flavors/generic/exec_query_tests/ex_window_match_same_generic.sql +59 -0
  89. testgen/template/flavors/generic/profiling/contingency_counts.sql +3 -0
  90. testgen/template/flavors/generic/validate_tests/ex_get_project_column_list_generic.sql +3 -0
  91. testgen/template/flavors/mssql/exec_query_tests/ex_relative_entropy_mssql.sql +53 -0
  92. testgen/template/flavors/mssql/profiling/project_ddf_query_mssql.sql +35 -0
  93. testgen/template/flavors/mssql/profiling/project_profiling_query_mssql.yaml +246 -0
  94. testgen/template/flavors/mssql/profiling/project_secondary_profiling_query_mssql.sql +36 -0
  95. testgen/template/flavors/mssql/setup_profiling_tools/00_drop_existing_functions_mssql.sql +8 -0
  96. testgen/template/flavors/mssql/setup_profiling_tools/01_create_functions_mssql.sql +12 -0
  97. testgen/template/flavors/mssql/setup_profiling_tools/02_create_functions_mssql.sql +54 -0
  98. testgen/template/flavors/mssql/setup_profiling_tools/create_qc_schema_mssql.sql +4 -0
  99. testgen/template/flavors/mssql/setup_profiling_tools/grant_execute_privileges_mssql.sql +1 -0
  100. testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_no_drops_postgresql.sql +46 -0
  101. testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_same_postgresql.sql +59 -0
  102. testgen/template/flavors/postgresql/profiling/project_ddf_query_postgresql.sql +42 -0
  103. testgen/template/flavors/postgresql/profiling/project_profiling_query_postgresql.yaml +225 -0
  104. testgen/template/flavors/postgresql/profiling/project_secondary_profiling_query_postgresql.sql +28 -0
  105. testgen/template/flavors/postgresql/setup_profiling_tools/create_functions_postgresql.sql +157 -0
  106. testgen/template/flavors/postgresql/setup_profiling_tools/create_qc_schema_postgresql.sql +1 -0
  107. testgen/template/flavors/postgresql/setup_profiling_tools/grant_execute_privileges_postgresql.sql +2 -0
  108. testgen/template/flavors/redshift/profiling/project_ddf_query_redshift.sql +38 -0
  109. testgen/template/flavors/redshift/profiling/project_profiling_query_redshift.yaml +221 -0
  110. testgen/template/flavors/redshift/profiling/project_secondary_profiling_query_redshift.sql +29 -0
  111. testgen/template/flavors/redshift/setup_profiling_tools/create_functions_redshift.sql +115 -0
  112. testgen/template/flavors/redshift/setup_profiling_tools/create_qc_schema_redshift.sql +1 -0
  113. testgen/template/flavors/redshift/setup_profiling_tools/grant_execute_privileges_redshift.sql +2 -0
  114. testgen/template/flavors/snowflake/profiling/project_ddf_query_snowflake.sql +38 -0
  115. testgen/template/flavors/snowflake/profiling/project_profiling_query_snowflake.yaml +220 -0
  116. testgen/template/flavors/snowflake/profiling/project_secondary_profiling_query_snowflake.sql +29 -0
  117. testgen/template/flavors/snowflake/setup_profiling_tools/create_functions_snowflake.sql +69 -0
  118. testgen/template/flavors/snowflake/setup_profiling_tools/create_qc_schema_snowflake.sql +1 -0
  119. testgen/template/flavors/snowflake/setup_profiling_tools/grant_execute_privileges_snowflake.sql +6 -0
  120. testgen/template/flavors/trino/profiling/project_profiling_query_trino.yaml +219 -0
  121. testgen/template/flavors/trino/setup_profiling_tools/create_functions_trino.sql +92 -0
  122. testgen/template/flavors/trino/setup_profiling_tools/create_qc_schema_trino.sql +1 -0
  123. testgen/template/gen_funny_cat_tests/gen_test_constant.sql +104 -0
  124. testgen/template/gen_funny_cat_tests/gen_test_distinct_value_ct.sql +98 -0
  125. testgen/template/gen_funny_cat_tests/gen_test_row_ct.sql +57 -0
  126. testgen/template/gen_funny_cat_tests/gen_test_row_ct_pct.sql +59 -0
  127. testgen/template/generation/gen_delete_old_tests.sql +5 -0
  128. testgen/template/generation/gen_insert_test_suite.sql +5 -0
  129. testgen/template/generation/gen_retrieve_or_insert_test_suite.sql +58 -0
  130. testgen/template/generation/gen_standard_test_type_list.sql +13 -0
  131. testgen/template/generation/gen_standard_tests.sql +48 -0
  132. testgen/template/get_entities/get_connection.sql +21 -0
  133. testgen/template/get_entities/get_connections_list.sql +9 -0
  134. testgen/template/get_entities/get_latest.sql +4 -0
  135. testgen/template/get_entities/get_profile.sql +12 -0
  136. testgen/template/get_entities/get_profile_info.sql +17 -0
  137. testgen/template/get_entities/get_profile_list.sql +17 -0
  138. testgen/template/get_entities/get_profile_screen.sql +275 -0
  139. testgen/template/get_entities/get_project_list.sql +6 -0
  140. testgen/template/get_entities/get_table_group_list.sql +10 -0
  141. testgen/template/get_entities/get_test_generation_list.sql +18 -0
  142. testgen/template/get_entities/get_test_info.sql +41 -0
  143. testgen/template/get_entities/get_test_results_for_run_cli.sql +16 -0
  144. testgen/template/get_entities/get_test_run_list.sql +24 -0
  145. testgen/template/get_entities/get_test_suite.sql +13 -0
  146. testgen/template/get_entities/get_test_suite_list.sql +18 -0
  147. testgen/template/get_entities/list_test_types.sql +4 -0
  148. testgen/template/observability/get_event_data.sql +23 -0
  149. testgen/template/observability/get_test_results.sql +41 -0
  150. testgen/template/observability/update_test_results_exported_to_observability.sql +12 -0
  151. testgen/template/parms/parms_profiling.sql +34 -0
  152. testgen/template/parms/parms_test_execution.sql +13 -0
  153. testgen/template/parms/parms_test_gen.sql +23 -0
  154. testgen/template/profiling/contingency_columns.sql +7 -0
  155. testgen/template/profiling/datatype_suggestions.sql +56 -0
  156. testgen/template/profiling/functional_datatype.sql +523 -0
  157. testgen/template/profiling/functional_tabletype_stage.sql +48 -0
  158. testgen/template/profiling/functional_tabletype_update.sql +8 -0
  159. testgen/template/profiling/pii_flag.sql +133 -0
  160. testgen/template/profiling/profile_anomalies_screen_column.sql +22 -0
  161. testgen/template/profiling/profile_anomalies_screen_multi_column.sql +58 -0
  162. testgen/template/profiling/profile_anomalies_screen_table.sql +22 -0
  163. testgen/template/profiling/profile_anomalies_screen_table_dates.sql +30 -0
  164. testgen/template/profiling/profile_anomalies_screen_variants.sql +40 -0
  165. testgen/template/profiling/profile_anomaly_types_get.sql +3 -0
  166. testgen/template/profiling/project_get_table_sample_count.sql +22 -0
  167. testgen/template/profiling/project_profile_run_record_insert.sql +8 -0
  168. testgen/template/profiling/project_profile_run_record_update.sql +5 -0
  169. testgen/template/profiling/project_profile_run_record_update_status.sql +5 -0
  170. testgen/template/profiling/project_update_profile_results_to_estimates.sql +32 -0
  171. testgen/template/profiling/refresh_anomalies.sql +33 -0
  172. testgen/template/profiling/refresh_data_chars_from_profiling.sql +156 -0
  173. testgen/template/profiling/secondary_profiling_columns.sql +12 -0
  174. testgen/template/profiling/secondary_profiling_delete.sql +4 -0
  175. testgen/template/profiling/secondary_profiling_update.sql +18 -0
  176. testgen/template/quick_start/populate_target_data.sql +1077 -0
  177. testgen/template/quick_start/recreate_target_data_schema.sql +167 -0
  178. testgen/template/quick_start/update_target_data.sql +100 -0
  179. testgen/template/updates/create_tmp_test_definition.sql +19 -0
  180. testgen/template/updates/get_test_def_parms.sql +38 -0
  181. testgen/template/updates/populate_stg_test_definitions.sql +184 -0
  182. testgen/template/validate_tests/ex_disable_tests_test_definitions.sql +5 -0
  183. testgen/template/validate_tests/ex_flag_tests_test_definitions.sql +64 -0
  184. testgen/template/validate_tests/ex_get_project_column_list_generic.sql +3 -0
  185. testgen/template/validate_tests/ex_get_test_column_list_tg.sql +65 -0
  186. testgen/template/validate_tests/ex_write_test_val_errors.sql +22 -0
  187. testgen/ui/__init__.py +0 -0
  188. testgen/ui/app.py +98 -0
  189. testgen/ui/assets/dk_logo.svg +46 -0
  190. testgen/ui/assets/question_mark.png +0 -0
  191. testgen/ui/assets/scripts.js +68 -0
  192. testgen/ui/assets/style.css +140 -0
  193. testgen/ui/bootstrap.py +109 -0
  194. testgen/ui/components/__init__.py +0 -0
  195. testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fBBc4.woff2 +0 -0
  196. testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2 +0 -0
  197. testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu4mxK.woff2 +0 -0
  198. testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu7GxKOzY.woff2 +0 -0
  199. testgen/ui/components/frontend/css/material-symbols-rounded.css +24 -0
  200. testgen/ui/components/frontend/css/material-symbols-rounded.woff2 +0 -0
  201. testgen/ui/components/frontend/css/roboto-font-faces.css +35 -0
  202. testgen/ui/components/frontend/css/shared.css +36 -0
  203. testgen/ui/components/frontend/img/dk_logo.svg +46 -0
  204. testgen/ui/components/frontend/index.html +17 -0
  205. testgen/ui/components/frontend/js/components/breadcrumbs.js +86 -0
  206. testgen/ui/components/frontend/js/components/button.js +66 -0
  207. testgen/ui/components/frontend/js/components/location.js +62 -0
  208. testgen/ui/components/frontend/js/components/select.js +75 -0
  209. testgen/ui/components/frontend/js/components/sidebar.js +358 -0
  210. testgen/ui/components/frontend/js/main.js +99 -0
  211. testgen/ui/components/frontend/js/streamlit.js +19 -0
  212. testgen/ui/components/frontend/js/van.min.js +1 -0
  213. testgen/ui/components/utils/__init__.py +0 -0
  214. testgen/ui/components/utils/callbacks.py +51 -0
  215. testgen/ui/components/utils/component.py +13 -0
  216. testgen/ui/components/widgets/__init__.py +6 -0
  217. testgen/ui/components/widgets/breadcrumbs.py +32 -0
  218. testgen/ui/components/widgets/location.py +65 -0
  219. testgen/ui/components/widgets/modal.py +97 -0
  220. testgen/ui/components/widgets/sidebar.py +69 -0
  221. testgen/ui/navigation/__init__.py +0 -0
  222. testgen/ui/navigation/menu.py +42 -0
  223. testgen/ui/navigation/page.py +20 -0
  224. testgen/ui/navigation/router.py +63 -0
  225. testgen/ui/queries/__init__.py +0 -0
  226. testgen/ui/queries/authentication_queries.py +47 -0
  227. testgen/ui/queries/connection_queries.py +121 -0
  228. testgen/ui/queries/profiling_queries.py +148 -0
  229. testgen/ui/queries/project_queries.py +9 -0
  230. testgen/ui/queries/table_group_queries.py +186 -0
  231. testgen/ui/queries/test_definition_queries.py +270 -0
  232. testgen/ui/queries/test_run_queries.py +32 -0
  233. testgen/ui/queries/test_suite_queries.py +145 -0
  234. testgen/ui/scripts/__init__.py +0 -0
  235. testgen/ui/scripts/patch_streamlit.py +111 -0
  236. testgen/ui/services/__init__.py +0 -0
  237. testgen/ui/services/authentication_service.py +119 -0
  238. testgen/ui/services/connection_service.py +220 -0
  239. testgen/ui/services/database_service.py +282 -0
  240. testgen/ui/services/form_service.py +1008 -0
  241. testgen/ui/services/javascript_service.py +44 -0
  242. testgen/ui/services/query_service.py +316 -0
  243. testgen/ui/services/string_service.py +12 -0
  244. testgen/ui/services/table_group_service.py +130 -0
  245. testgen/ui/services/test_definition_service.py +117 -0
  246. testgen/ui/services/test_run_service.py +13 -0
  247. testgen/ui/services/test_suite_service.py +76 -0
  248. testgen/ui/services/toolbar_service.py +77 -0
  249. testgen/ui/session.py +46 -0
  250. testgen/ui/views/__init__.py +0 -0
  251. testgen/ui/views/app_log_modal.py +92 -0
  252. testgen/ui/views/connections.py +72 -0
  253. testgen/ui/views/connections_base.py +367 -0
  254. testgen/ui/views/login.py +40 -0
  255. testgen/ui/views/not_found.py +16 -0
  256. testgen/ui/views/overview.py +34 -0
  257. testgen/ui/views/profiling_anomalies.py +501 -0
  258. testgen/ui/views/profiling_details.py +335 -0
  259. testgen/ui/views/profiling_modal.py +40 -0
  260. testgen/ui/views/profiling_results.py +206 -0
  261. testgen/ui/views/profiling_summary.py +177 -0
  262. testgen/ui/views/project_settings.py +74 -0
  263. testgen/ui/views/table_groups.py +530 -0
  264. testgen/ui/views/test_definitions.py +1020 -0
  265. testgen/ui/views/test_results.py +908 -0
  266. testgen/ui/views/test_runs.py +195 -0
  267. testgen/ui/views/test_suites.py +545 -0
  268. testgen/utils/__init__.py +0 -0
  269. testgen/utils/plugins.py +17 -0
  270. testgen/utils/singleton.py +14 -0
@@ -0,0 +1,530 @@
1
+ import time
2
+ import typing
3
+
4
+ import pandas as pd
5
+ import streamlit as st
6
+
7
+ import testgen.ui.services.authentication_service as authentication_service
8
+ import testgen.ui.services.connection_service as connection_service
9
+ import testgen.ui.services.form_service as fm
10
+ import testgen.ui.services.table_group_service as table_group_service
11
+ import testgen.ui.services.toolbar_service as tb
12
+ from testgen.commands.run_profiling_bridge import run_profiling_in_background
13
+ from testgen.ui.components import widgets as testgen
14
+ from testgen.ui.navigation.page import Page
15
+ from testgen.ui.services.string_service import empty_if_null
16
+ from testgen.ui.session import session
17
+
18
+
19
+ class TableGroupsPage(Page):
20
+ path = "connections/table-groups"
21
+ can_activate: typing.ClassVar = [
22
+ lambda: authentication_service.current_user_has_admin_role() or "overview",
23
+ lambda: session.authentication_status or "login",
24
+ ]
25
+
26
+ def render(self, connection_id: int | None = None) -> None:
27
+ fm.render_page_header(
28
+ "Table Groups",
29
+ "https://docs.datakitchen.io/article/dataops-testgen-help/create-a-table-group",
30
+ lst_breadcrumbs=[
31
+ {"label": "Overview", "path": "overview"},
32
+ {"label": "Connections", "path": "connections"},
33
+ {"label": "Table Groups", "path": None},
34
+ ],
35
+ )
36
+
37
+ # Get page parameters from session
38
+ project_code = st.session_state["project"]
39
+ connection = (
40
+ connection_service.get_by_id(connection_id, hide_passwords=False)
41
+ if connection_id
42
+ else st.session_state["connection"]
43
+ )
44
+ connection_id = connection["connection_id"]
45
+
46
+ tool_bar = tb.ToolBar(1, 5, 0, None)
47
+
48
+ with tool_bar.long_slots[0]:
49
+ st.selectbox("Connection", [connection["connection_name"]], disabled=True)
50
+
51
+ df = table_group_service.get_by_connection(project_code, connection_id)
52
+
53
+ show_columns = [
54
+ "table_groups_name",
55
+ "table_group_schema",
56
+ "profiling_include_mask",
57
+ "profiling_exclude_mask",
58
+ "profiling_table_set",
59
+ "profile_use_sampling",
60
+ "profiling_delay_days",
61
+ ]
62
+
63
+ show_column_headers = [
64
+ "Table Groups Name",
65
+ "DB Schema",
66
+ "Tables to Include Mask",
67
+ "Tables to Exclude Mask",
68
+ "Explicit Table List",
69
+ "Uses Record Sampling",
70
+ "Min Profiling Age (Days)",
71
+ ]
72
+
73
+ selected = fm.render_grid_select(df, show_columns, show_column_headers=show_column_headers)
74
+
75
+ add_modal = testgen.Modal(title=None, key="dk-add-table-group-modal", max_width=1100)
76
+ edit_modal = testgen.Modal(title=None, key="dk-edit-table-group-modal", max_width=1100)
77
+ delete_modal = testgen.Modal(title=None, key="dk-delete-table-group-modal", max_width=1100)
78
+ profile_cli_command_modal = testgen.Modal(
79
+ title=None, key="dk-profiling-cli-command-modal", max_width=1100
80
+ )
81
+ profile_command_modal = testgen.Modal(title=None, key="dk-profiling-command-modal", max_width=1100)
82
+
83
+ if tool_bar.short_slots[1].button(
84
+ "➕ Add", help="Add a new Table Group", use_container_width=True # NOQA RUF001
85
+ ):
86
+ add_modal.open()
87
+
88
+ disable_buttons = selected is None
89
+ if tool_bar.short_slots[2].button(
90
+ "🖊️ Edit", help="Edit the selected Table Group", disabled=disable_buttons, use_container_width=True
91
+ ):
92
+ edit_modal.open()
93
+ if tool_bar.short_slots[3].button(
94
+ "❌ Delete", help="Delete the selected Table Group", disabled=disable_buttons, use_container_width=True
95
+ ):
96
+ delete_modal.open()
97
+ if tool_bar.short_slots[4].button(
98
+ f":{'gray' if disable_buttons else 'green'}[Test Suites →]",
99
+ help="Create or edit Test Suites for the selected Table Group",
100
+ disabled=disable_buttons,
101
+ use_container_width=True,
102
+ ):
103
+ st.session_state["table_group"] = selected[0]
104
+
105
+ session.current_page = "connections/table-groups/test-suites"
106
+ session.current_page_args = {"connection_id": connection_id, "table_group_id": selected[0]["id"]}
107
+ st.experimental_rerun()
108
+
109
+ if add_modal.is_open():
110
+ show_add_or_edit_modal(add_modal, "add", project_code, connection)
111
+
112
+ if edit_modal.is_open():
113
+ show_add_or_edit_modal(edit_modal, "edit", project_code, connection, selected)
114
+
115
+ if delete_modal.is_open():
116
+ show_delete_modal(delete_modal, selected)
117
+
118
+ if profile_cli_command_modal.is_open():
119
+ show_profile_cli_command(profile_cli_command_modal, selected)
120
+
121
+ if profile_command_modal.is_open():
122
+ show_profile_command(profile_command_modal, selected)
123
+
124
+ if not selected:
125
+ st.markdown(":orange[Select a row to see Table Group details.]")
126
+ else:
127
+ show_record_detail(selected[0], profile_cli_command_modal, profile_command_modal)
128
+
129
+
130
+ def show_record_detail(selected, profile_cli_command_modal, profile_command_modal):
131
+ left_column, right_column = st.columns([0.5, 0.5])
132
+
133
+ with left_column:
134
+ fm.render_html_list(
135
+ selected,
136
+ lst_columns=[
137
+ "id",
138
+ "project_code",
139
+ "table_groups_name",
140
+ "table_group_schema",
141
+ "profiling_include_mask",
142
+ "profiling_exclude_mask",
143
+ "profiling_table_set",
144
+ "profile_id_column_mask",
145
+ "profile_sk_column_mask",
146
+
147
+ "data_source",
148
+ "source_system",
149
+ "data_location",
150
+ "business_domain",
151
+ "transform_level",
152
+ "source_process",
153
+ "stakeholder_group",
154
+
155
+ "profile_use_sampling",
156
+ "profile_sample_percent",
157
+ "profile_sample_min_count",
158
+ "profiling_delay_days",
159
+ ],
160
+ str_section_header="Table Group Information",
161
+ int_data_width=700,
162
+ lst_labels=[
163
+ "id",
164
+ "Project",
165
+ "Table Groups Name",
166
+ "Database Schema",
167
+ "Tables to Include Mask",
168
+ "Tables to Exlude Mask",
169
+ "Explicit Table List",
170
+ "ID Column Mask",
171
+ "Surrogate Key Column Mask",
172
+
173
+ "Data Source",
174
+ "Source System",
175
+ "Data Location",
176
+ "Business Domain",
177
+ "Transform Level",
178
+ "Source Process",
179
+ "Stakeholder Group",
180
+
181
+ "Uses Record Sampling",
182
+ "Sample Record Percent",
183
+ "Sample Minimum Record Count",
184
+ "Minimum Profiling Age (Days)",
185
+ ],
186
+ )
187
+
188
+ with right_column:
189
+ st.write("<br/><br/>", unsafe_allow_html=True)
190
+ _, button_column = st.columns([0.3, 0.7])
191
+ with button_column:
192
+ if st.button("Run Profiling", help="Performs profiling on the Table Group", use_container_width=True):
193
+ profile_command_modal.open()
194
+ if st.button(
195
+ "Show Run Profile CLI Command", help="Shows the run-profile CLI command", use_container_width=True
196
+ ):
197
+ profile_cli_command_modal.open()
198
+
199
+
200
+ def show_profile_command(modal, selected):
201
+ selected_table_group = selected[0]
202
+
203
+ with modal.container():
204
+ fm.render_modal_header("Profiling Command", None)
205
+ container = st.empty()
206
+ with container:
207
+ st.markdown(
208
+ ":green[Execute Profile for the Table Group (since can take time, it is performed in background)]"
209
+ )
210
+
211
+ button_container = st.empty()
212
+ status_container = st.empty()
213
+
214
+ with button_container:
215
+ start_process_button_message = "Start"
216
+ profile_button = st.button(start_process_button_message)
217
+
218
+ if profile_button:
219
+ button_container.empty()
220
+
221
+ table_group_id = selected_table_group["id"]
222
+ status_container.info("Executing Profiling...")
223
+
224
+ try:
225
+ run_profiling_in_background(table_group_id)
226
+ except Exception as e:
227
+ status_container.empty()
228
+ status_container.error(f"Process started with errors: {e!s}.")
229
+
230
+ status_container.empty()
231
+ status_container.success(
232
+ "Process has successfully started. Check 'Data Profiling' item in the menu to see the progress."
233
+ )
234
+
235
+
236
+ def show_profile_cli_command(modal, selected):
237
+ with modal.container():
238
+ fm.render_modal_header("Profiling CLI Command", None)
239
+ selected_table_group = selected[0]
240
+ table_group_id = selected_table_group["id"]
241
+ profile_command = f"testgen run-profile --table-group-id {table_group_id}"
242
+ st.code(profile_command, language="shellSession")
243
+
244
+
245
+ def show_delete_modal(modal, selected=None):
246
+ selected_table_group = selected[0]
247
+
248
+ with modal.container():
249
+ fm.render_modal_header("Delete Table Group", None)
250
+ table_group_id = selected_table_group["id"]
251
+ table_group_name = selected_table_group["table_groups_name"]
252
+
253
+ can_be_deleted = table_group_service.cascade_delete([table_group_name], dry_run=True)
254
+
255
+ fm.render_html_list(
256
+ selected_table_group,
257
+ [
258
+ "id",
259
+ "table_groups_name",
260
+ "table_group_schema",
261
+ ],
262
+ "Table Group Information",
263
+ int_data_width=700,
264
+ )
265
+
266
+ if not can_be_deleted:
267
+ st.markdown(
268
+ ":orange[This Table Group has related data, which may include profiling, test definitions and test results. If you proceed, all related data will be permanently deleted.<br/>Are you sure you want to proceed?]",
269
+ unsafe_allow_html=True,
270
+ )
271
+ accept_cascade_delete = st.toggle("I accept deletion of this Table Group and all related TestGen data.")
272
+
273
+ with st.form("Delete Table Group", clear_on_submit=True):
274
+ disable_delete_button = authentication_service.current_user_has_read_role() or (
275
+ not can_be_deleted and not accept_cascade_delete
276
+ )
277
+ delete = st.form_submit_button("Delete", disabled=disable_delete_button)
278
+
279
+ if delete:
280
+ if table_group_service.are_table_groups_in_use([table_group_name]):
281
+ st.error("This Table Group is in use by a running process and cannot be deleted.")
282
+ else:
283
+ table_group_service.cascade_delete([table_group_name])
284
+ success_message = f"Table Group {table_group_name} has been deleted. "
285
+ st.success(success_message)
286
+ time.sleep(1)
287
+ modal.close()
288
+ st.experimental_rerun()
289
+
290
+
291
+ def show_add_or_edit_modal(modal, mode, project_code, connection, selected=None):
292
+ connection_id = connection["connection_id"]
293
+ with modal.container():
294
+ fm.render_modal_header("Edit Table Group" if mode == "edit" else "Add Table Group", None)
295
+ table_groups_settings_tab, table_groups_preview_tab = st.tabs(["Table Group Settings", "Test"])
296
+
297
+ with table_groups_settings_tab:
298
+ selected_table_group = selected[0] if mode == "edit" else None
299
+
300
+ # establish default values
301
+ table_group_id = selected_table_group["id"] if mode == "edit" else None
302
+ table_groups_name = (
303
+ selected_table_group["table_groups_name"]
304
+ if mode == "edit"
305
+ else f'{connection["connection_name"]}_table_group'
306
+ )
307
+ table_group_schema = selected_table_group["table_group_schema"] if mode == "edit" else ""
308
+ profiling_table_set = (
309
+ selected_table_group["profiling_table_set"]
310
+ if mode == "edit" and selected_table_group["profiling_table_set"]
311
+ else ""
312
+ )
313
+ profiling_include_mask = selected_table_group["profiling_include_mask"] if mode == "edit" else "%"
314
+ profiling_exclude_mask = selected_table_group["profiling_exclude_mask"] if mode == "edit" else "tmp%"
315
+ profile_id_column_mask = selected_table_group["profile_id_column_mask"] if mode == "edit" else "%_id"
316
+ profile_sk_column_mask = selected_table_group["profile_sk_column_mask"] if mode == "edit" else "%_sk"
317
+ profile_use_sampling = selected_table_group["profile_use_sampling"] == "Y" if mode == "edit" else False
318
+ profile_sample_percent = int(selected_table_group["profile_sample_percent"]) if mode == "edit" else 30
319
+ profile_sample_min_count = (
320
+ int(selected_table_group["profile_sample_min_count"]) if mode == "edit" else 15000
321
+ )
322
+ profiling_delay_days = int(selected_table_group["profiling_delay_days"]) if mode == "edit" else 0
323
+
324
+ left_column, right_column = st.columns([0.50, 0.50])
325
+
326
+ profile_sampling_expander = st.expander("Sampling Parameters", expanded=False)
327
+ with profile_sampling_expander:
328
+ expander_left_column, expander_right_column = st.columns([0.50, 0.50])
329
+
330
+ provenance_expander = st.expander("Data Provenance (Optional)", expanded=False)
331
+ with provenance_expander:
332
+ provenance_left_column, provenance_right_column = st.columns([0.50, 0.50])
333
+
334
+ with st.form("Table Group Add / Edit", clear_on_submit=True):
335
+ entity = {
336
+ "id": table_group_id,
337
+ "project_code": project_code,
338
+ "connection_id": connection["connection_id"],
339
+ "table_groups_name": left_column.text_input(
340
+ label="Name",
341
+ max_chars=40,
342
+ value=table_groups_name,
343
+ help="A unique name to describe the table group",
344
+ ),
345
+ "profiling_include_mask": left_column.text_input(
346
+ label="Tables to Include Mask",
347
+ max_chars=40,
348
+ value=profiling_include_mask,
349
+ help="A SQL filter supported by your database's LIKE operator for table names to include",
350
+ ),
351
+ "profiling_exclude_mask": left_column.text_input(
352
+ label="Tables to Exclude Mask",
353
+ max_chars=40,
354
+ value=profiling_exclude_mask,
355
+ help="A SQL filter supported by your database's LIKE operator for table names to exclude",
356
+ ),
357
+ "profiling_table_set": left_column.text_input(
358
+ label="Explicit Table List",
359
+ max_chars=2000,
360
+ value=profiling_table_set,
361
+ help="A list of specific table names to include, separated by commas",
362
+ ),
363
+ "table_group_schema": right_column.text_input(
364
+ label="Schema",
365
+ max_chars=40,
366
+ value=table_group_schema,
367
+ help="The database schema containing the tables in the Table Group",
368
+ ),
369
+ "profile_id_column_mask": right_column.text_input(
370
+ label="Profiling ID column mask",
371
+ max_chars=40,
372
+ value=profile_id_column_mask,
373
+ help="A SQL filter supported by your database's LIKE operator representing ID columns (optional)",
374
+ ),
375
+ "profile_sk_column_mask": right_column.text_input(
376
+ label="Profiling Surrogate Key column mask",
377
+ max_chars=40,
378
+ value=profile_sk_column_mask,
379
+ help="A SQL filter supported by your database's LIKE operator representing surrogate key columns (optional)",
380
+ ),
381
+ "profiling_delay_days": right_column.number_input(
382
+ label="Min Profiling Age, Days",
383
+ min_value=0,
384
+ max_value=999,
385
+ value=profiling_delay_days,
386
+ help="The number of days to wait before new profiling will be available to generate tests",
387
+ ),
388
+ "profile_use_sampling": left_column.toggle(
389
+ "Use profile sampling",
390
+ value=profile_use_sampling,
391
+ help="Toggle on to base profiling on a sample of records instead of the full table",
392
+ ),
393
+ "profile_sample_percent": str(
394
+ expander_left_column.number_input(
395
+ label="Sample percent",
396
+ min_value=1,
397
+ max_value=100,
398
+ value=profile_sample_percent,
399
+ help="Percent of records to include in the sample, unless the calculated count falls below the specified minimum.",
400
+ )
401
+ ),
402
+ "profile_sample_min_count": expander_right_column.number_input(
403
+ label="Min Sample Record Count",
404
+ min_value=1,
405
+ max_value=1000000,
406
+ value=profile_sample_min_count,
407
+ help="The minimum number of records to be included in any sample (if available)",
408
+ ),
409
+ "data_source": provenance_left_column.text_input(
410
+ label="Data Source",
411
+ max_chars=40,
412
+ value=empty_if_null(selected_table_group["data_source"]) if mode == "edit" else "",
413
+ help="Original source of all tables in this dataset. This can be overridden at the table level. (Optional)",
414
+ ),
415
+ "source_system": provenance_left_column.text_input(
416
+ label="System of Origin",
417
+ max_chars=40,
418
+ value=empty_if_null(selected_table_group["source_system"]) if mode == "edit" else "",
419
+ help="Enterprise system source for all tables in this dataset. "
420
+ "This can be overridden at the table level. (Optional)",
421
+ ),
422
+ "business_domain": provenance_left_column.text_input(
423
+ label="Business Domain",
424
+ max_chars=40,
425
+ value=empty_if_null(selected_table_group["business_domain"]) if mode == "edit" else "",
426
+ help="Business division responsible for all tables in this dataset. "
427
+ "e.g. Finance, Sales, Manufacturing. (Optional)",
428
+ ),
429
+ "data_location": provenance_left_column.text_input(
430
+ label="Location",
431
+ max_chars=40,
432
+ value=empty_if_null(selected_table_group["data_location"]) if mode == "edit" else "",
433
+ help="Physical or virtual location of all tables in this dataset. "
434
+ "e.g. Headquarters, Cloud, etc. (Optional)",
435
+ ),
436
+ "transform_level": provenance_right_column.text_input(
437
+ label="Transform Level",
438
+ max_chars=40,
439
+ value=empty_if_null(selected_table_group["transform_level"]) if mode == "edit" else "",
440
+ help="Data warehouse processing layer. "
441
+ "Indicates the processing stage: e.g. Raw, Conformed, Processed, Reporting. (Optional)",
442
+ ),
443
+ "source_process": provenance_right_column.text_input(
444
+ label="Source Process",
445
+ max_chars=40,
446
+ value=empty_if_null(selected_table_group["source_process"]) if mode == "edit" else "",
447
+ help="The process, program or data flow that produced this data. (Optional)",
448
+ ),
449
+ "stakeholder_group": provenance_right_column.text_input(
450
+ label="Stakeholder Group",
451
+ max_chars=40,
452
+ value=empty_if_null(selected_table_group["stakeholder_group"]) if mode == "edit" else "",
453
+ help="Designator for data owners or stakeholders who are responsible for this data. (Optional)",
454
+ ),
455
+ }
456
+
457
+ submit_button_text = "Save" if mode == "edit" else "Add"
458
+ submit = st.form_submit_button(
459
+ submit_button_text, disabled=authentication_service.current_user_has_read_role()
460
+ )
461
+
462
+ if submit:
463
+ if mode == "edit":
464
+ table_group_service.edit(entity)
465
+ else:
466
+ table_group_service.add(entity)
467
+ success_message = (
468
+ "Changes have been saved successfully. "
469
+ if mode == "edit"
470
+ else "New Table Group added successfully. "
471
+ )
472
+ st.success(success_message)
473
+ time.sleep(1)
474
+ modal.close()
475
+ st.experimental_rerun()
476
+
477
+ with table_groups_preview_tab:
478
+ if mode == "edit":
479
+ preview_left_column, preview_right_column = st.columns([0.5, 0.5])
480
+ status_preview = preview_right_column.empty()
481
+ preview = preview_left_column.button("Test Table Group")
482
+ if preview:
483
+ table_group_preview(entity, connection_id, project_code, status_preview)
484
+ else:
485
+ st.write("No preview available while adding a Table Group. Save the configuration first.")
486
+
487
+
488
+ def table_group_preview(entity, connection_id, project_code, status):
489
+ status.empty()
490
+ status.info("Connecting to the Table Group ...")
491
+ try:
492
+ table_group_results, qc_results = table_group_service.test_table_group(entity, connection_id, project_code)
493
+ if len(table_group_results) > 0 and all(qc_results):
494
+ tables = set()
495
+ columns = []
496
+ schemas = set()
497
+ for result in table_group_results:
498
+ schemas.add(result["table_schema"])
499
+ tables.add(result["table_name"])
500
+ columns.append(result["column_name"])
501
+
502
+ show_test_results(schemas, tables, columns, qc_results)
503
+
504
+ status.empty()
505
+ status.success("Operation has finished successfully.")
506
+ else:
507
+ status.empty()
508
+ status.error("Operation was unsuccessful.")
509
+ error_message = ""
510
+ if len(table_group_results) == 0:
511
+ error_message = "Result is empty."
512
+ if not all(qc_results):
513
+ error_message = f"Error testing the connection to the Table Group. Details: {qc_results}"
514
+ st.text_area("Table Group Error Details", value=error_message)
515
+ except Exception as e:
516
+ status.empty()
517
+ status.error("Error testing the Table Group.")
518
+ error_message = e.args[0]
519
+ st.text_area("Table Group Error Details", value=error_message)
520
+
521
+
522
+ def show_test_results(schemas, tables, columns, qc_results):
523
+ qc_test_results = all(qc_results)
524
+ st.markdown(f"**Utility QC Schema Validity Test**: {':white_check_mark:' if qc_test_results else ':x:'}")
525
+
526
+ st.markdown(f"**Schema**: {schemas.pop()}")
527
+ st.markdown(f"**Column Count**: {len(columns)}")
528
+
529
+ tables_df = pd.DataFrame({"[tables]": list(tables)})
530
+ fm.render_grid_select(tables_df, ["[tables]"])