diff --git a/testgen/common/date_service.py b/testgen/common/date_service.py index 28e4b06..e5e89c1 100644 --- a/testgen/common/date_service.py +++ b/testgen/common/date_service.py @@ -68,7 +68,10 @@ def get_timezoned_now(streamlit_session): return get_timezoned_timestamp(streamlit_session, value) -def get_formatted_duration(duration: str) -> str: +def get_formatted_duration(duration: str | None) -> str: + if not duration: + return "--" + hour, minute, second = duration.split(":") formatted = "" if int(hour): diff --git a/testgen/ui/queries/test_suite_queries.py b/testgen/ui/queries/test_suite_queries.py index 80a3fcc..5c696c4 100644 --- a/testgen/ui/queries/test_suite_queries.py +++ b/testgen/ui/queries/test_suite_queries.py @@ -67,6 +67,18 @@ def get_by_project(schema, project_code, table_group_id=None): test_runs.id = test_results.test_run_id ) GROUP BY test_runs.id + ), + test_defs AS ( + SELECT test_suite_id, + COUNT(*) as count + FROM {schema}.test_definitions + GROUP BY test_suite_id + ), + last_profile_date AS ( + SELECT table_groups_id, + MAX(profiling_starttime) as profiling_starttime + FROM {schema}.profiling_runs + GROUP BY table_groups_id ) SELECT suites.id::VARCHAR(50), @@ -84,7 +96,9 @@ def get_by_project(schema, project_code, table_group_id=None): suites.component_key, suites.component_type, suites.component_name, + test_defs.count as test_ct, last_gen_date.auto_gen_date as latest_auto_gen_date, + last_profile_date.profiling_starttime as latest_profiling_date, last_run.id as latest_run_id, last_run.test_starttime as latest_run_start, last_run.test_ct as last_run_test_ct, @@ -98,10 +112,14 @@ def get_by_project(schema, project_code, table_group_id=None): ON (suites.id = last_gen_date.test_suite_id) LEFT JOIN last_run ON (suites.id = last_run.test_suite_id) + LEFT JOIN test_defs + ON (suites.id = test_defs.test_suite_id) LEFT JOIN {schema}.connections AS connections ON (connections.connection_id = suites.connection_id) LEFT JOIN {schema}.table_groups as groups - ON (groups.id = suites.table_groups_id) + ON (groups.id = suites.table_groups_id) + LEFT JOIN last_profile_date + ON (groups.id = last_profile_date.table_groups_id) WHERE suites.project_code = '{project_code}' """ diff --git a/testgen/ui/services/query_service.py b/testgen/ui/services/query_service.py index 3343010..284a5da 100644 --- a/testgen/ui/services/query_service.py +++ b/testgen/ui/services/query_service.py @@ -1,3 +1,4 @@ +import pandas as pd import testgen.ui.services.database_service as db """ @@ -84,35 +85,35 @@ def run_connections_lookup_query(str_schema, str_project_code): return db.retrieve_data(str_sql) -def run_table_groups_lookup_query(str_schema, str_project_code, connection_id=None, table_group_id=None): - str_sql = f""" +def run_table_groups_lookup_query(schema: str, project_code: str, connection_id: str | None = None, table_group_id: str | None = None) -> pd.DataFrame: + sql = f""" SELECT tg.id::VARCHAR(50), tg.table_groups_name, tg.connection_id, tg.table_group_schema - FROM {str_schema}.table_groups tg + FROM {schema}.table_groups tg """ if connection_id: - str_sql += f""" - inner join {str_schema}.connections c on c.connection_id = tg.connection_id + sql += f""" + inner join {schema}.connections c on c.connection_id = tg.connection_id """ - str_sql += f""" - WHERE tg.project_code = '{str_project_code}' + sql += f""" + WHERE tg.project_code = '{project_code}' """ if table_group_id: - str_sql += f""" + sql += f""" AND tg.id = '{table_group_id}'::UUID """ if connection_id: - str_sql += f""" + sql += f""" AND c.id = '{connection_id}'::UUID """ - str_sql += """ + sql += """ ORDER BY table_groups_name """ - return db.retrieve_data(str_sql) + return db.retrieve_data(sql) def run_table_lookup_query(str_schema, str_table_groups_id): diff --git a/testgen/ui/views/overview.py b/testgen/ui/views/overview.py index dccd338..00c9525 100644 --- a/testgen/ui/views/overview.py +++ b/testgen/ui/views/overview.py @@ -132,9 +132,19 @@ def render_table_group_card(table_group: pd.Series, project_code: str, key: int) ) anomaly_count = to_int(table_group["latest_anomalies_ct"]) - st.html(f""" - {anomaly_count} hygiene issues in {to_int(table_group["latest_profile_table_ct"])} tables - """) + with st.container(): + testgen.flex_row_start() + testgen.text(f""" + {to_int(table_group['latest_profile_table_ct'])} tables | + {to_int(table_group['latest_profile_column_ct'])} tables | + """) + testgen.link( + label=f"{anomaly_count} hygiene issues", + href="profiling-runs:hygiene", + params={ "run_id": str(table_group["latest_profile_id"]) }, + width=150, + key=f"overview:keys:go-to-issues:{table_group['latest_profile_id']}", + ) if anomaly_count: testgen.summary_bar( @@ -156,11 +166,8 @@ def render_table_group_card(table_group: pd.Series, project_code: str, key: int) total_tests = to_int(table_group["latest_tests_ct"]) if total_tests: passed_tests = to_int(table_group["latest_tests_passed_ct"]) - - st.html(f""" -
{round(passed_tests * 100 / total_tests)}% passed
- {total_tests} tests in {to_int(table_group["latest_tests_suite_ct"])} test suites - """) + testgen.text(f"{round(passed_tests * 100 / total_tests)}% passed") + testgen.text(f"{total_tests} tests in {to_int(table_group['latest_tests_suite_ct'])} test suites", "margin: 12px 0 12px;") testgen.summary_bar( items=[ @@ -207,7 +214,7 @@ def render_test_suite_item(test_suite: pd.Series, column_spec: list[int]) -> Non params={ "test_suite_id": str(test_suite["id"]) }, key=f"overview:keys:go-to-definitions:{test_suite['id']}", ) - testgen.caption(f"{to_int(test_suite['last_run_test_ct'])} tests", "margin-top: -16px;") + testgen.caption(f"{to_int(test_suite['test_ct'])} tests", "margin-top: -16px;") with generation_column: if (latest_generation := test_suite["latest_auto_gen_date"]) and pd.notnull(latest_generation): @@ -257,6 +264,7 @@ def get_table_groups_summary(project_code: str) -> pd.DataFrame: latest_run.id, latest_run.profiling_starttime, latest_run.table_ct, + latest_run.column_ct, latest_run.anomaly_ct, SUM( CASE @@ -359,6 +367,7 @@ def get_table_groups_summary(project_code: str) -> pd.DataFrame: latest_profile.id as latest_profile_id, latest_profile.profiling_starttime as latest_profile_start, latest_profile.table_ct as latest_profile_table_ct, + latest_profile.column_ct as latest_profile_column_ct, latest_profile.anomaly_ct as latest_anomalies_ct, latest_profile.definite_ct as latest_anomalies_definite_ct, latest_profile.likely_ct as latest_anomalies_likely_ct, diff --git a/testgen/ui/views/table_groups.py b/testgen/ui/views/table_groups.py index 887bc09..0fcd627 100644 --- a/testgen/ui/views/table_groups.py +++ b/testgen/ui/views/table_groups.py @@ -164,11 +164,18 @@ def delete_table_group_dialog(self, table_group: pd.Series): ) accept_cascade_delete = st.toggle("I accept deletion of this Table Group and all related TestGen data.") - with st.form("Delete Table Group", clear_on_submit=True): + with st.form("Delete Table Group", clear_on_submit=True, border=False): disable_delete_button = authentication_service.current_user_has_read_role() or ( not can_be_deleted and not accept_cascade_delete ) - delete = st.form_submit_button("Delete", disabled=disable_delete_button, type="primary") + _, button_column = st.columns([.85, .15]) + with button_column: + delete = st.form_submit_button( + "Delete", + disabled=disable_delete_button, + type="primary", + use_container_width=True, + ) if delete: if table_group_service.are_table_groups_in_use([table_group_name]): @@ -375,7 +382,7 @@ def show_table_group_form(mode, project_code: str, connection: dict, table_group success_message = "Changes have been saved successfully. " else: table_group_service.add(entity) - success_message = "New Table Group added successfully. " + success_message = "New table group added successfully. " except IntegrityError: st.error("A Table Group with the same name already exists. ") return diff --git a/testgen/ui/views/test_definitions.py b/testgen/ui/views/test_definitions.py index baae650..9348c32 100644 --- a/testgen/ui/views/test_definitions.py +++ b/testgen/ui/views/test_definitions.py @@ -156,9 +156,16 @@ def delete_test_dialog(selected_test_definition): int_data_width=700, ) - with st.form("Delete Test Definition", clear_on_submit=True): + with st.form("Delete Test Definition", clear_on_submit=True, border=False): disable_delete_button = authentication_service.current_user_has_read_role() or not can_be_deleted - delete = st.form_submit_button("Delete", disabled=disable_delete_button, type="primary") + _, button_column = st.columns([.85, .15]) + with button_column: + delete = st.form_submit_button( + "Delete", + disabled=disable_delete_button, + type="primary", + use_container_width=True, + ) if delete: test_definition_service.delete([test_definition_id]) diff --git a/testgen/ui/views/test_suites.py b/testgen/ui/views/test_suites.py index 9b9ac5b..8c4a5bc 100644 --- a/testgen/ui/views/test_suites.py +++ b/testgen/ui/views/test_suites.py @@ -103,7 +103,7 @@ def render(self, project_code: str | None = None, table_group_id: str | None = N with main_section: testgen.no_flex_gap() testgen.link( - label=f"{to_int(test_suite['last_run_test_ct'])} tests definitions", + label=f"{to_int(test_suite['test_ct'])} tests definitions", href="test-suites:definitions", params={ "test_suite_id": test_suite["id"] }, right_icon="chevron_right", @@ -143,16 +143,22 @@ def render(self, project_code: str | None = None, table_group_id: str | None = N if user_can_edit: with actions_section: + run_disabled = not to_int(test_suite["test_ct"]) testgen.button( type_="stroked", label="Run Tests", + tooltip="No test definitions to run" if run_disabled else None, on_click=partial(run_tests_dialog, project_code, test_suite), + disabled=run_disabled, key=f"test_suite:keys:runtests:{test_suite['id']}", ) + generate_disabled = pd.isnull(test_suite["latest_profiling_date"]) testgen.button( type_="stroked", label="Generate Tests", + tooltip="No profiling data available for test generation" if generate_disabled else None, on_click=partial(generate_tests_dialog, test_suite), + disabled=generate_disabled, key=f"test_suite:keys:generatetests:{test_suite['id']}", ) @@ -303,7 +309,7 @@ def show_test_suite(mode, project_code, table_groups_df, selected=None): success_message = ( "Changes have been saved successfully. " if mode == "edit" - else "New TestSuite added successfully. " + else "New test suite added successfully. " ) st.success(success_message) time.sleep(1)