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.
- dataops_testgen-2.2.0.dist-info/LICENSE +203 -0
- dataops_testgen-2.2.0.dist-info/METADATA +287 -0
- dataops_testgen-2.2.0.dist-info/NOTICE +5 -0
- dataops_testgen-2.2.0.dist-info/RECORD +270 -0
- dataops_testgen-2.2.0.dist-info/WHEEL +5 -0
- dataops_testgen-2.2.0.dist-info/entry_points.txt +2 -0
- dataops_testgen-2.2.0.dist-info/top_level.txt +1 -0
- testgen/__init__.py +0 -0
- testgen/__main__.py +770 -0
- testgen/commands/__init__.py +0 -0
- testgen/commands/queries/__init__.py +0 -0
- testgen/commands/queries/execute_cat_tests_query.py +95 -0
- testgen/commands/queries/execute_tests_query.py +160 -0
- testgen/commands/queries/generate_tests_query.py +94 -0
- testgen/commands/queries/profiling_query.py +366 -0
- testgen/commands/queries/test_parameter_validation_query.py +88 -0
- testgen/commands/run_execute_cat_tests.py +162 -0
- testgen/commands/run_execute_tests.py +168 -0
- testgen/commands/run_generate_tests.py +107 -0
- testgen/commands/run_get_entities.py +122 -0
- testgen/commands/run_launch_db_config.py +84 -0
- testgen/commands/run_observability_exporter.py +330 -0
- testgen/commands/run_profiling_bridge.py +495 -0
- testgen/commands/run_quick_start.py +168 -0
- testgen/commands/run_setup_profiling_tools.py +96 -0
- testgen/commands/run_test_definition.py +146 -0
- testgen/commands/run_test_parameter_validation.py +135 -0
- testgen/commands/run_upgrade_db_config.py +156 -0
- testgen/common/__init__.py +8 -0
- testgen/common/clean_sql.py +53 -0
- testgen/common/credentials.py +25 -0
- testgen/common/database/__init__.py +0 -0
- testgen/common/database/database_service.py +629 -0
- testgen/common/database/flavor/__init__.py +0 -0
- testgen/common/database/flavor/flavor_service.py +75 -0
- testgen/common/database/flavor/mssql_flavor_service.py +34 -0
- testgen/common/database/flavor/postgresql_flavor_service.py +5 -0
- testgen/common/database/flavor/redshift_flavor_service.py +22 -0
- testgen/common/database/flavor/snowflake_flavor_service.py +69 -0
- testgen/common/database/flavor/trino_flavor_service.py +21 -0
- testgen/common/date_service.py +68 -0
- testgen/common/display_service.py +85 -0
- testgen/common/docker_service.py +76 -0
- testgen/common/encrypt.py +55 -0
- testgen/common/get_pipeline_parms.py +57 -0
- testgen/common/logs.py +79 -0
- testgen/common/process_service.py +62 -0
- testgen/common/read_file.py +69 -0
- testgen/settings.py +440 -0
- testgen/template/dbsetup/010_create_base_schema.sql +2 -0
- testgen/template/dbsetup/020_create_standard_functions_sprocs.sql +179 -0
- testgen/template/dbsetup/030_initialize_new_schema_structure.sql +735 -0
- testgen/template/dbsetup/040_populate_new_schema_project.sql +59 -0
- testgen/template/dbsetup/050_populate_new_schema_metadata.sql +1517 -0
- testgen/template/dbsetup/060_create_standard_views.sql +248 -0
- testgen/template/dbsetup/070_create_default_users.sql +17 -0
- testgen/template/dbsetup/075_grant_role_rights.sql +43 -0
- testgen/template/dbsetup/080_set_current_revision.sql +5 -0
- testgen/template/dbupgrade/0100_incremental_upgrade.sql +5 -0
- testgen/template/dbupgrade/0101_incremental_upgrade.sql +15 -0
- testgen/template/dbupgrade/0102_incremental_upgrade.sql +4 -0
- testgen/template/dbupgrade/0103_incremental_upgrade.sql +22 -0
- testgen/template/dbupgrade/0104_incremental_upgrade.sql +44 -0
- testgen/template/dbupgrade/0105_incremental_upgrade.sql +1 -0
- testgen/template/dbupgrade/0106_incremental_upgrade.sql +5 -0
- testgen/template/dbupgrade/0107_incremental_upgrade.sql +3 -0
- testgen/template/dbupgrade_helpers/get_tg_revision.sql +2 -0
- testgen/template/exec_cat_tests/ex_cat_build_agg_table_tests.sql +116 -0
- testgen/template/exec_cat_tests/ex_cat_get_distinct_tables.sql +11 -0
- testgen/template/exec_cat_tests/ex_cat_results_parse.sql +69 -0
- testgen/template/exec_cat_tests/ex_cat_retrieve_agg_test_parms.sql +6 -0
- testgen/template/exec_cat_tests/ex_cat_test_query.sql +8 -0
- testgen/template/execution/ex_finalize_test_run_results.sql +37 -0
- testgen/template/execution/ex_get_tests_non_cat.sql +47 -0
- testgen/template/execution/ex_update_test_record_in_testrun_table.sql +27 -0
- testgen/template/execution/ex_write_test_record_to_testrun_table.sql +6 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_no_drops_generic.sql +48 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_num_incr_generic.sql +34 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_above_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_within_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_same_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_custom_query_generic.sql +39 -0
- testgen/template/flavors/generic/exec_query_tests/ex_data_match_2way_generic.sql +58 -0
- testgen/template/flavors/generic/exec_query_tests/ex_data_match_generic.sql +44 -0
- testgen/template/flavors/generic/exec_query_tests/ex_prior_match_generic.sql +37 -0
- testgen/template/flavors/generic/exec_query_tests/ex_relative_entropy_generic.sql +53 -0
- testgen/template/flavors/generic/exec_query_tests/ex_window_match_no_drops_generic.sql +46 -0
- testgen/template/flavors/generic/exec_query_tests/ex_window_match_same_generic.sql +59 -0
- testgen/template/flavors/generic/profiling/contingency_counts.sql +3 -0
- testgen/template/flavors/generic/validate_tests/ex_get_project_column_list_generic.sql +3 -0
- testgen/template/flavors/mssql/exec_query_tests/ex_relative_entropy_mssql.sql +53 -0
- testgen/template/flavors/mssql/profiling/project_ddf_query_mssql.sql +35 -0
- testgen/template/flavors/mssql/profiling/project_profiling_query_mssql.yaml +246 -0
- testgen/template/flavors/mssql/profiling/project_secondary_profiling_query_mssql.sql +36 -0
- testgen/template/flavors/mssql/setup_profiling_tools/00_drop_existing_functions_mssql.sql +8 -0
- testgen/template/flavors/mssql/setup_profiling_tools/01_create_functions_mssql.sql +12 -0
- testgen/template/flavors/mssql/setup_profiling_tools/02_create_functions_mssql.sql +54 -0
- testgen/template/flavors/mssql/setup_profiling_tools/create_qc_schema_mssql.sql +4 -0
- testgen/template/flavors/mssql/setup_profiling_tools/grant_execute_privileges_mssql.sql +1 -0
- testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_no_drops_postgresql.sql +46 -0
- testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_same_postgresql.sql +59 -0
- testgen/template/flavors/postgresql/profiling/project_ddf_query_postgresql.sql +42 -0
- testgen/template/flavors/postgresql/profiling/project_profiling_query_postgresql.yaml +225 -0
- testgen/template/flavors/postgresql/profiling/project_secondary_profiling_query_postgresql.sql +28 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/create_functions_postgresql.sql +157 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/create_qc_schema_postgresql.sql +1 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/grant_execute_privileges_postgresql.sql +2 -0
- testgen/template/flavors/redshift/profiling/project_ddf_query_redshift.sql +38 -0
- testgen/template/flavors/redshift/profiling/project_profiling_query_redshift.yaml +221 -0
- testgen/template/flavors/redshift/profiling/project_secondary_profiling_query_redshift.sql +29 -0
- testgen/template/flavors/redshift/setup_profiling_tools/create_functions_redshift.sql +115 -0
- testgen/template/flavors/redshift/setup_profiling_tools/create_qc_schema_redshift.sql +1 -0
- testgen/template/flavors/redshift/setup_profiling_tools/grant_execute_privileges_redshift.sql +2 -0
- testgen/template/flavors/snowflake/profiling/project_ddf_query_snowflake.sql +38 -0
- testgen/template/flavors/snowflake/profiling/project_profiling_query_snowflake.yaml +220 -0
- testgen/template/flavors/snowflake/profiling/project_secondary_profiling_query_snowflake.sql +29 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/create_functions_snowflake.sql +69 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/create_qc_schema_snowflake.sql +1 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/grant_execute_privileges_snowflake.sql +6 -0
- testgen/template/flavors/trino/profiling/project_profiling_query_trino.yaml +219 -0
- testgen/template/flavors/trino/setup_profiling_tools/create_functions_trino.sql +92 -0
- testgen/template/flavors/trino/setup_profiling_tools/create_qc_schema_trino.sql +1 -0
- testgen/template/gen_funny_cat_tests/gen_test_constant.sql +104 -0
- testgen/template/gen_funny_cat_tests/gen_test_distinct_value_ct.sql +98 -0
- testgen/template/gen_funny_cat_tests/gen_test_row_ct.sql +57 -0
- testgen/template/gen_funny_cat_tests/gen_test_row_ct_pct.sql +59 -0
- testgen/template/generation/gen_delete_old_tests.sql +5 -0
- testgen/template/generation/gen_insert_test_suite.sql +5 -0
- testgen/template/generation/gen_retrieve_or_insert_test_suite.sql +58 -0
- testgen/template/generation/gen_standard_test_type_list.sql +13 -0
- testgen/template/generation/gen_standard_tests.sql +48 -0
- testgen/template/get_entities/get_connection.sql +21 -0
- testgen/template/get_entities/get_connections_list.sql +9 -0
- testgen/template/get_entities/get_latest.sql +4 -0
- testgen/template/get_entities/get_profile.sql +12 -0
- testgen/template/get_entities/get_profile_info.sql +17 -0
- testgen/template/get_entities/get_profile_list.sql +17 -0
- testgen/template/get_entities/get_profile_screen.sql +275 -0
- testgen/template/get_entities/get_project_list.sql +6 -0
- testgen/template/get_entities/get_table_group_list.sql +10 -0
- testgen/template/get_entities/get_test_generation_list.sql +18 -0
- testgen/template/get_entities/get_test_info.sql +41 -0
- testgen/template/get_entities/get_test_results_for_run_cli.sql +16 -0
- testgen/template/get_entities/get_test_run_list.sql +24 -0
- testgen/template/get_entities/get_test_suite.sql +13 -0
- testgen/template/get_entities/get_test_suite_list.sql +18 -0
- testgen/template/get_entities/list_test_types.sql +4 -0
- testgen/template/observability/get_event_data.sql +23 -0
- testgen/template/observability/get_test_results.sql +41 -0
- testgen/template/observability/update_test_results_exported_to_observability.sql +12 -0
- testgen/template/parms/parms_profiling.sql +34 -0
- testgen/template/parms/parms_test_execution.sql +13 -0
- testgen/template/parms/parms_test_gen.sql +23 -0
- testgen/template/profiling/contingency_columns.sql +7 -0
- testgen/template/profiling/datatype_suggestions.sql +56 -0
- testgen/template/profiling/functional_datatype.sql +523 -0
- testgen/template/profiling/functional_tabletype_stage.sql +48 -0
- testgen/template/profiling/functional_tabletype_update.sql +8 -0
- testgen/template/profiling/pii_flag.sql +133 -0
- testgen/template/profiling/profile_anomalies_screen_column.sql +22 -0
- testgen/template/profiling/profile_anomalies_screen_multi_column.sql +58 -0
- testgen/template/profiling/profile_anomalies_screen_table.sql +22 -0
- testgen/template/profiling/profile_anomalies_screen_table_dates.sql +30 -0
- testgen/template/profiling/profile_anomalies_screen_variants.sql +40 -0
- testgen/template/profiling/profile_anomaly_types_get.sql +3 -0
- testgen/template/profiling/project_get_table_sample_count.sql +22 -0
- testgen/template/profiling/project_profile_run_record_insert.sql +8 -0
- testgen/template/profiling/project_profile_run_record_update.sql +5 -0
- testgen/template/profiling/project_profile_run_record_update_status.sql +5 -0
- testgen/template/profiling/project_update_profile_results_to_estimates.sql +32 -0
- testgen/template/profiling/refresh_anomalies.sql +33 -0
- testgen/template/profiling/refresh_data_chars_from_profiling.sql +156 -0
- testgen/template/profiling/secondary_profiling_columns.sql +12 -0
- testgen/template/profiling/secondary_profiling_delete.sql +4 -0
- testgen/template/profiling/secondary_profiling_update.sql +18 -0
- testgen/template/quick_start/populate_target_data.sql +1077 -0
- testgen/template/quick_start/recreate_target_data_schema.sql +167 -0
- testgen/template/quick_start/update_target_data.sql +100 -0
- testgen/template/updates/create_tmp_test_definition.sql +19 -0
- testgen/template/updates/get_test_def_parms.sql +38 -0
- testgen/template/updates/populate_stg_test_definitions.sql +184 -0
- testgen/template/validate_tests/ex_disable_tests_test_definitions.sql +5 -0
- testgen/template/validate_tests/ex_flag_tests_test_definitions.sql +64 -0
- testgen/template/validate_tests/ex_get_project_column_list_generic.sql +3 -0
- testgen/template/validate_tests/ex_get_test_column_list_tg.sql +65 -0
- testgen/template/validate_tests/ex_write_test_val_errors.sql +22 -0
- testgen/ui/__init__.py +0 -0
- testgen/ui/app.py +98 -0
- testgen/ui/assets/dk_logo.svg +46 -0
- testgen/ui/assets/question_mark.png +0 -0
- testgen/ui/assets/scripts.js +68 -0
- testgen/ui/assets/style.css +140 -0
- testgen/ui/bootstrap.py +109 -0
- testgen/ui/components/__init__.py +0 -0
- testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fBBc4.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu4mxK.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu7GxKOzY.woff2 +0 -0
- testgen/ui/components/frontend/css/material-symbols-rounded.css +24 -0
- testgen/ui/components/frontend/css/material-symbols-rounded.woff2 +0 -0
- testgen/ui/components/frontend/css/roboto-font-faces.css +35 -0
- testgen/ui/components/frontend/css/shared.css +36 -0
- testgen/ui/components/frontend/img/dk_logo.svg +46 -0
- testgen/ui/components/frontend/index.html +17 -0
- testgen/ui/components/frontend/js/components/breadcrumbs.js +86 -0
- testgen/ui/components/frontend/js/components/button.js +66 -0
- testgen/ui/components/frontend/js/components/location.js +62 -0
- testgen/ui/components/frontend/js/components/select.js +75 -0
- testgen/ui/components/frontend/js/components/sidebar.js +358 -0
- testgen/ui/components/frontend/js/main.js +99 -0
- testgen/ui/components/frontend/js/streamlit.js +19 -0
- testgen/ui/components/frontend/js/van.min.js +1 -0
- testgen/ui/components/utils/__init__.py +0 -0
- testgen/ui/components/utils/callbacks.py +51 -0
- testgen/ui/components/utils/component.py +13 -0
- testgen/ui/components/widgets/__init__.py +6 -0
- testgen/ui/components/widgets/breadcrumbs.py +32 -0
- testgen/ui/components/widgets/location.py +65 -0
- testgen/ui/components/widgets/modal.py +97 -0
- testgen/ui/components/widgets/sidebar.py +69 -0
- testgen/ui/navigation/__init__.py +0 -0
- testgen/ui/navigation/menu.py +42 -0
- testgen/ui/navigation/page.py +20 -0
- testgen/ui/navigation/router.py +63 -0
- testgen/ui/queries/__init__.py +0 -0
- testgen/ui/queries/authentication_queries.py +47 -0
- testgen/ui/queries/connection_queries.py +121 -0
- testgen/ui/queries/profiling_queries.py +148 -0
- testgen/ui/queries/project_queries.py +9 -0
- testgen/ui/queries/table_group_queries.py +186 -0
- testgen/ui/queries/test_definition_queries.py +270 -0
- testgen/ui/queries/test_run_queries.py +32 -0
- testgen/ui/queries/test_suite_queries.py +145 -0
- testgen/ui/scripts/__init__.py +0 -0
- testgen/ui/scripts/patch_streamlit.py +111 -0
- testgen/ui/services/__init__.py +0 -0
- testgen/ui/services/authentication_service.py +119 -0
- testgen/ui/services/connection_service.py +220 -0
- testgen/ui/services/database_service.py +282 -0
- testgen/ui/services/form_service.py +1008 -0
- testgen/ui/services/javascript_service.py +44 -0
- testgen/ui/services/query_service.py +316 -0
- testgen/ui/services/string_service.py +12 -0
- testgen/ui/services/table_group_service.py +130 -0
- testgen/ui/services/test_definition_service.py +117 -0
- testgen/ui/services/test_run_service.py +13 -0
- testgen/ui/services/test_suite_service.py +76 -0
- testgen/ui/services/toolbar_service.py +77 -0
- testgen/ui/session.py +46 -0
- testgen/ui/views/__init__.py +0 -0
- testgen/ui/views/app_log_modal.py +92 -0
- testgen/ui/views/connections.py +72 -0
- testgen/ui/views/connections_base.py +367 -0
- testgen/ui/views/login.py +40 -0
- testgen/ui/views/not_found.py +16 -0
- testgen/ui/views/overview.py +34 -0
- testgen/ui/views/profiling_anomalies.py +501 -0
- testgen/ui/views/profiling_details.py +335 -0
- testgen/ui/views/profiling_modal.py +40 -0
- testgen/ui/views/profiling_results.py +206 -0
- testgen/ui/views/profiling_summary.py +177 -0
- testgen/ui/views/project_settings.py +74 -0
- testgen/ui/views/table_groups.py +530 -0
- testgen/ui/views/test_definitions.py +1020 -0
- testgen/ui/views/test_results.py +908 -0
- testgen/ui/views/test_runs.py +195 -0
- testgen/ui/views/test_suites.py +545 -0
- testgen/utils/__init__.py +0 -0
- testgen/utils/plugins.py +17 -0
- 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]"])
|