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,1020 @@
1
+ import logging
2
+ import time
3
+ import typing
4
+
5
+ import streamlit as st
6
+ from streamlit_extras.no_default_selectbox import selectbox
7
+
8
+ import testgen.ui.services.form_service as fm
9
+ import testgen.ui.services.query_service as dq
10
+ import testgen.ui.services.test_definition_service as test_definition_service
11
+ import testgen.ui.services.toolbar_service as tb
12
+ from testgen.common import date_service
13
+ from testgen.ui.components import widgets as testgen
14
+ from testgen.ui.navigation.menu import MenuItem
15
+ from testgen.ui.navigation.page import Page
16
+ from testgen.ui.services import authentication_service
17
+ from testgen.ui.services.string_service import empty_if_null, snake_case_to_title_case
18
+ from testgen.ui.session import session
19
+ from testgen.ui.views.profiling_modal import view_profiling_modal
20
+
21
+ LOG = logging.getLogger("testgen")
22
+
23
+
24
+ class TestDefinitionsPage(Page):
25
+ path = "tests/definitions"
26
+ can_activate: typing.ClassVar = [
27
+ lambda: session.authentication_status or "login",
28
+ ]
29
+ breadcrumbs: typing.ClassVar = [
30
+ {"label": "Overview", "path": "overview"},
31
+ {"label": "Tests Definitions", "path": None},
32
+ ]
33
+ menu_item = MenuItem(icon="list_alt", label="Tests Definitions", order=4)
34
+
35
+ def render(self, **_) -> None:
36
+ # Get page parameters from session
37
+ project_code = st.session_state["project"]
38
+
39
+ connection = st.session_state["connection"] if "connection" in st.session_state.keys() else None
40
+ table_group = st.session_state["table_group"] if "table_group" in st.session_state.keys() else None
41
+ test_suite = st.session_state["test_suite"] if "test_suite" in st.session_state.keys() else None
42
+
43
+ str_table_name = st.session_state["table_name"] if "table_name" in st.session_state.keys() else None
44
+
45
+ str_column_name = None
46
+
47
+ export_container = fm.render_page_header(
48
+ "Test Definitions",
49
+ "https://docs.datakitchen.io/article/dataops-testgen-help/testgen-test-types",
50
+ lst_breadcrumbs=self.breadcrumbs,
51
+ boo_show_refresh=True,
52
+ )
53
+
54
+ tool_bar = tb.ToolBar(5, 6, 4, None, multiline=True)
55
+
56
+ add_test_definition_modal = testgen.Modal(title=None, key="dk-add-test-definition", max_width=1100)
57
+ edit_test_definition_modal = testgen.Modal(title=None, key="dk-edit-test-definition", max_width=1100)
58
+ delete_test_definition_modal = testgen.Modal(
59
+ title=None, key="dk-delete-test-definition", max_width=1100
60
+ )
61
+
62
+ with tool_bar.long_slots[0]:
63
+ str_connection_id, connection = prompt_for_connection(session.project, connection)
64
+
65
+ # Prompt for Table Group
66
+ with tool_bar.long_slots[1]:
67
+ str_table_groups_id, str_connection_id, str_schema, table_group = prompt_for_table_group(
68
+ session.project, table_group, str_connection_id
69
+ )
70
+
71
+ # Prompt for Test Suite
72
+ if str_table_groups_id:
73
+ with tool_bar.long_slots[2]:
74
+ str_test_suite, test_suite = prompt_for_test_suite(str_table_groups_id, test_suite)
75
+ with tool_bar.long_slots[3]:
76
+ str_table_name = prompt_for_table_name(str_table_groups_id, str_table_name)
77
+ if str_table_name:
78
+ with tool_bar.long_slots[4]:
79
+ str_column_name = prompt_for_column_name(str_table_groups_id, str_table_name)
80
+
81
+ if str_test_suite and str_table_name:
82
+ with tool_bar.short_slots[5]:
83
+ str_help = "Toggle on to perform actions on multiple test definitions"
84
+ do_multi_select = st.toggle("Multi-Select", help=str_help)
85
+
86
+ if tool_bar.short_slots[0].button(
87
+ "➕ Add", help="Add a new Test Definition", use_container_width=True # NOQA RUF001
88
+ ):
89
+ add_test_definition_modal.open()
90
+
91
+ selected = show_test_defs_grid(
92
+ session.project, str_test_suite, str_table_name, str_column_name, do_multi_select, export_container,
93
+ str_table_groups_id
94
+ )
95
+
96
+ # Display buttons
97
+ if tool_bar.button_slots[0].button("✓", help="Activate for future runs", disabled=not selected):
98
+ fm.reset_post_updates(
99
+ update_test_definition(selected, "test_active", True, "Activated"),
100
+ as_toast=True,
101
+ clear_cache=True,
102
+ lst_cached_functions=[],
103
+ )
104
+ if tool_bar.button_slots[1].button("✘", help="Inactivate Test for future runs", disabled=not selected):
105
+ fm.reset_post_updates(
106
+ update_test_definition(selected, "test_active", False, "Inactivated"),
107
+ as_toast=True,
108
+ clear_cache=True,
109
+ lst_cached_functions=[],
110
+ )
111
+ if tool_bar.button_slots[2].button(
112
+ "🔒", help="Protect from future test generation", disabled=not selected
113
+ ):
114
+ fm.reset_post_updates(
115
+ update_test_definition(selected, "lock_refresh", True, "Locked"),
116
+ as_toast=True,
117
+ clear_cache=True,
118
+ lst_cached_functions=[],
119
+ )
120
+ if tool_bar.button_slots[3].button(
121
+ "🔐", help="Unlock for future test generation", disabled=not selected
122
+ ):
123
+ fm.reset_post_updates(
124
+ update_test_definition(selected, "lock_refresh", False, "Unlocked"),
125
+ as_toast=True,
126
+ clear_cache=True,
127
+ lst_cached_functions=[],
128
+ )
129
+
130
+ if tool_bar.short_slots[1].button(
131
+ "🖊️ Edit", # RUF001
132
+ help="Edit the Test Definition",
133
+ use_container_width=True,
134
+ disabled=not selected,
135
+ ):
136
+ edit_test_definition_modal.open()
137
+
138
+ if tool_bar.short_slots[2].button(
139
+ "❌ Delete",
140
+ help="Delete the selected Test Definition",
141
+ use_container_width=True,
142
+ disabled=not selected,
143
+ ):
144
+ delete_test_definition_modal.open()
145
+
146
+ if selected:
147
+ selected_test_def = selected[0]
148
+
149
+ else:
150
+ st.markdown(":orange[Select a Test Suite and Table Name to view Test Definition details.]")
151
+
152
+ # ----- Modal forms
153
+ if add_test_definition_modal.is_open():
154
+ show_add_edit_modal(
155
+ add_test_definition_modal, "add", project_code, table_group, test_suite, str_table_name, str_column_name
156
+ )
157
+
158
+ if edit_test_definition_modal.is_open():
159
+ show_add_edit_modal(
160
+ edit_test_definition_modal,
161
+ "edit",
162
+ project_code,
163
+ table_group,
164
+ test_suite,
165
+ str_table_name,
166
+ str_column_name,
167
+ selected_test_def,
168
+ )
169
+
170
+ if delete_test_definition_modal.is_open():
171
+ show_delete_modal(delete_test_definition_modal, selected_test_def)
172
+
173
+
174
+ class TestDefinitionsPageFromSuite(TestDefinitionsPage):
175
+ path = "connections/table-groups/test-suites/test-definitions"
176
+ breadcrumbs: typing.ClassVar = [
177
+ {"label": "Overview", "path": "overview"},
178
+ {"label": "Connections", "path": "connections"},
179
+ {"label": "Table Groups", "path": "connections/table-groups"},
180
+ {"label": "Test Suites", "path": "connections/table-groups/test-suites"},
181
+ {"label": "Test Definitions", "path": None},
182
+ ]
183
+ menu_item = None
184
+
185
+
186
+ def show_delete_modal(modal, selected_test_definition=None):
187
+ with modal.container():
188
+ fm.render_modal_header("Delete Test", None)
189
+ test_definition_id = selected_test_definition["id"]
190
+ test_name_short = selected_test_definition["test_name_short"]
191
+
192
+ can_be_deleted = test_definition_service.delete([test_definition_id], dry_run=True)
193
+
194
+ fm.render_html_list(
195
+ selected_test_definition,
196
+ [
197
+ "id",
198
+ "project_code",
199
+ "schema_name",
200
+ "table_name",
201
+ "column_name",
202
+ "test_name_short",
203
+ "table_groups_id",
204
+ "test_suite",
205
+ "test_active_display",
206
+ "test_description",
207
+ "last_manual_update",
208
+ ],
209
+ "Test Definition Information",
210
+ int_data_width=700,
211
+ )
212
+
213
+ with st.form("Delete Test Definition", clear_on_submit=True):
214
+ disable_delete_button = authentication_service.current_user_has_read_role() or not can_be_deleted
215
+ delete = st.form_submit_button("Delete", disabled=disable_delete_button)
216
+
217
+ if delete:
218
+ test_definition_service.delete([test_definition_id])
219
+ success_message = f"Test Definition {test_name_short} has been deleted. "
220
+ st.success(success_message)
221
+ time.sleep(1)
222
+ modal.close()
223
+
224
+ if not can_be_deleted:
225
+ st.markdown(":orange[This Test Definition cannot be deleted because it is being used in existing tests.]")
226
+
227
+
228
+ def show_add_edit_modal_by_test_definition(test_definition_modal, test_definition_id):
229
+ selected_test_raw = test_definition_service.get_test_definitions(test_definition_ids=[test_definition_id])
230
+ test_definition = selected_test_raw.iloc[0].to_dict()
231
+
232
+ mode = "edit"
233
+ project_code = test_definition["project_code"]
234
+ table_group_id = test_definition["table_groups_id"]
235
+ test_suite_name = test_definition["test_suite"]
236
+ table_name = test_definition["table_name"]
237
+ column_name = test_definition["column_name"]
238
+
239
+ table_group_raw = run_table_groups_lookup_query(project_code, table_group_id=None)
240
+ table_group = table_group_raw.iloc[0].to_dict()
241
+
242
+ test_suite_raw = run_test_suite_lookup_query(table_group_id, test_suite_name)
243
+ if not test_suite_raw.empty:
244
+ test_suite = test_suite_raw.iloc[0].to_dict()
245
+
246
+ show_add_edit_modal(
247
+ test_definition_modal, mode, project_code, table_group, test_suite, table_name, column_name, test_definition
248
+ )
249
+
250
+
251
+ def show_add_edit_modal(
252
+ test_definition_modal,
253
+ mode,
254
+ project_code,
255
+ table_group,
256
+ test_suite,
257
+ str_table_name,
258
+ str_column_name,
259
+ selected_test_def=None,
260
+ ):
261
+ with test_definition_modal.container():
262
+ fm.render_modal_header("Add Test" if mode == "add" else "Edit Test", None)
263
+ # test_type logic
264
+ if mode == "add":
265
+ selected_test_type, selected_test_type_row = prompt_for_test_type()
266
+ test_type = selected_test_type
267
+ else:
268
+ test_type = selected_test_def["test_type"]
269
+ df = run_test_type_lookup_query()
270
+ selected_test_type_row = df[df["test_type"] == test_type].iloc[0]
271
+ test_type_display = selected_test_type_row["test_name_short"]
272
+
273
+ if selected_test_type_row is None:
274
+ return
275
+
276
+ # run type
277
+ run_type = selected_test_type_row["run_type"] # Can be "QUERY" or "CAT"
278
+ test_scope = selected_test_type_row["test_scope"] # Can be "column", "table", "referential", "custom"
279
+
280
+ # test_description
281
+ test_description = empty_if_null(selected_test_def["test_description"]) if mode == "edit" else ""
282
+ test_type_test_description = selected_test_type_row["test_description"]
283
+ test_description_help = (
284
+ "You may enter a description here to override the default description above for the Test Type."
285
+ )
286
+ test_description_placeholder = f"Inherited ({test_type_test_description})"
287
+
288
+ # severity
289
+ test_suite_severity = test_suite["severity"]
290
+ test_types_severity = selected_test_type_row["default_severity"]
291
+ inherited_severity = test_suite_severity if test_suite_severity else test_types_severity
292
+
293
+ severity_options = [f"Inherited ({inherited_severity})", "Warning", "Fail"]
294
+ if mode == "add" or selected_test_def["severity"] is None:
295
+ severity_index = 0
296
+ else:
297
+ severity_index = severity_options.index(selected_test_def["severity"])
298
+
299
+ # general value parsing
300
+ entity_id = selected_test_def["id"] if mode == "edit" else ""
301
+ cat_test_id = selected_test_def["cat_test_id"] if mode == "edit" else ""
302
+ project_code = selected_test_def["project_code"] if mode == "edit" else project_code
303
+ table_groups_id = selected_test_def["table_groups_id"] if mode == "edit" else table_group["id"]
304
+ profile_run_id = selected_test_def["profile_run_id"] if mode == "edit" else ""
305
+ test_suite_name = selected_test_def["test_suite"] if mode == "edit" else test_suite["test_suite"]
306
+ test_suite_id = test_suite["id"]
307
+ test_action = empty_if_null(selected_test_def["test_action"]) if mode == "edit" else ""
308
+ schema_name = selected_test_def["schema_name"] if mode == "edit" else table_group["table_group_schema"]
309
+ table_name = empty_if_null(selected_test_def["table_name"]) if mode == "edit" else empty_if_null(str_table_name)
310
+ skip_errors = selected_test_def["skip_errors"] if mode == "edit" else 0
311
+ test_active = selected_test_def["test_active"] == "Y" if mode == "edit" else True
312
+ lock_refresh = selected_test_def["lock_refresh"] == "Y" if mode == "edit" else False
313
+ test_definition_status = selected_test_def["test_definition_status"] if mode == "edit" else ""
314
+ check_result = selected_test_def["check_result"] if mode == "edit" else None
315
+ column_name = empty_if_null(selected_test_def["column_name"]) if mode == "edit" else ""
316
+
317
+ # dynamic attributes
318
+ custom_query = empty_if_null(selected_test_def["custom_query"]) if mode == "edit" else ""
319
+ baseline_ct = empty_if_null(selected_test_def["baseline_ct"]) if mode == "edit" else ""
320
+ baseline_unique_ct = empty_if_null(selected_test_def["baseline_unique_ct"]) if mode == "edit" else ""
321
+ baseline_value = empty_if_null(selected_test_def["baseline_value"]) if mode == "edit" else ""
322
+ baseline_value_ct = empty_if_null(selected_test_def["baseline_value_ct"]) if mode == "edit" else ""
323
+ threshold_value = empty_if_null(selected_test_def["threshold_value"]) if mode == "edit" else 0
324
+ baseline_sum = empty_if_null(selected_test_def["baseline_sum"]) if mode == "edit" else ""
325
+ baseline_avg = empty_if_null(selected_test_def["baseline_avg"]) if mode == "edit" else ""
326
+ baseline_sd = empty_if_null(selected_test_def["baseline_sd"]) if mode == "edit" else ""
327
+ subset_condition = empty_if_null(selected_test_def["subset_condition"]) if mode == "edit" else ""
328
+ groupby_names = empty_if_null(selected_test_def["groupby_names"]) if mode == "edit" else ""
329
+ having_condition = empty_if_null(selected_test_def["having_condition"]) if mode == "edit" else ""
330
+ window_date_column = empty_if_null(selected_test_def["window_date_column"]) if mode == "edit" else ""
331
+ match_schema_name = empty_if_null(selected_test_def["match_schema_name"]) if mode == "edit" else ""
332
+ match_table_name = empty_if_null(selected_test_def["match_table_name"]) if mode == "edit" else ""
333
+ match_column_names = empty_if_null(selected_test_def["match_column_names"]) if mode == "edit" else ""
334
+ match_subset_condition = empty_if_null(selected_test_def["match_subset_condition"]) if mode == "edit" else ""
335
+ match_groupby_names = empty_if_null(selected_test_def["match_groupby_names"]) if mode == "edit" else ""
336
+ match_having_condition = empty_if_null(selected_test_def["match_having_condition"]) if mode == "edit" else ""
337
+ window_days = selected_test_def["window_days"] if mode == "edit" and selected_test_def["window_days"] else 0
338
+ test_mode = empty_if_null(selected_test_def["test_mode"]) if mode == "edit" else ""
339
+
340
+ # export_to_observability
341
+ test_suite_export_to_observability = test_suite["export_to_observability"]
342
+ inherited_export_to_observability = "Yes" if test_suite_export_to_observability == "Y" else "No"
343
+
344
+ inherited_legend = f"Inherited ({inherited_export_to_observability})"
345
+ export_to_observability_options = [inherited_legend, "Yes", "No"]
346
+ if mode == "edit":
347
+ match selected_test_def["export_to_observability_raw"]:
348
+ case "N":
349
+ export_to_observability = "No"
350
+ case "Y":
351
+ export_to_observability = "Yes"
352
+ case _:
353
+ export_to_observability = inherited_legend
354
+ else:
355
+ export_to_observability = inherited_legend
356
+ export_to_observability_index = export_to_observability_options.index(export_to_observability)
357
+
358
+ # watch_level
359
+ watch_level = selected_test_def["watch_level"] if mode == "edit" else "WARN"
360
+
361
+ # dynamic attributes
362
+ dynamic_attributes_raw = selected_test_type_row["default_parm_columns"]
363
+ dynamic_attributes = dynamic_attributes_raw.split(",")
364
+
365
+ dynamic_attributes_labels_raw = selected_test_type_row["default_parm_prompts"]
366
+ dynamic_attributes_labels = dynamic_attributes_labels_raw.split(",")
367
+
368
+ dynamic_attributes_help_raw = selected_test_type_row["default_parm_help"]
369
+ if not dynamic_attributes_help_raw:
370
+ dynamic_attributes_help_raw = "No help is available"
371
+ # Split on pipe -- could contain commas
372
+ dynamic_attributes_help = dynamic_attributes_help_raw.split("|")
373
+
374
+ if mode == "edit":
375
+ st.text_input(label="Test Type", value=test_type_display, disabled=True),
376
+
377
+ # Using the test_type, display the default description and usage_notes
378
+ if selected_test_type_row["test_description"]:
379
+ st.markdown(
380
+ f"""
381
+ <div style="border: 1px solid #e6e6e6; border-radius: 5px; padding: 10px;">
382
+ {selected_test_type_row['test_description']}
383
+ </div><br/>
384
+ """,
385
+ unsafe_allow_html=True,
386
+ )
387
+
388
+ if selected_test_type_row["usage_notes"]:
389
+ st.info(f"**Usage Notes:**\n\n{selected_test_type_row['usage_notes']}")
390
+
391
+ left_column, right_column = st.columns([0.5, 0.5])
392
+
393
+ test_definition = {
394
+ "id": entity_id,
395
+ "cat_test_id": cat_test_id,
396
+ "watch_level": watch_level,
397
+ "project_code": project_code,
398
+ "table_groups_id": table_groups_id,
399
+ "profile_run_id": profile_run_id,
400
+ "test_type": test_type,
401
+ "test_suite": left_column.text_input(
402
+ label="Test Suite Name", max_chars=200, value=test_suite_name, disabled=True
403
+ ),
404
+ "test_suite_id": test_suite_id,
405
+ "test_description": left_column.text_area(
406
+ label="Test Description Override",
407
+ max_chars=1000,
408
+ height=3,
409
+ placeholder=test_description_placeholder,
410
+ value=test_description,
411
+ help=test_description_help,
412
+ ),
413
+ "test_action": test_action,
414
+ "test_mode": test_mode,
415
+ "lock_refresh": left_column.toggle(
416
+ label="Lock Refresh",
417
+ value=lock_refresh,
418
+ help="Protects test parameters from being overwritten when tests in this Test Suite are regenerated.",
419
+ ),
420
+ "schema_name": right_column.text_input(
421
+ label="Schema Name", max_chars=100, value=schema_name, disabled=True
422
+ ),
423
+ "test_active": left_column.toggle(label="Test Active", value=test_active),
424
+ "check_result": check_result,
425
+ "custom_query": custom_query,
426
+ "baseline_ct": baseline_ct,
427
+ "baseline_unique_ct": baseline_unique_ct,
428
+ "baseline_value": baseline_value,
429
+ "baseline_value_ct": baseline_value_ct,
430
+ "threshold_value": threshold_value,
431
+ "baseline_sum": baseline_sum,
432
+ "baseline_avg": baseline_avg,
433
+ "baseline_sd": baseline_sd,
434
+ "subset_condition": subset_condition,
435
+ "groupby_names": groupby_names,
436
+ "having_condition": having_condition,
437
+ "window_date_column": window_date_column,
438
+ "match_schema_name": match_schema_name,
439
+ "match_table_name": match_table_name,
440
+ "column_name": column_name,
441
+ "match_column_names": match_column_names,
442
+ "match_subset_condition": match_subset_condition,
443
+ "match_groupby_names": match_groupby_names,
444
+ "match_having_condition": match_having_condition,
445
+ "window_days": window_days,
446
+ }
447
+
448
+ # test_definition_status
449
+ test_definition["test_definition_status"] = test_definition_status
450
+ if mode == "edit":
451
+ test_definition_status_display = test_definition_status if test_definition_status else "OK"
452
+ left_column.text_input(
453
+ label="Validation Status", max_chars=200, value=test_definition_status_display, disabled=True
454
+ )
455
+
456
+ # export_to_observability
457
+ export_to_observability_help = "Send results to DataKitchen Observability - overrides Test Suite toggle"
458
+ test_definition["export_to_observability_raw"] = right_column.selectbox(
459
+ label="Send to Observability - Override",
460
+ options=export_to_observability_options,
461
+ index=export_to_observability_index,
462
+ help=export_to_observability_help,
463
+ )
464
+
465
+ # severity
466
+ severity_help = "Urgency is defined by default for the Test Type, but can be overridden for all tests in the Test Suite, and ultimately here for each individual test."
467
+ test_definition["severity"] = right_column.selectbox(
468
+ label="Urgency Override",
469
+ options=severity_options,
470
+ index=severity_index,
471
+ help=severity_help,
472
+ )
473
+
474
+ st.divider()
475
+
476
+ # table_name
477
+ test_definition["table_name"] = st.text_input(
478
+ label="Table Name", max_chars=100, value=table_name, disabled=False
479
+ )
480
+
481
+ # column_name
482
+ if selected_test_type_row["column_name_prompt"]:
483
+ column_name_label = selected_test_type_row["column_name_prompt"]
484
+ else:
485
+ column_name_label = "Test Focus"
486
+ if selected_test_type_row["column_name_help"]:
487
+ column_name_help = selected_test_type_row["column_name_help"]
488
+ else:
489
+ column_name_help = "Help is not available"
490
+
491
+ if test_scope == "table":
492
+ test_definition["column_name"] = None
493
+ column_name_label = None
494
+ elif test_scope == "referential":
495
+ column_name_disabled = False
496
+ test_definition["column_name"] = st.text_input(
497
+ label=column_name_label,
498
+ value=column_name,
499
+ max_chars=500,
500
+ help=column_name_help,
501
+ disabled=column_name_disabled,
502
+ )
503
+ elif test_scope == "custom":
504
+ if str_column_name:
505
+ if mode == "add": # query add present
506
+ column_name_disabled = False
507
+ column_name = str_column_name
508
+ else: # query edit present
509
+ column_name_disabled = False
510
+ column_name = str_column_name
511
+ else:
512
+ if mode == "add": # query add not-present
513
+ column_name_disabled = False
514
+ else: # query edit not-present
515
+ column_name_disabled = False
516
+
517
+ test_definition["column_name"] = st.text_input(
518
+ label=column_name_label,
519
+ value=column_name,
520
+ max_chars=100,
521
+ help=column_name_help,
522
+ disabled=column_name_disabled,
523
+ )
524
+ elif test_scope == "column": # CAT column test
525
+ if str_column_name:
526
+ column_name_disabled = True
527
+ if mode == "add":
528
+ column_name = str_column_name # CAT add present
529
+ else:
530
+ pass # CAT edit present
531
+ else:
532
+ column_name_disabled = False
533
+ if mode == "add":
534
+ pass # CAT add not-present
535
+ else:
536
+ pass # CAT edit not-present
537
+
538
+ column_name_label = "Column Name"
539
+ column_name_options = get_column_names(table_groups_id, test_definition["table_name"])
540
+ column_name_help = "Select the column to test"
541
+ column_name_index = column_name_options.index(column_name) if column_name else 0
542
+ test_definition["column_name"] = st.selectbox(
543
+ label=column_name_label,
544
+ options=column_name_options,
545
+ index=column_name_index,
546
+ help=column_name_help,
547
+ key="column-name-form",
548
+ disabled=column_name_disabled,
549
+ )
550
+
551
+ st.divider()
552
+
553
+ # dynamic attributes
554
+ mid_left_column, mid_right_column = st.columns([0.5, 0.5])
555
+
556
+ current_column = mid_left_column
557
+ show_custom_query = False
558
+ dynamic_attributes_length = len(dynamic_attributes)
559
+ dynamic_attributes_half_length = max(round((dynamic_attributes_length + 0.5) / 2), 1)
560
+ for i, dynamic_attribute in enumerate(dynamic_attributes):
561
+ if i >= dynamic_attributes_half_length:
562
+ current_column = mid_right_column
563
+
564
+ value = empty_if_null(selected_test_def[dynamic_attribute]) if mode == "edit" else ""
565
+
566
+ actual_dynamic_attributes_labels = (
567
+ dynamic_attributes_labels[i]
568
+ if dynamic_attributes_labels and len(dynamic_attributes_labels) > i
569
+ else "Help text is not available."
570
+ )
571
+
572
+ actual_dynamic_attributes_help = (
573
+ dynamic_attributes_help[i]
574
+ if dynamic_attributes_help and len(dynamic_attributes_help) > i
575
+ else snake_case_to_title_case(dynamic_attribute)
576
+ )
577
+
578
+ if dynamic_attribute in ["custom_query"]:
579
+ show_custom_query = True
580
+ else:
581
+ test_definition[dynamic_attribute] = current_column.text_input(
582
+ label=actual_dynamic_attributes_labels,
583
+ max_chars=4000 if dynamic_attribute in ["match_column_names", "match_groupby_names", "groupby_names"] else 1000,
584
+ value=value,
585
+ help=actual_dynamic_attributes_help,
586
+ )
587
+
588
+ # Custom Query
589
+ if show_custom_query:
590
+ if test_type == "Condition_Flag":
591
+ custom_query_default = "EXAMPLE: status = 'SHIPPED' and qty_shipped = 0"
592
+ custom_query_height = 75
593
+ elif test_type == "CUSTOM":
594
+ custom_query_default = "EXAMPLE: SELECT product, SUM(qty_sold) as sum_sold, SUM(qty_shipped) as qty_shipped \n FROM {DATA_SCHEMA}.sales_history \n GROUP BY product \n HAVING SUM(qty_shipped) > SUM(qty_sold)"
595
+ custom_query_height = 150
596
+ else:
597
+ custom_query_default = None
598
+ custom_query_height = 75
599
+ test_definition["custom_query"] = st.text_area(
600
+ label=actual_dynamic_attributes_labels,
601
+ value=custom_query,
602
+ placeholder=custom_query_default,
603
+ height=custom_query_height,
604
+ help=actual_dynamic_attributes_help,
605
+ )
606
+
607
+ # skip_errors
608
+ if run_type == "QUERY":
609
+ test_definition["skip_errors"] = left_column.number_input(label="Threshold Error Count", value=skip_errors)
610
+ else:
611
+ test_definition["skip_errors"] = skip_errors
612
+
613
+ # submit logic
614
+ bottom_left_column, bottom_right_column = st.columns([0.5, 0.5])
615
+
616
+ # Add Validate button
617
+ if test_type in ("Condition_Flag", "CUSTOM"):
618
+ validate = bottom_left_column.button(
619
+ "Validate", disabled=authentication_service.current_user_has_read_role()
620
+ )
621
+ if validate:
622
+ try:
623
+ test_definition_service.validate_test(test_definition)
624
+ bottom_right_column.success("Validation is successful.")
625
+ except Exception as e:
626
+ bottom_right_column.error(f"Test validation failed with error: {e}")
627
+
628
+ submit = bottom_left_column.button("Save", disabled=authentication_service.current_user_has_read_role())
629
+
630
+ if submit:
631
+ if validate_form(test_scope, test_type, test_definition, column_name_label):
632
+ if mode == "edit":
633
+ test_definition_service.update(test_definition)
634
+ test_definition_modal.close()
635
+ else:
636
+ test_definition_service.add(test_definition)
637
+ test_definition_modal.close()
638
+
639
+
640
+ def validate_form(test_scope, test_type, test_definition, column_name_label):
641
+ if test_type == "Condition_Flag" and not test_definition["threshold_value"]:
642
+ st.error("Threshold Error Count is a required field.")
643
+ return False
644
+ if not test_definition["test_type"]:
645
+ st.error("Test Type is a required field.")
646
+ return False
647
+ if test_scope in ["column", "referential", "custom"] and not test_definition["column_name"]:
648
+ st.error(f"{column_name_label} is a required field.")
649
+ return False
650
+ return True
651
+
652
+
653
+ def validate_test_definition_uniqueness(test_definition, test_scope):
654
+ record_count = test_definition_service.check_test_definition_uniqueness(test_definition)
655
+ if record_count > 0:
656
+ match test_scope:
657
+ case "column":
658
+ message_bit = "and Column Name "
659
+ case "referential":
660
+ message_bit = "and Column Names "
661
+ case "custom":
662
+ message_bit = "and Test Focus "
663
+ case "table":
664
+ message_bit = ""
665
+ case _:
666
+ message_bit = ""
667
+
668
+ return f"Validation error: the combination of Table Name, Test Type {message_bit}must be unique within a Test Suite"
669
+
670
+
671
+ def prompt_for_test_type():
672
+
673
+ col0, col1, col2, col3, col4, col5 = st.columns([0.1, 0.2, 0.2, 0.2, 0.2, 0.1])
674
+ col0.write("Show Types")
675
+ boo_show_referential = col1.checkbox(":green[⧉] Referential", True)
676
+ boo_show_table = col2.checkbox(":green[⊞] Table", True)
677
+ boo_show_column = col3.checkbox(":green[≣] Column", True)
678
+ boo_show_custom = col4.checkbox(":green[⛭] Custom", True)
679
+
680
+ df = run_test_type_lookup_query(str_test_type=None, boo_show_referential=boo_show_referential,
681
+ boo_show_table=boo_show_table, boo_show_column=boo_show_column,
682
+ boo_show_custom=boo_show_custom)
683
+ lst_choices = ["(Select a Test Type)", *df["select_name"].tolist()]
684
+
685
+ str_selected = selectbox("Test Type", lst_choices)
686
+ if str_selected:
687
+ row_selected = df[df["test_name_short"] == str_selected.split(":", 1)[0][2:]].iloc[0]
688
+ str_value = row_selected["test_type"]
689
+ else:
690
+ str_value = None
691
+ row_selected = None
692
+ return str_value, row_selected
693
+
694
+
695
+ def update_test_definition(selected, attribute, value, message):
696
+ result = None
697
+ test_definition_ids = [row["id"] for row in selected if "id" in row]
698
+ test_definition_service.update_attribute(test_definition_ids, attribute, value)
699
+ st.success(message)
700
+ return result
701
+
702
+
703
+ def show_test_defs_grid(
704
+ str_project_code, str_test_suite, str_table_name, str_column_name, do_multi_select, export_container,
705
+ str_table_groups_id
706
+ ):
707
+ df = test_definition_service.get_test_definitions(
708
+ str_project_code, str_test_suite, str_table_name, str_column_name
709
+ )
710
+ date_service.accommodate_dataframe_to_timezone(df, st.session_state)
711
+
712
+ for col in df.select_dtypes(include=["datetime"]).columns:
713
+ df[col] = df[col].astype(str).replace("NaT", "")
714
+
715
+ lst_show_columns = [
716
+ "schema_name",
717
+ "table_name",
718
+ "column_name",
719
+ "test_name_short",
720
+ "test_active_display",
721
+ "lock_refresh_display",
722
+ "urgency",
723
+ "export_to_observability",
724
+ "profiling_as_of_date",
725
+ "last_manual_update",
726
+ ]
727
+ show_column_headers = [
728
+ "Schema",
729
+ "Table",
730
+ "Columns / Focus",
731
+ "Test Name",
732
+ "Active",
733
+ "Locked",
734
+ "Urgency",
735
+ "Export to Observabilty",
736
+ "Based on Profiling",
737
+ "Last Manual Update",
738
+ ]
739
+
740
+ # show_column_headers = list(map(snake_case_to_title_case, show_column_headers))
741
+
742
+ dct_selected_row = fm.render_grid_select(
743
+ df,
744
+ lst_show_columns,
745
+ do_multi_select=do_multi_select,
746
+ show_column_headers=show_column_headers,
747
+ render_highlights=False,
748
+ )
749
+
750
+ with export_container:
751
+ lst_export_columns = [
752
+ "schema_name",
753
+ "table_name",
754
+ "column_name",
755
+ "test_name_short",
756
+ "final_test_description",
757
+ "threshold_value",
758
+ "export_uom",
759
+ "test_active_display",
760
+ "lock_refresh_display",
761
+ "urgency",
762
+ "profiling_as_of_date",
763
+ "last_manual_update",
764
+ ]
765
+ lst_wrap_columns = ["final_test_description"]
766
+ lst_export_headers = [
767
+ "Schema",
768
+ "Table Name",
769
+ "Column/Test Focus",
770
+ "Test Type",
771
+ "Description",
772
+ "Test Threshold",
773
+ "Unit of Measure",
774
+ "Active",
775
+ "Locked",
776
+ "Urgency",
777
+ "From Profiling As-Of",
778
+ "Last Manual Update",
779
+ ]
780
+ fm.render_excel_export(
781
+ df,
782
+ lst_export_columns,
783
+ f"Test Definitions for Test Suite {str_test_suite}",
784
+ "{TIMESTAMP}",
785
+ lst_wrap_columns,
786
+ lst_export_headers,
787
+ )
788
+
789
+ if dct_selected_row:
790
+ st.markdown("</p>&nbsp;</br>", unsafe_allow_html=True)
791
+ selected_row = dct_selected_row[0]
792
+ str_test_id = selected_row["id"]
793
+ row_selected = df[df["id"] == str_test_id].iloc[0]
794
+ str_parm_columns = selected_row["default_parm_columns"]
795
+
796
+ # Shared columns to show
797
+ lst_show_columns = [
798
+ "schema_name",
799
+ "table_name",
800
+ "column_name",
801
+ "test_type",
802
+ "test_active_display",
803
+ "test_definition_status",
804
+ "lock_refresh_display",
805
+ "urgency",
806
+ "export_to_observability",
807
+ ]
808
+
809
+ labels = [
810
+ "schema_name",
811
+ "table_name",
812
+ "column_name",
813
+ "test_type",
814
+ "test_active",
815
+ "test_definition_status",
816
+ "lock_refresh",
817
+ "urgency",
818
+ "export_to_observability",
819
+ ]
820
+
821
+ # Test-specific columns to show
822
+ additional_columns = [val.strip() for val in str_parm_columns.split(",")]
823
+ lst_show_columns = lst_show_columns + additional_columns
824
+ labels = labels + additional_columns
825
+
826
+ labels = list(map(snake_case_to_title_case, labels))
827
+
828
+ left_column, right_column = st.columns([0.5, 0.5])
829
+
830
+ with left_column:
831
+ fm.render_html_list(
832
+ selected_row,
833
+ lst_show_columns,
834
+ "Test Definition Information",
835
+ int_data_width=700,
836
+ lst_labels=labels,
837
+ )
838
+
839
+ _, col_profile_button = right_column.columns([0.7, 0.3])
840
+ if selected_row["test_scope"] == "column":
841
+ view_profiling_modal(
842
+ col_profile_button, selected_row["table_name"], selected_row["column_name"],
843
+ str_table_groups_id=str_table_groups_id
844
+ )
845
+
846
+ with right_column:
847
+ st.write(generate_test_defs_help(row_selected["test_type"]))
848
+
849
+ return dct_selected_row
850
+
851
+
852
+ def generate_test_defs_help(str_test_type):
853
+ df = run_test_type_lookup_query(str_test_type)
854
+ if not df.empty:
855
+ row = df.iloc[0]
856
+
857
+ str_help = f"""
858
+ ##### {row["test_name_short"]}
859
+ {row["test_description"]}
860
+
861
+ **Measure UOM:** {row["measure_uom"]}
862
+
863
+ {row["measure_uom_description"]}
864
+
865
+ **Threshold:** {row["threshold_description"]}
866
+
867
+ **Default Test Severity:** {row["default_severity"]}
868
+
869
+ **Test Run Type:** {row["test_scope"]}
870
+ - COLUMN tests are consolidated into aggregate queries and execute faster.
871
+ - TABLE, REFERENTIAL and CUSTOM tests are executed individually and may take longer to run.
872
+
873
+ **Data Quality Dimension:** {row["dq_dimension"]}
874
+ """
875
+ else:
876
+ str_help = ""
877
+ return str_help
878
+
879
+
880
+ @st.cache_data(show_spinner=False)
881
+ def run_project_lookup_query():
882
+ str_schema = st.session_state["dbschema"]
883
+ return dq.run_project_lookup_query(str_schema)
884
+
885
+
886
+ @st.cache_data(show_spinner=False)
887
+ def run_test_type_lookup_query(str_test_type=None, boo_show_referential=True, boo_show_table=True,
888
+ boo_show_column=True, boo_show_custom=True):
889
+ str_schema = st.session_state["dbschema"]
890
+ return dq.run_test_type_lookup_query(str_schema, str_test_type, boo_show_referential, boo_show_table,
891
+ boo_show_column, boo_show_custom)
892
+
893
+
894
+ @st.cache_data(show_spinner=False)
895
+ def run_connections_lookup_query(str_project_code):
896
+ str_schema = st.session_state["dbschema"]
897
+ return dq.run_connections_lookup_query(str_schema, str_project_code)
898
+
899
+
900
+ @st.cache_data(show_spinner=False)
901
+ def run_table_groups_lookup_query(str_project_code, str_connection_id=None, table_group_id=None):
902
+ str_schema = st.session_state["dbschema"]
903
+ return dq.run_table_groups_lookup_query(str_schema, str_project_code, str_connection_id, table_group_id)
904
+
905
+
906
+ @st.cache_data(show_spinner=False)
907
+ def run_table_lookup_query(str_table_groups_id):
908
+ str_schema = st.session_state["dbschema"]
909
+ return dq.run_table_lookup_query(str_schema, str_table_groups_id)
910
+
911
+
912
+ @st.cache_data(show_spinner=False)
913
+ def run_column_lookup_query(str_table_groups_id, str_table_name):
914
+ str_schema = st.session_state["dbschema"]
915
+ return dq.run_column_lookup_query(str_schema, str_table_groups_id, str_table_name)
916
+
917
+
918
+ @st.cache_data(show_spinner=False)
919
+ def run_test_suite_lookup_query(str_table_groups_id, test_suite_name=None):
920
+ str_schema = st.session_state["dbschema"]
921
+ return dq.run_test_suite_lookup_by_tgroup_query(str_schema, str_table_groups_id, test_suite_name)
922
+
923
+
924
+ def prompt_for_connection(str_project_code, selected_connection):
925
+ str_id = None
926
+
927
+ df = run_connections_lookup_query(str_project_code)
928
+ lst_choices = df["connection_name"].tolist()
929
+
930
+ if selected_connection:
931
+ connection_name = selected_connection["connection_name"]
932
+ selected_connection_index = lst_choices.index(connection_name)
933
+ else:
934
+ selected_connection_index = 0
935
+
936
+ str_name = st.selectbox("Connection", lst_choices, index=selected_connection_index)
937
+ if str_name:
938
+ str_id = df.loc[df["connection_name"] == str_name, "id"].iloc[0]
939
+ connection = df.loc[df["connection_name"] == str_name].iloc[0]
940
+ return str_id, connection
941
+
942
+
943
+ def prompt_for_table_group(str_project_code, selected_table_group, str_connection_id):
944
+ str_id = None
945
+ str_schema = None
946
+ table_group = None
947
+
948
+ df = run_table_groups_lookup_query(str_project_code, str_connection_id)
949
+ lst_choices = df["table_groups_name"].tolist()
950
+
951
+ table_group_name = None
952
+ if selected_table_group:
953
+ table_group_name = selected_table_group["table_groups_name"]
954
+
955
+ if table_group_name and table_group_name in lst_choices:
956
+ selected_table_group_index = lst_choices.index(table_group_name)
957
+ else:
958
+ selected_table_group_index = 0
959
+
960
+ str_name = st.selectbox("Table Group", lst_choices, index=selected_table_group_index)
961
+ if str_name:
962
+ str_id = df.loc[df["table_groups_name"] == str_name, "id"].iloc[0]
963
+ str_connection_id = df.loc[df["table_groups_name"] == str_name, "connection_id"].iloc[0]
964
+ str_schema = df.loc[df["table_groups_name"] == str_name, "table_group_schema"].iloc[0]
965
+ table_group = df.loc[df["table_groups_name"] == str_name].iloc[0]
966
+ return str_id, str_connection_id, str_schema, table_group
967
+
968
+
969
+ def prompt_for_test_suite(str_table_groups_id, selected_test_suite):
970
+ df = run_test_suite_lookup_query(str_table_groups_id)
971
+ lst_choices = df["test_suite"].tolist()
972
+
973
+ test_suite = None
974
+ test_suite_name = None
975
+ if selected_test_suite:
976
+ test_suite_name = selected_test_suite["test_suite"]
977
+
978
+ if test_suite_name and test_suite_name in lst_choices:
979
+ test_suite_index = lst_choices.index(test_suite_name)
980
+ else:
981
+ test_suite_index = 0
982
+
983
+ str_name = st.selectbox("Test Suite", lst_choices, index=test_suite_index)
984
+ if str_name:
985
+ test_suite = df.loc[df["test_suite"] == str_name].iloc[0]
986
+
987
+ return str_name, test_suite
988
+
989
+
990
+ def prompt_for_table_name(str_table_groups_id, selected_table_name):
991
+ df = run_table_lookup_query(str_table_groups_id)
992
+ lst_choices = df["table_name"].tolist()
993
+
994
+ if selected_table_name and selected_table_name in lst_choices:
995
+ table_name_index = lst_choices.index(selected_table_name) + 1
996
+ else:
997
+ table_name_index = 0
998
+
999
+ def table_name_callback():
1000
+ st.session_state["table_name"] = st.session_state.new_table_name
1001
+
1002
+ str_name = selectbox(
1003
+ "Table Name", lst_choices, index=table_name_index, key="new_table_name", on_change=table_name_callback
1004
+ )
1005
+
1006
+ return str_name
1007
+
1008
+
1009
+ def prompt_for_column_name(str_table_groups_id, str_table_name):
1010
+ lst_choices = get_column_names(str_table_groups_id, str_table_name)
1011
+ # Using extras selectbox to allow no entry
1012
+ str_name = selectbox("Column Name", lst_choices, key="column-name-main-drop-down")
1013
+
1014
+ return str_name
1015
+
1016
+
1017
+ def get_column_names(str_table_groups_id, str_table_name):
1018
+ df = run_column_lookup_query(str_table_groups_id, str_table_name)
1019
+ lst_choices = df["column_name"].tolist()
1020
+ return lst_choices