From 052df0305cfe26e64ef4bcaef959c7d3a2ba833d Mon Sep 17 00:00:00 2001 From: Kushagra Parashar Date: Mon, 10 Feb 2025 23:11:55 +0530 Subject: [PATCH 1/4] Initial version for Reconcile App --- .gitignore | 1 + src/databricks/labs/remorph/app/README.md | 0 src/databricks/labs/remorph/app/app.py | 21 +++ src/databricks/labs/remorph/app/app.yaml | 13 ++ .../labs/remorph/app/requirements.txt | 11 ++ .../labs/remorph/app/src/__init__.py | 0 .../labs/remorph/app/src/config/__init__.py | 0 .../labs/remorph/app/src/config/settings.py | 17 ++ .../labs/remorph/app/src/queries/__init__.py | 0 .../remorph/app/src/queries/ddls/__init__.py | 0 .../src/queries/ddls/create_config_table.sql | 61 +++++++ .../src/queries/ddls/create_status_table.sql | 29 ++++ .../remorph/app/src/queries/dmls/__init__.py | 0 .../queries/dmls/fetch_existing_configs.sql | 1 + .../app/src/queries/dmls/fetch_recon_type.sql | 1 + .../dmls/fetch_schema_comparison_details.sql | 38 +++++ .../src/queries/dmls/fetch_source_types.sql | 1 + .../app/src/queries/dmls/fetch_summary.sql | 39 +++++ .../queries/dmls/fetch_total_failed_runs.sql | 7 + .../fetch_unique_target_tables_failed.sql | 7 + .../fetch_unique_target_tables_successful.SQL | 7 + .../app/src/queries/dmls/fetch_users.sql | 1 + .../app/src/resources/logo/remorph.png | Bin 0 -> 12944 bytes .../app/src/resources/logo/remorph.svg | 1 + .../src/resources/web_components/__init__.py | 0 .../src/resources/web_components/homepage.py | 73 ++++++++ .../labs/remorph/app/src/routes/__init__.py | 0 .../labs/remorph/app/src/routes/about.py | 7 + .../remorph/app/src/routes/config_manager.py | 160 ++++++++++++++++++ .../labs/remorph/app/src/routes/dashboard.py | 83 +++++++++ .../labs/remorph/app/src/routes/home.py | 94 ++++++++++ .../remorph/app/src/routes/recon_executor.py | 31 ++++ .../remorph/app/src/routes/secret_manager.py | 7 + .../labs/remorph/app/src/services/__init__.py | 0 .../remorph/app/src/services/data_service.py | 31 ++++ .../remorph/app/src/services/spark_service.py | 85 ++++++++++ .../labs/remorph/app/src/utils/__init__.py | 0 .../app/src/utils/pretty_print_configs.py | 8 + .../remorph/app/src/utils/query_loader.py | 34 ++++ .../remorph/app/src/utils/schemas/__init__.py | 0 .../app/src/utils/schemas/config_schema.py | 38 +++++ .../utils/schemas/job_run_details_schema.py | 0 .../labs/remorph/app/tests/__init__.py | 0 .../app/tests/test_config/test_settings.py | 17 ++ 44 files changed, 924 insertions(+) create mode 100644 src/databricks/labs/remorph/app/README.md create mode 100644 src/databricks/labs/remorph/app/app.py create mode 100644 src/databricks/labs/remorph/app/app.yaml create mode 100644 src/databricks/labs/remorph/app/requirements.txt create mode 100644 src/databricks/labs/remorph/app/src/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/config/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/config/settings.py create mode 100644 src/databricks/labs/remorph/app/src/queries/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/queries/ddls/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/queries/ddls/create_config_table.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/ddls/create_status_table.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_existing_configs.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_recon_type.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_schema_comparison_details.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_source_types.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_summary.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_total_failed_runs.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_unique_target_tables_failed.sql create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_unique_target_tables_successful.SQL create mode 100644 src/databricks/labs/remorph/app/src/queries/dmls/fetch_users.sql create mode 100644 src/databricks/labs/remorph/app/src/resources/logo/remorph.png create mode 100644 src/databricks/labs/remorph/app/src/resources/logo/remorph.svg create mode 100644 src/databricks/labs/remorph/app/src/resources/web_components/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/resources/web_components/homepage.py create mode 100644 src/databricks/labs/remorph/app/src/routes/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/routes/about.py create mode 100644 src/databricks/labs/remorph/app/src/routes/config_manager.py create mode 100644 src/databricks/labs/remorph/app/src/routes/dashboard.py create mode 100644 src/databricks/labs/remorph/app/src/routes/home.py create mode 100644 src/databricks/labs/remorph/app/src/routes/recon_executor.py create mode 100644 src/databricks/labs/remorph/app/src/routes/secret_manager.py create mode 100644 src/databricks/labs/remorph/app/src/services/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/services/data_service.py create mode 100644 src/databricks/labs/remorph/app/src/services/spark_service.py create mode 100644 src/databricks/labs/remorph/app/src/utils/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/utils/pretty_print_configs.py create mode 100644 src/databricks/labs/remorph/app/src/utils/query_loader.py create mode 100644 src/databricks/labs/remorph/app/src/utils/schemas/__init__.py create mode 100644 src/databricks/labs/remorph/app/src/utils/schemas/config_schema.py create mode 100644 src/databricks/labs/remorph/app/src/utils/schemas/job_run_details_schema.py create mode 100644 src/databricks/labs/remorph/app/tests/__init__.py create mode 100644 src/databricks/labs/remorph/app/tests/test_config/test_settings.py diff --git a/.gitignore b/.gitignore index 66e5bb4282..0f9db012f5 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ remorph_transpile/ /linter/src/main/antlr4/library/gen/ .databricks-login.json /core/src/main/antlr4/com/databricks/labs/remorph/parsers/*/gen/ +src/databricks/labs/remorph/app/.env diff --git a/src/databricks/labs/remorph/app/README.md b/src/databricks/labs/remorph/app/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/app.py b/src/databricks/labs/remorph/app/app.py new file mode 100644 index 0000000000..5bd38d9364 --- /dev/null +++ b/src/databricks/labs/remorph/app/app.py @@ -0,0 +1,21 @@ +from src.resources.web_components.homepage import render_homepage +from src.services.spark_service import initialize_app + +selected_option = render_homepage() +initialize_app() + +# Routing +if selected_option == "Home": + from src.routes.home import main as page_main +elif selected_option == "Recon Executor": + from src.routes.recon_executor import main as page_main +elif selected_option == "Secret Manager": + from src.routes.secret_manager import main as page_main +elif selected_option == "Config Manager": + from src.routes.config_manager import main as page_main +elif selected_option == "Dashboard": + from src.routes.dashboard import main as page_main +elif selected_option == "About": + from src.routes.about import main as page_main + +page_main() diff --git a/src/databricks/labs/remorph/app/app.yaml b/src/databricks/labs/remorph/app/app.yaml new file mode 100644 index 0000000000..3779bca52d --- /dev/null +++ b/src/databricks/labs/remorph/app/app.yaml @@ -0,0 +1,13 @@ +command: [ + "streamlit", + "run", + "app.py" +] + +env: + - name: "REMORPH_METADATA_SCHEMA" + value: "kushagra_remorph.reconcile" + - name: STREAMLIT_BROWSER_GATHER_USAGE_STATS + value: "false" + - name: RECON_APP_RESOURCES + value: /Workspace/Users/kushagra.parashar@databricks.com/Swiggy/ diff --git a/src/databricks/labs/remorph/app/requirements.txt b/src/databricks/labs/remorph/app/requirements.txt new file mode 100644 index 0000000000..e32cf0b6d4 --- /dev/null +++ b/src/databricks/labs/remorph/app/requirements.txt @@ -0,0 +1,11 @@ +streamlit==1.41.0 +databricks-connect==15.4.0 +streamlit-dynamic-filters +streamlit-aggrid +altair==5.0.0 +streamlit-dynamic-filters +pandas~=2.2.2 +databricks-sdk~=0.41.0 +streamlit_option_menu +python-dotenv~=1.0.1 +pandas-stubs diff --git a/src/databricks/labs/remorph/app/src/__init__.py b/src/databricks/labs/remorph/app/src/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/config/__init__.py b/src/databricks/labs/remorph/app/src/config/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/config/settings.py b/src/databricks/labs/remorph/app/src/config/settings.py new file mode 100644 index 0000000000..4e11e81930 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/config/settings.py @@ -0,0 +1,17 @@ +import os +from dotenv import load_dotenv +from databricks.connect.session import DatabricksSession + + +class Settings: + def __init__(self): + load_dotenv() + self.DATABRICKS_CLUSTER_ID = os.getenv('DATABRICKS_CLUSTER_ID') + self.REMORPH_METADATA_SCHEMA = os.getenv('REMORPH_METADATA_SCHEMA') + self.RECON_CONFIG_TABLE_NAME = os.getenv('RECON_CONFIG_TABLE_NAME') + self.RECON_JOB_RUN_DETAILS_TABLE_NAME = os.getenv('RECON_JOqB_RUN_DETAILS_TABLE_NAME') + # self.LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO') + self.spark = DatabricksSession.builder.clusterId(self.DATABRICKS_CLUSTER_ID).getOrCreate() + + +settings = Settings() diff --git a/src/databricks/labs/remorph/app/src/queries/__init__.py b/src/databricks/labs/remorph/app/src/queries/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/queries/ddls/__init__.py b/src/databricks/labs/remorph/app/src/queries/ddls/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/queries/ddls/create_config_table.sql b/src/databricks/labs/remorph/app/src/queries/ddls/create_config_table.sql new file mode 100644 index 0000000000..c8cfe67db2 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/ddls/create_config_table.sql @@ -0,0 +1,61 @@ +CREATE TABLE IF NOT EXISTS {RECON_CONFIG_TABLE_NAME} +( + config_id + INT + PRIMARY + KEY, + source_catalog + STRING + NOT + NULL, + source_schema + STRING + NOT + NULL, + target_catalog + STRING + NOT + NULL, + target_schema + STRING + NOT + NULL, + tables + ARRAY< + STRUCT< + source_name + : + STRING, + target_name + : + STRING, + drop_columns + : + ARRAY< + STRING>, + join_columns + : + ARRAY< + STRING>, + transformations + : + ARRAY< + STRUCT< + column_name + : + STRING, + source + : + STRING, + target + : + STRING + >>, + jdbc_reader_options + : + MAP< + STRING, + STRING> + >> +) + USING DELTA \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/ddls/create_status_table.sql b/src/databricks/labs/remorph/app/src/queries/ddls/create_status_table.sql new file mode 100644 index 0000000000..a849ad1a1f --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/ddls/create_status_table.sql @@ -0,0 +1,29 @@ +CREATE TABLE IF NOT EXISTS {RECON_JOB_RUN_DETAILS_TABLE_NAME} +( + job_run_id + BIGINT + PRIMARY + KEY, + start_time + TIMESTAMP, + end_time + TIMESTAMP, + user_name + STRING, + duration + BIGINT, -- Store duration in seconds + source_dialect + STRING, + workspace_id + STRING, + workspace_name + STRING, + status + STRING, + exception_message + STRING, + created_at + TIMESTAMP, + updated_at + TIMESTAMP +) USING DELTA; \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/__init__.py b/src/databricks/labs/remorph/app/src/queries/dmls/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_existing_configs.sql b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_existing_configs.sql new file mode 100644 index 0000000000..a85ba8182c --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_existing_configs.sql @@ -0,0 +1 @@ +select * from {REMORPH_METADATA_SCHEMA}.recon_app_config_table; \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_recon_type.sql b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_recon_type.sql new file mode 100644 index 0000000000..7c580f2021 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_recon_type.sql @@ -0,0 +1 @@ +SELECT DISTINCT recon_type FROM {REMORPH_METADATA_SCHEMA}.details \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_schema_comparison_details.sql b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_schema_comparison_details.sql new file mode 100644 index 0000000000..92e0b347f5 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_schema_comparison_details.sql @@ -0,0 +1,38 @@ +WITH tmp AS (SELECT recon_table_id, + inserted_ts, + EXPLODE(data) AS schema_data + FROM {REMORPH_METADATA_SCHEMA}.details +WHERE + recon_type = 'schema' + ) +SELECT main.recon_id, + main.source_table.`catalog` AS source_catalog, + main.source_table.`schema` AS source_schema, + main.source_table.table_name AS source_table_name, + IF( + ISNULL(source_catalog), + CONCAT_WS('.', source_schema, source_table_name), + CONCAT_WS('.', source_catalog, source_schema, source_table_name) + ) AS source_table, + main.target_table.`catalog` AS target_catalog, + main.target_table.`schema` AS target_schema, + main.target_table.table_name AS target_table_name, + CONCAT( + main.target_table.catalog, + '.', + main.target_table.schema, + '.', + main.target_table.table_name + ) AS target_table, + schema_data['source_column'] AS source_column, + schema_data['source_datatype'] AS source_datatype, + schema_data['databricks_column'] AS databricks_column, + schema_data['databricks_datatype'] AS databricks_datatype, + schema_data['is_valid'] AS is_valid +FROM {REMORPH_METADATA_SCHEMA}.main AS main +INNER JOIN tmp +ON main.recon_table_id = tmp.recon_table_id +ORDER BY + tmp.inserted_ts DESC, + main.recon_id, + main.target_table \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_source_types.sql b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_source_types.sql new file mode 100644 index 0000000000..cd1f32e10c --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_source_types.sql @@ -0,0 +1 @@ +SELECT DISTINCT source_type FROM {REMORPH_METADATA_SCHEMA}.main \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_summary.sql b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_summary.sql new file mode 100644 index 0000000000..f037b42d05 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_summary.sql @@ -0,0 +1,39 @@ +SELECT main.recon_id, + main.source_type, + main.report_type, + main.source_table.`catalog` AS source_catalog, + main.source_table.`schema` AS source_schema, + main.source_table.table_name AS source_table_name, + IF( + ISNULL(source_catalog), + CONCAT_WS('.', source_schema, source_table_name), + CONCAT_WS('.', source_catalog, source_schema, source_table_name) + ) AS source_table, + main.target_table.`catalog` AS target_catalog, + main.target_table.`schema` AS target_schema, + main.target_table.table_name AS target_table_name, + CONCAT( + main.target_table.catalog, + '.', + main.target_table.schema, + '.', + main.target_table.table_name + ) AS target_table, + metrics.run_metrics.status AS status, + metrics.run_metrics.exception_message AS exception, + metrics.recon_metrics.row_comparison.missing_in_source AS missing_in_source, + metrics.recon_metrics.row_comparison.missing_in_target AS missing_in_target, + metrics.recon_metrics.column_comparison.absolute_mismatch AS absolute_mismatch, + metrics.recon_metrics.column_comparison.threshold_mismatch AS threshold_mismatch, + metrics.recon_metrics.column_comparison.mismatch_columns AS mismatch_columns, + metrics.recon_metrics.schema_comparison AS schema_comparison, + metrics.run_metrics.run_by_user AS executed_by, + main.start_ts AS start_ts, + main.end_ts AS end_ts +FROM {REMORPH_METADATA_SCHEMA}.main AS main + INNER JOIN {REMORPH_METADATA_SCHEMA}.metrics AS metrics +ON main.recon_table_id = metrics.recon_table_id +ORDER BY + metrics.inserted_ts DESC, + main.recon_id, + main.target_table.table_name \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_total_failed_runs.sql b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_total_failed_runs.sql new file mode 100644 index 0000000000..d2b6f1b231 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_total_failed_runs.sql @@ -0,0 +1,7 @@ +SELECT main.recon_id AS rec_id, + CAST(main.start_ts AS DATE) AS start_date +FROM {REMORPH_METADATA_SCHEMA}.main AS main +INNER JOIN {REMORPH_METADATA_SCHEMA}.metrics AS metrics +ON main.recon_table_id = metrics.recon_table_id +WHERE + metrics.run_metrics.status = FALSE \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_unique_target_tables_failed.sql b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_unique_target_tables_failed.sql new file mode 100644 index 0000000000..3e3ca3e998 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_unique_target_tables_failed.sql @@ -0,0 +1,7 @@ +SELECT CONCAT_WS('.', main.target_table.catalog, main.target_table.schema, main.target_table.table_name) AS t_table, + CAST(main.start_ts AS DATE) AS start_date +FROM {REMORPH_METADATA_SCHEMA}.main AS main +INNER JOIN {REMORPH_METADATA_SCHEMA}.metrics AS metrics +ON main.recon_table_id = metrics.recon_table_id +WHERE + metrics.run_metrics.status = FALSE \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_unique_target_tables_successful.SQL b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_unique_target_tables_successful.SQL new file mode 100644 index 0000000000..098035e621 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_unique_target_tables_successful.SQL @@ -0,0 +1,7 @@ +SELECT CONCAT_WS('.', main.target_table.catalog, main.target_table.schema, main.target_table.table_name) AS t_table, + CAST(main.start_ts AS DATE) AS start_date +FROM {REMORPH_METADATA_SCHEMA}.main AS main +INNER JOIN {REMORPH_METADATA_SCHEMA}.metrics AS metrics +ON main.recon_table_id = metrics.recon_table_id +WHERE + metrics.run_metrics.status = TRUE \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/queries/dmls/fetch_users.sql b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_users.sql new file mode 100644 index 0000000000..8054b86c85 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/queries/dmls/fetch_users.sql @@ -0,0 +1 @@ +SELECT DISTINCT run_metrics.run_by_user FROM {REMORPH_METADATA_SCHEMA}.metrics \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/resources/logo/remorph.png b/src/databricks/labs/remorph/app/src/resources/logo/remorph.png new file mode 100644 index 0000000000000000000000000000000000000000..b5de4ed67cbfb76ef993a71236dfdba4fbadbf17 GIT binary patch literal 12944 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 literal 0 HcmV?d00001 diff --git a/src/databricks/labs/remorph/app/src/resources/logo/remorph.svg b/src/databricks/labs/remorph/app/src/resources/logo/remorph.svg new file mode 100644 index 0000000000..5f3c32f822 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/resources/logo/remorph.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/databricks/labs/remorph/app/src/resources/web_components/__init__.py b/src/databricks/labs/remorph/app/src/resources/web_components/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/resources/web_components/homepage.py b/src/databricks/labs/remorph/app/src/resources/web_components/homepage.py new file mode 100644 index 0000000000..2918525d58 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/resources/web_components/homepage.py @@ -0,0 +1,73 @@ +import streamlit as st # type: ignore +from pathlib import Path +from streamlit_option_menu import option_menu # type: ignore + +# Set up paths for logo +logo_path_svg = Path("src/resources/logo/remorph.svg") +logo_path_png = Path("src/resources/logo/remorph.png") +st.set_page_config(page_title="Reconcile", page_icon=logo_path_png, layout="wide") + + +def load_svg_inline(svg_path): + with open(svg_path, "r") as file: + return file.read() + + +def render_homepage(): + logo_svg_content = load_svg_inline(logo_path_svg) + + with st.sidebar: + st.markdown( + f""" +
+
+ {logo_svg_content} +
+

Reconcile

+
+ """, + unsafe_allow_html=True, + ) + + menu_options = { + "Home": None, + "Recon Executor": "recon_executor", + "Secret Manager": "secret_manager", + "Config Manager": "config_manager", + "Dashboard": "dashboard", + "About": "about", + } + + query_params = st.query_params.get("page", None) + + default_index = list(menu_options.values()).index(query_params) if query_params in menu_options.values() else 0 + selected_option = option_menu( + menu_title=None, + options=list(menu_options.keys()), + icons=["house", "play-circle", "key", "wrench", "bar-chart", "info-circle"], + menu_icon="cast", + default_index=default_index, + styles={ + "container": {"padding": "0", "background-color": "#f8f9fa"}, + "icon": {"color": "blue", "font-size": "20px"}, + "nav-link": { + "font-size": "16px", + "text-align": "left", + "margin": "0px", + "color": "black", + "--hover-color": "#eeeeee", + }, + "nav-link-selected": {"background-color": "#4CAF50", "color": "white"}, + }, + ) + + # Update the URL **only if the selected page has changed** (avoiding unnecessary rerun) + new_page = menu_options[selected_option] + if new_page != query_params: + if new_page is None: + st.query_params.clear() + else: + st.query_params["page"] = new_page + st.rerun() + + return selected_option diff --git a/src/databricks/labs/remorph/app/src/routes/__init__.py b/src/databricks/labs/remorph/app/src/routes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/routes/about.py b/src/databricks/labs/remorph/app/src/routes/about.py new file mode 100644 index 0000000000..dfdc77d9ce --- /dev/null +++ b/src/databricks/labs/remorph/app/src/routes/about.py @@ -0,0 +1,7 @@ +import streamlit as st # type: ignore + + +def main(): + # Title and Introduction + st.title("Remorph - Reconcile") + st.markdown("### W.I.P") diff --git a/src/databricks/labs/remorph/app/src/routes/config_manager.py b/src/databricks/labs/remorph/app/src/routes/config_manager.py new file mode 100644 index 0000000000..ec602c3b39 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/routes/config_manager.py @@ -0,0 +1,160 @@ +import json +import streamlit as st # type: ignore +from ..config.settings import settings +from ..services.spark_service import load_query, fetch_dataframe, save_config_to_delta +from ..utils.pretty_print_configs import create_collapsible_json + + +def main(): + st.title("Config Manager") + tab1, tab2 = st.tabs(["Add New Config", "View Existing Configs"]) + with tab1: + table_form_expander = st.expander("Add a new config") + with table_form_expander: + col1, col2, col3, col4 = st.columns(4) + with col1: + source_catalog = st.text_input("Source Catalog", value="hive_metastore") + with col2: + source_schema = st.text_input("Source Schema", value="labs") + with col3: + target_catalog = st.text_input("Target Catalog", value="sandbox_db") + with col4: + target_schema = st.text_input("Target Schema", value="labs") + + # Initialize tables list + if "tables" not in st.session_state: + st.session_state["tables"] = [] + + col1, col2 = st.columns(2) + with col1: + source_name = st.text_input("Source Table Name") + with col2: + target_name = st.text_input("Target Table Name") + + # Column mapping + st.subheader("Column Mapping") + column_mapping = [] + num_columns = st.number_input("Number of columns to map", min_value=1, step=1, key="num_columns") + for i in range(num_columns): + col1, col2 = st.columns(2) + with col1: + source_column = st.text_input(f"Source Column {i + 1}", key=f"source_column_{i}") + with col2: + target_column = st.text_input(f"Target Column {i + 1}", key=f"target_column_{i}") + column_mapping.append({"source_name": source_column, "target_name": target_column}) + + # Join columns + st.subheader("Join Columns") + join_columns = st.text_input("Enter join columns, comma-separated") + + # Column thresholds + st.subheader("Column Thresholds") + column_thresholds = [] + num_thresholds = st.number_input("Number of column thresholds", min_value=0, step=1, key="num_thresholds") + for i in range(num_thresholds): + col1, col2, col3, col4 = st.columns(4) + with col1: + column_name = st.text_input(f"Threshold Column {i + 1}", key=f"threshold_column_{i}") + with col2: + upper_bound = st.text_input(f"Upper Bound for {column_name}", key=f"upper_bound_{i}") + with col3: + lower_bound = st.text_input(f"Lower Bound for {column_name}", key=f"lower_bound_{i}") + with col4: + threshold_type = st.selectbox( + f"Type for {column_name}", ["int", "float", "string"], key=f"threshold_type_{i}" + ) + column_thresholds.append( + { + "column_name": column_name, + "upper_bound": upper_bound, + "lower_bound": lower_bound, + "type": threshold_type, + } + ) + + # Transformations + st.subheader("Transformations") + transformations = [] + num_transformations = st.number_input( + "Number of transformations", min_value=0, step=1, key="num_transformations" + ) + for i in range(num_transformations): + col1, col2, col3 = st.columns(3) + with col1: + column_name = st.text_input(f"Column Name {i + 1}", key=f"column_name_{i}") + with col2: + source = st.text_input(f"Transformation Source {i + 1}", key=f"transformation_source_{i}") + with col3: + target = st.text_input(f"Transformation Target {i + 1}", key=f"transformation_target_{i}") + transformations.append({"column_name": column_name, "source": source, "target": target}) + + # Drop columns + st.subheader("Drop Columns") + drop_columns = st.text_input("Enter columns to drop, comma-separated") + + # Filters + st.subheader("Filters") + col1, col2 = st.columns(2) + with col1: + source_filter = st.text_area("Source Filter") + with col2: + target_filter = st.text_area("Target Filter") + filters = {"source": source_filter, "target": target_filter} + + # JDBC Reader Options + st.subheader("JDBC Reader Options") + jdbc_reader_options = st.text_area("Enter JDBC reader options as JSON") + + # Table thresholds + st.subheader("Table Thresholds") + table_thresholds = st.text_area("Enter table thresholds as JSON") + + table_details = { + "source_catalog": source_catalog, + "source_schema": source_schema, + "target_catalog": target_catalog, + "target_schema": target_schema, + "tables": [ + { + "source_name": source_name, + "target_name": target_name, + "column_mapping": column_mapping, + "drop_columns": drop_columns.split(",") if drop_columns else [], + "filters": filters, + "jdbc_reader_options": json.loads(jdbc_reader_options) if jdbc_reader_options else {}, + "join_columns": join_columns.split(",") if join_columns else [], + "select_columns": None, # This can be updated based on additional user inputs if needed + "column_thresholds": column_thresholds, + "table_thresholds": json.loads(table_thresholds) if table_thresholds else {}, + "transformations": transformations, + } + ], + } + table_details_json = json.dumps(table_details, indent=4) + view_config, save_config = st.columns(2) + with view_config: + if st.button("View Config"): + st.json(table_details_json) + st.button("Close") + with save_config: + if st.button("Save Config"): + # save_json(json_data=table_details_json, path=RECON_APP_RESOURCES_DIR) + save_config_to_delta(table_details=table_details) + + with tab2: + st.write("View Existing Configs") + # Fetch existing configs + fetch_configs = load_query( + "dmls", "fetch_existing_configs", REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA + ) + configs = fetch_dataframe(fetch_configs) + + # Convert the 'tables' column to a string + configs['tables'] = configs['tables'].apply(create_collapsible_json) + + # Display the DataFrame + st.dataframe(configs) + + +if __name__ == "__main__": + main() diff --git a/src/databricks/labs/remorph/app/src/routes/dashboard.py b/src/databricks/labs/remorph/app/src/routes/dashboard.py new file mode 100644 index 0000000000..b0cd0b5218 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/routes/dashboard.py @@ -0,0 +1,83 @@ +import streamlit as st # type: ignore +from ..config.settings import settings +from ..services.spark_service import load_query, fetch_dataframe, fetch_list_from_queries + + +def main(): + st.title("Reconciliation Metrics") + + # Fetch all distinct users to populate the dropdown + fetch_users = load_query("dmls", "fetch_users", REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA) + users = fetch_list_from_queries(fetch_users) + users = [None] + users if users is not None else [None] + + # Fetch all distinct recon tables to populate the dropdown + fetch_recon_type = load_query("dmls", "fetch_recon_type", REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA) + recon_tables = fetch_list_from_queries(fetch_recon_type) + recon_tables = [None] + recon_tables if recon_tables is not None else [None] + + # Fetch source types to populate the dropdown + fetch_source_types = load_query( + "dmls", "fetch_source_types", REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA + ) + source_types = fetch_list_from_queries(fetch_source_types) + source_types = [None] + source_types if source_types is not None else [None] + + filter_1, filter_2, filter_3 = st.columns(3) + with filter_1: + user_name_filter = st.selectbox("Select User", users, index=0) + with filter_2: + recon_table_filter = st.selectbox("Select Recon Type", recon_tables, index=0) + with filter_3: + source_type_filter = st.selectbox("Select Source", source_types, index=0) + print(f'User: {user_name_filter}, Recon Table: {recon_table_filter}, Source Type: {source_type_filter}') + + st.divider() + + fetch_total_failed_runs = load_query( + "dmls", 'fetch_total_failed_runs', REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA + ) + total_failed_runs = fetch_dataframe(fetch_total_failed_runs) + total_failed_runs = ( + len(total_failed_runs.index) if total_failed_runs is not None and not total_failed_runs.empty else 0 + ) + + fetch_unique_target_tables_failed = load_query( + "dmls", 'fetch_unique_target_tables_failed', REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA + ) + unique_target_tables_failed = fetch_dataframe(fetch_unique_target_tables_failed) + unique_target_tables_failed = ( + len(unique_target_tables_failed.index) + if unique_target_tables_failed is not None and not unique_target_tables_failed.empty + else 0 + ) + + fetch_unique_target_tables_successful = load_query( + "dmls", 'fetch_unique_target_tables_successful', REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA + ) + unique_target_tables_successful = fetch_dataframe(fetch_unique_target_tables_successful) + unique_target_tables_successful = ( + len(unique_target_tables_successful.index) + if unique_target_tables_successful is not None and not unique_target_tables_successful.empty + else 0 + ) + + col1, col2, col3 = st.columns(3) + with col1: + st.metric("Total Failed Runs", total_failed_runs, " - No of failed runs") + with col2: + st.metric("Unique Target Tables Failed", unique_target_tables_failed, " - Unique Failed Tables") + with col3: + st.metric("Unique Target Tables Successful", unique_target_tables_successful, "Unique Successful") + + st.write("Reconciliation Summary") + fetch_summary_sql = load_query("dmls", "fetch_summary", REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA) + summary_df = fetch_dataframe(fetch_summary_sql) + st.dataframe(summary_df) + + st.write("Schema Comparison Details") + fetch_schema_comparison_details_sql = load_query( + "dmls", "fetch_schema_comparison_details", REMORPH_METADATA_SCHEMA=settings.REMORPH_METADATA_SCHEMA + ) + df_schema_comparison_details = fetch_dataframe(fetch_schema_comparison_details_sql) + st.dataframe(df_schema_comparison_details) diff --git a/src/databricks/labs/remorph/app/src/routes/home.py b/src/databricks/labs/remorph/app/src/routes/home.py new file mode 100644 index 0000000000..15f22332bd --- /dev/null +++ b/src/databricks/labs/remorph/app/src/routes/home.py @@ -0,0 +1,94 @@ +import streamlit as st # type: ignore +import streamlit.components.v1 as components # type: ignore + + +def main(): + # Title and Introduction + st.title("🔍 Remorph - Reconcile") + st.markdown("### A powerful tool for **automated data reconciliation** in Databricks.") + + st.header("What is Reconcile?") + st.markdown( + """ + **Reconcile** is an automated **data validation and comparison** tool designed for **verifying the accuracy of migrated data** + between a **source system** and **Databricks**. It helps identify **discrepancies** in data to ensure **seamless and error-free migrations**. + + **Key Capabilities:** + - ✅ **Compare** source data (e.g., Snowflake, Oracle) with target data in Databricks. + - 🔍 **Detect anomalies** such as missing rows, mismatched values, or schema inconsistencies. + - 🚀 **Scale efficiently** to handle large datasets with optimized performance. + """ + ) + + st.header("How Reconcile Works") + st.markdown("Below is a **visual representation** of the reconciliation process:") + + mermaid_html = """ +
+ flowchart TD + A(Transpile CLI) --> |Directory| B[Transpile All Files In Directory]; + A --> |File| C[Transpile Single File] ; + B --> D[List Files]; + C --> E("Sqlglot(transpile)"); + D --> E + E --> |Parse Error| F(Failed Queries) + E --> G{Skip Validations} + G --> |Yes| H(Save Output) + G --> |No| I{Validate} + I --> |Success| H + I --> |Fail| J(Flag, Capture) + J --> H +
+ + + """ + + components.html(mermaid_html, height=1000) + + st.markdown( + """ + The **Reconcile process** works in the following steps: + + 1️⃣ **Extract Data**: Fetches data from both the **source system** (e.g., Snowflake, Oracle) and **Databricks target table**. + 2️⃣ **Transform & Normalize**: Applies **data transformations** and aligns schema differences. + 3️⃣ **Compare Data**: Performs **row-level, column-level, and aggregated** comparisons. + 4️⃣ **Generate Report**: Identifies **mismatched records**, missing values, and summary statistics. + + 🎯 **Result:** A clear report showing how well the migrated data matches the original source. + """ + ) + + st.header("Why is Data Reconciliation Important?") + options = { + "Prevent incorrect reports due to data mismatches": "Reconciliation ensures **accurate insights** by eliminating errors.", + "Ensure compliance with industry regulations": "Regulatory standards often require **data integrity checks**.", + "Detect unexpected data loss during migration": "Helps catch missing records **before they impact business decisions**.", + "Improve trust in the migration process": "Ensures **stakeholders** have confidence in the new system.", + } + + selected_reason = st.radio("📌 Select a reason to learn more:", list(options.keys())) + st.success(options[selected_reason]) + + st.header("Common Data Reconciliation Challenges") + st.markdown( + """ + Even with **automated tools** like Reconcile, data validation presents challenges: + + - ⚠️ **Schema Drift**: Unexpected changes in column types or structures. + - 🔄 **Data Sync Issues**: Time-lagged data updates leading to inconsistencies. + - 📉 **Large Data Volume**: Millions of rows requiring optimized comparison techniques. + - 🔍 **Precision vs. Performance**: Balancing speed with deep data validation. + + Reconcile tackles these challenges with **efficient algorithms, distributed processing, and flexible configurations**. + """ + ) + + st.header("Explore More") + st.markdown( + "🔗 **Check out the full project on GitHub:** [Databricks Labs Remorph](https://github.com/databrickslabs/remorph)" + ) + + st.markdown("💡 **Need more details?** Reach out to the community and contribute to the project!") diff --git a/src/databricks/labs/remorph/app/src/routes/recon_executor.py b/src/databricks/labs/remorph/app/src/routes/recon_executor.py new file mode 100644 index 0000000000..bbba095556 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/routes/recon_executor.py @@ -0,0 +1,31 @@ +import streamlit as st # type: ignore + + +def load_configurations(): + # This function should load available configurations + # For demonstration, we use a static list + return ["Config 1", "Config 2", "Config 3"] + + +def run_reconciliation(selected_config): + # This function should run the reconciliation process based on the selected configuration + st.success(f"Running reconciliation with {selected_config}") + + +def main(): + st.title("Recon Executor") + st.markdown("### W.I.P") + + # Load available configurations + configurations = load_configurations() + + # Select a configuration + selected_config = st.selectbox("Choose a configuration", configurations) + + # Button to run reconciliation + if st.button("Run Reconciliation"): + run_reconciliation(selected_config) + + +if __name__ == "__main__": + main() diff --git a/src/databricks/labs/remorph/app/src/routes/secret_manager.py b/src/databricks/labs/remorph/app/src/routes/secret_manager.py new file mode 100644 index 0000000000..bfb0a33390 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/routes/secret_manager.py @@ -0,0 +1,7 @@ +import streamlit as st # type: ignore + + +def main(): + st.title("Secret Manager") + st.write("Manage your secrets securely here.") + st.text_input("Enter your secret key:") diff --git a/src/databricks/labs/remorph/app/src/services/__init__.py b/src/databricks/labs/remorph/app/src/services/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/services/data_service.py b/src/databricks/labs/remorph/app/src/services/data_service.py new file mode 100644 index 0000000000..5d7a9e83fb --- /dev/null +++ b/src/databricks/labs/remorph/app/src/services/data_service.py @@ -0,0 +1,31 @@ +# import os +# import streamlit as st +# +# +# def save_json(json_data, path: str): +# try: +# import dbutils +# +# is_databricks = True +# except ImportError: +# is_databricks = False +# path = path if path.endswith('/') else f'{path}/' +# full_path_with_extension = f'{path}config.json' +# if is_databricks: +# try: +# dbutils.fs.put(full_path_with_extension, json_data, overwrite=True) +# print(f"JSON file successfully saved to Databricks workspace path: {path}") +# st.success(f"JSON file successfully saved to Databricks workspace path: {path}") +# except Exception as e: +# print(f"Failed to save JSON to Databricks workspace: {e}") +# st.exception(e) +# else: +# # Local environment: Save to local file system +# try: +# os.makedirs(os.path.dirname(path), exist_ok=True) +# with open(full_path_with_extension, "w") as file: +# file.write(json_data) +# print(f"JSON file successfully saved to local path: {full_path_with_extension}") +# st.success(f"JSON file successfully saved to locaL path: {full_path_with_extension}") +# except Exception as e: +# print(f"Failed to save JSON locally: {e}") diff --git a/src/databricks/labs/remorph/app/src/services/spark_service.py b/src/databricks/labs/remorph/app/src/services/spark_service.py new file mode 100644 index 0000000000..63ba641db7 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/services/spark_service.py @@ -0,0 +1,85 @@ +import streamlit as st # type: ignore +from databricks.connect.session import DatabricksSession +from pyspark.sql import functions as F +from ..utils.schemas.config_schema import config_schema +from ..utils.query_loader import load_query +from ..config.settings import settings + + +def create_spark_session(cluster_id): + spark = DatabricksSession.builder.clusterId(cluster_id).getOrCreate() + return spark + + +def save_config_to_delta(table_details): + existing_df = settings.spark.table(f"{settings.REMORPH_METADATA_SCHEMA}.{settings.RECON_CONFIG_TABLE_NAME}") + max_config_id = existing_df.agg(F.max("config_id")).collect()[0][0] + new_config_id = (max_config_id or 0) + 1 + table_details["config_id"] = new_config_id + + df = settings.spark.createDataFrame([table_details], schema=config_schema) + # df.show(truncate=False) + try: + df.write.format("delta").mode("append").saveAsTable( + f"{settings.REMORPH_METADATA_SCHEMA}." f"{settings.RECON_CONFIG_TABLE_NAME}" + ) + st.success("Config saved successfully") + except Exception as e: + st.error(f"Error saving config: {e}") + + +def run_ddl(ddl, table_name): + if settings.spark.catalog.tableExists(table_name): + pass + else: + settings.spark.sql(ddl) + st.success(f"Table {table_name} created successfully") + + +def initialize_tables(): + with st.spinner("Ensuring config and status tables are present. May take a while if cluster is not up"): + try: + create_config_table_ddl = load_query( + "ddls", + "create_config_table", + RECON_CONFIG_TABLE_NAME=f"{settings.REMORPH_METADATA_SCHEMA}." f"{settings.RECON_CONFIG_TABLE_NAME}", + ) + + create_status_table_ddl = load_query( + "ddls", + "create_status_table", + RECON_JOB_RUN_DETAILS_TABLE_NAME=f"{settings.REMORPH_METADATA_SCHEMA}." + f"{settings.RECON_JOB_RUN_DETAILS_TABLE_NAME}", + ) + + run_ddl( + create_config_table_ddl, f"{settings.REMORPH_METADATA_SCHEMA}." f"{settings.RECON_CONFIG_TABLE_NAME}" + ) + + run_ddl( + create_status_table_ddl, + f"{settings.REMORPH_METADATA_SCHEMA}." f"{settings.RECON_JOB_RUN_DETAILS_TABLE_NAME}", + ) + except Exception: + st.error( + f"Please check if these " + f"tables are present: {settings.RECON_CONFIG_TABLE_NAME} and " + f"{settings.RECON_JOB_RUN_DETAILS_TABLE_NAME}" + ) + return + st.session_state.tables_initialized = True + + +def initialize_app(): + if "tables_initialized" not in st.session_state: + initialize_tables() + + +def fetch_dataframe(sql): + df = settings.spark.sql(sql).toPandas() + return df + + +def fetch_list_from_queries(sql): + items = [item[0] for item in settings.spark.sql(sql).toPandas().values.tolist()] + return items or None diff --git a/src/databricks/labs/remorph/app/src/utils/__init__.py b/src/databricks/labs/remorph/app/src/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/utils/pretty_print_configs.py b/src/databricks/labs/remorph/app/src/utils/pretty_print_configs.py new file mode 100644 index 0000000000..178fedd13c --- /dev/null +++ b/src/databricks/labs/remorph/app/src/utils/pretty_print_configs.py @@ -0,0 +1,8 @@ +# utils.py +import numpy as np + + +def create_collapsible_json(data): + if isinstance(data, np.ndarray): + data = data.tolist() + return str(data) diff --git a/src/databricks/labs/remorph/app/src/utils/query_loader.py b/src/databricks/labs/remorph/app/src/utils/query_loader.py new file mode 100644 index 0000000000..86a2d62ab6 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/utils/query_loader.py @@ -0,0 +1,34 @@ +import os + + +def load_query(category, query_name, **kwargs): + """ + Loads an SQL query from the 'queries' folder (ddls or dmls) and formats it with provided parameters. + + Args: + category (str): 'ddls' or 'dmls' (subdirectory in 'queries') + query_name (str): The name of the SQL file without extension. + kwargs: Dynamic parameters to format inside the query. + + Returns: + str: The formatted SQL query. + + Raises: + FileNotFoundError: If the SQL file is not found. + ValueError: If an invalid category is provided. + """ + + valid_categories = {"ddls", "dmls"} + + if category not in valid_categories: + raise ValueError(f"Invalid category '{category}'. Choose from {valid_categories}") + + query_path = os.path.join(os.path.dirname(__file__), "..", "queries", category, f"{query_name}.sql") + + if not os.path.exists(query_path): + raise FileNotFoundError(f"Query file '{query_name}.sql' not found in '{category}'") + + with open(query_path, "r", encoding="utf-8") as file: + query = file.read() + + return query.format(**kwargs) diff --git a/src/databricks/labs/remorph/app/src/utils/schemas/__init__.py b/src/databricks/labs/remorph/app/src/utils/schemas/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/src/utils/schemas/config_schema.py b/src/databricks/labs/remorph/app/src/utils/schemas/config_schema.py new file mode 100644 index 0000000000..8180377289 --- /dev/null +++ b/src/databricks/labs/remorph/app/src/utils/schemas/config_schema.py @@ -0,0 +1,38 @@ +from pyspark.sql.types import StructType, StructField, StringType, ArrayType, IntegerType, MapType + +# Define the schema for the transformations array +transformations_schema = ArrayType( + StructType( + [ + StructField("column_name", StringType(), True), + StructField("source", StringType(), True), + StructField("target", StringType(), True), + ] + ) +) + +# Define the schema for the tables array +tables_schema = ArrayType( + StructType( + [ + StructField("source_name", StringType(), True), + StructField("target_name", StringType(), True), + StructField("drop_columns", ArrayType(StringType()), True), + StructField("join_columns", ArrayType(StringType()), True), + StructField("transformations", transformations_schema, True), + StructField("jdbc_reader_options", MapType(StringType(), StringType()), True), + ] + ) +) + +# Define the overall schema +config_schema = StructType( + [ + StructField("config_id", IntegerType(), False), + StructField("source_catalog", StringType(), False), + StructField("source_schema", StringType(), False), + StructField("target_catalog", StringType(), False), + StructField("target_schema", StringType(), False), + StructField("tables", tables_schema, False), + ] +) diff --git a/src/databricks/labs/remorph/app/src/utils/schemas/job_run_details_schema.py b/src/databricks/labs/remorph/app/src/utils/schemas/job_run_details_schema.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/tests/__init__.py b/src/databricks/labs/remorph/app/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/databricks/labs/remorph/app/tests/test_config/test_settings.py b/src/databricks/labs/remorph/app/tests/test_config/test_settings.py new file mode 100644 index 0000000000..9ff6517c71 --- /dev/null +++ b/src/databricks/labs/remorph/app/tests/test_config/test_settings.py @@ -0,0 +1,17 @@ +from ...src.config.settings import settings + + +def test_databricks_cluster_id(): + assert settings.DATABRICKS_CLUSTER_ID is not None + + +def test_remorph_metadata_schema(): + assert settings.REMORPH_METADATA_SCHEMA is not None + + +def test_recon_config_table_name(): + assert settings.RECON_CONFIG_TABLE_NAME is not None + + +# def test_recon_job_run_details_table_name(): +# assert settings.RECON_JOB_RUN_DETAILS_TABLE_NAME is not None From f63d238c982188e48a2dfdec3a755606689e40d4 Mon Sep 17 00:00:00 2001 From: Kushagra Parashar Date: Tue, 11 Feb 2025 10:59:02 +0530 Subject: [PATCH 2/4] Test GPG after changing primary email --- .../labs/remorph/app/src/routes/home.py | 2 +- .../lsp_transpiler/test-lsp-server.log | 49 +++++++++++++++++++ ...pile_errors_2025_02_10_20_04_33_827971.lst | 1 + ...pile_errors_2025_02_10_20_12_00_970479.lst | 1 + 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/resources/lsp_transpiler/test-lsp-server.log create mode 100644 tests/resources/lsp_transpiler/transpile_errors_2025_02_10_20_04_33_827971.lst create mode 100644 tests/resources/lsp_transpiler/transpile_errors_2025_02_10_20_12_00_970479.lst diff --git a/src/databricks/labs/remorph/app/src/routes/home.py b/src/databricks/labs/remorph/app/src/routes/home.py index 15f22332bd..7dd0c5c044 100644 --- a/src/databricks/labs/remorph/app/src/routes/home.py +++ b/src/databricks/labs/remorph/app/src/routes/home.py @@ -4,7 +4,7 @@ def main(): # Title and Introduction - st.title("🔍 Remorph - Reconcile") + st.title("Remorph - Reconcile") st.markdown("### A powerful tool for **automated data reconciliation** in Databricks.") st.header("What is Reconcile?") diff --git a/tests/resources/lsp_transpiler/test-lsp-server.log b/tests/resources/lsp_transpiler/test-lsp-server.log new file mode 100644 index 0000000000..26666c0c45 --- /dev/null +++ b/tests/resources/lsp_transpiler/test-lsp-server.log @@ -0,0 +1,49 @@ +INFO:pygls.feature_manager:Registered builtin feature exit +INFO:pygls.feature_manager:Registered builtin feature initialize +INFO:pygls.feature_manager:Registered builtin feature initialized +INFO:pygls.feature_manager:Registered builtin feature notebookDocument/didChange +INFO:pygls.feature_manager:Registered builtin feature notebookDocument/didClose +INFO:pygls.feature_manager:Registered builtin feature notebookDocument/didOpen +INFO:pygls.feature_manager:Registered builtin feature $/setTrace +INFO:pygls.feature_manager:Registered builtin feature shutdown +INFO:pygls.feature_manager:Registered builtin feature textDocument/didChange +INFO:pygls.feature_manager:Registered builtin feature textDocument/didClose +INFO:pygls.feature_manager:Registered builtin feature textDocument/didOpen +INFO:pygls.feature_manager:Registered builtin feature window/workDoneProgress/cancel +INFO:pygls.feature_manager:Registered builtin feature workspace/didChangeWorkspaceFolders +INFO:pygls.feature_manager:Registered builtin feature workspace/executeCommand +INFO:pygls.feature_manager:Registered "initialize" with options "None" +INFO:pygls.feature_manager:Registered "textDocument/didOpen" with options "None" +INFO:pygls.feature_manager:Registered "textDocument/didClose" with options "None" +INFO:pygls.feature_manager:Registered "document/transpileToDatabricks" with options "None" +DEBUG:__main__:SOME_ENV=abc +DEBUG:__main__:sys.args=['lsp_server.py', '--stuff=12'] +INFO:pygls.server:Starting async IO server +DEBUG:asyncio:Using selector: KqueueSelector +DEBUG:pygls.server:Content length: 301 +DEBUG:pygls.protocol.json_rpc:Request message received. +INFO:pygls.protocol.language_server:Language server initialized InitializeParams(capabilities=ClientCapabilities(workspace=None, text_document=None, notebook_document=None, window=None, general=None, experimental=None), process_id=None, client_info=None, locale=None, root_path='.', root_uri=None, initialization_options={'remorph': {'source-dialect': 'snowflake'}, 'custom': {'whatever': 'xyz'}}, trace=None, work_done_token=None, workspace_folders=None) +DEBUG:pygls.protocol.language_server:Server capabilities: {"positionEncoding": "utf-16", "textDocumentSync": {"openClose": true, "change": 2, "save": false}, "executeCommandProvider": {"commands": []}, "workspace": {"workspaceFolders": {"supported": true, "changeNotifications": true}, "fileOperations": {}}} +INFO:pygls.protocol.json_rpc:Sending data: {"id": "d27a418d-1dbf-4279-aa39-0cfb419a208f", "jsonrpc": "2.0", "result": {"capabilities": {"positionEncoding": "utf-16", "textDocumentSync": {"openClose": true, "change": 2, "save": false}, "executeCommandProvider": {"commands": []}, "workspace": {"workspaceFolders": {"supported": true, "changeNotifications": true}, "fileOperations": {}}}, "serverInfo": {"name": "test-lsp-server", "version": "v0.1"}}} +DEBUG:__main__:dialect=snowflake +DEBUG:__main__:whatever=xyz +DEBUG:pygls.protocol.json_rpc:Sending request with id "e9005efb-b04c-43a1-8ebd-db929eb509c8": client/registerCapability RegistrationParams(registrations=[Registration(id='6006fa53-c96f-4d02-80c8-56ed086963a5', method='document/transpileToDatabricks', register_options=None)]) +INFO:pygls.protocol.json_rpc:Sending data: {"id": "e9005efb-b04c-43a1-8ebd-db929eb509c8", "params": {"registrations": [{"id": "6006fa53-c96f-4d02-80c8-56ed086963a5", "method": "document/transpileToDatabricks"}]}, "method": "client/registerCapability", "jsonrpc": "2.0"} +DEBUG:pygls.server:Content length: 274 +DEBUG:pygls.protocol.json_rpc:Notification message received. +DEBUG:__main__:open-document-uri=file:///Users/kushagra.parashar/IdeaProjects/remorph/tests/resources/lsp_transpiler/internal.sql +DEBUG:pygls.server:Content length: 80 +DEBUG:pygls.protocol.json_rpc:Response message received. +DEBUG:pygls.protocol.json_rpc:Received result for message "e9005efb-b04c-43a1-8ebd-db929eb509c8": None +DEBUG:pygls.server:Content length: 248 +DEBUG:pygls.protocol.json_rpc:Request message received. +INFO:pygls.protocol.json_rpc:Sending data: {"id": "a2225697-3269-426b-9b00-3ee6da142767", "jsonrpc": "2.0", "result": {"uri": "file:///Users/kushagra.parashar/IdeaProjects/remorph/tests/resources/lsp_transpiler/internal.sql", "changes": [{"range": {"start": {"line": 0, "character": 0}, "end": {"line": 2, "character": 0}}, "newText": "create table stuff(name varchar(12))\n"}], "diagnostics": [{"range": {"start": {"line": 0, "character": 0}, "end": {"line": 2, "character": 0}}, "message": "Something went wrong", "severity": 2, "code": "SOME_ERROR_CODE"}]}} +DEBUG:pygls.server:Content length: 190 +DEBUG:pygls.protocol.json_rpc:Notification message received. +DEBUG:__main__:close-document-uri=file:///Users/kushagra.parashar/IdeaProjects/remorph/tests/resources/lsp_transpiler/internal.sql +DEBUG:pygls.server:Content length: 86 +DEBUG:pygls.protocol.json_rpc:Request message received. +INFO:pygls.protocol.json_rpc:Sending data: {"id": "93f3df4c-1f36-468d-ace6-f0e11371d552", "jsonrpc": "2.0", "result": null} +DEBUG:pygls.server:Content length: 36 +DEBUG:pygls.protocol.json_rpc:Notification message received. +INFO:pygls.server:Shutting down the server diff --git a/tests/resources/lsp_transpiler/transpile_errors_2025_02_10_20_04_33_827971.lst b/tests/resources/lsp_transpiler/transpile_errors_2025_02_10_20_04_33_827971.lst new file mode 100644 index 0000000000..ca742cb667 --- /dev/null +++ b/tests/resources/lsp_transpiler/transpile_errors_2025_02_10_20_04_33_827971.lst @@ -0,0 +1 @@ +TranspileError(code=UNSUPPORTED_LCA, kind=ANALYSIS, severity=ERROR, path='/Users/kushagra.parashar/IdeaProjects/remorph/tests/resources/lsp_transpiler/unsupported_lca.sql', message='LCA conversion not supported') diff --git a/tests/resources/lsp_transpiler/transpile_errors_2025_02_10_20_12_00_970479.lst b/tests/resources/lsp_transpiler/transpile_errors_2025_02_10_20_12_00_970479.lst new file mode 100644 index 0000000000..ca742cb667 --- /dev/null +++ b/tests/resources/lsp_transpiler/transpile_errors_2025_02_10_20_12_00_970479.lst @@ -0,0 +1 @@ +TranspileError(code=UNSUPPORTED_LCA, kind=ANALYSIS, severity=ERROR, path='/Users/kushagra.parashar/IdeaProjects/remorph/tests/resources/lsp_transpiler/unsupported_lca.sql', message='LCA conversion not supported') From d091fa59a7c1c9cffb4fa4c46a854c753361f09d Mon Sep 17 00:00:00 2001 From: Kushagra Parashar Date: Thu, 27 Feb 2025 16:17:11 +0530 Subject: [PATCH 3/4] Fixed table creation issue --- .../labs/remorph/app/src/config/settings.py | 2 +- .../labs/remorph/app/src/services/spark_service.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/databricks/labs/remorph/app/src/config/settings.py b/src/databricks/labs/remorph/app/src/config/settings.py index 4e11e81930..f4a49846b9 100644 --- a/src/databricks/labs/remorph/app/src/config/settings.py +++ b/src/databricks/labs/remorph/app/src/config/settings.py @@ -9,7 +9,7 @@ def __init__(self): self.DATABRICKS_CLUSTER_ID = os.getenv('DATABRICKS_CLUSTER_ID') self.REMORPH_METADATA_SCHEMA = os.getenv('REMORPH_METADATA_SCHEMA') self.RECON_CONFIG_TABLE_NAME = os.getenv('RECON_CONFIG_TABLE_NAME') - self.RECON_JOB_RUN_DETAILS_TABLE_NAME = os.getenv('RECON_JOqB_RUN_DETAILS_TABLE_NAME') + self.RECON_JOB_RUN_DETAILS_TABLE_NAME = os.getenv('RECON_JOB_RUN_DETAILS_TABLE_NAME') # self.LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO') self.spark = DatabricksSession.builder.clusterId(self.DATABRICKS_CLUSTER_ID).getOrCreate() diff --git a/src/databricks/labs/remorph/app/src/services/spark_service.py b/src/databricks/labs/remorph/app/src/services/spark_service.py index 63ba641db7..9865ae937f 100644 --- a/src/databricks/labs/remorph/app/src/services/spark_service.py +++ b/src/databricks/labs/remorph/app/src/services/spark_service.py @@ -61,11 +61,12 @@ def initialize_tables(): f"{settings.REMORPH_METADATA_SCHEMA}." f"{settings.RECON_JOB_RUN_DETAILS_TABLE_NAME}", ) except Exception: - st.error( - f"Please check if these " - f"tables are present: {settings.RECON_CONFIG_TABLE_NAME} and " - f"{settings.RECON_JOB_RUN_DETAILS_TABLE_NAME}" - ) + st.error(f'Error creating tables. Please check if "{settings.REMORPH_METADATA_SCHEMA}" schema exists') + # st.error( + # f"Please check if these " + # f"tables are present: {settings.RECON_CONFIG_TABLE_NAME} and " + # f"{settings.RECON_JOB_RUN_DETAILS_TABLE_NAME}" + # ) return st.session_state.tables_initialized = True From 5d9d3e095109bf2f30f1142d483964b83b4352be Mon Sep 17 00:00:00 2001 From: Kushagra Parashar Date: Tue, 1 Apr 2025 19:09:00 +0530 Subject: [PATCH 4/4] Added Readme to install Reconcile app --- src/databricks/labs/remorph/app/README.md | 55 ++++++++++++++++++ src/databricks/labs/remorph/app/app.yaml | 9 ++- .../app/assets/app_service_principle.png | Bin 0 -> 21871 bytes .../remorph/app/assets/catalog_permission.png | Bin 0 -> 68360 bytes .../labs/remorph/app/assets/yaml_path.png | Bin 0 -> 15665 bytes .../remorph/app/src/services/spark_service.py | 3 +- 6 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 src/databricks/labs/remorph/app/assets/app_service_principle.png create mode 100644 src/databricks/labs/remorph/app/assets/catalog_permission.png create mode 100644 src/databricks/labs/remorph/app/assets/yaml_path.png diff --git a/src/databricks/labs/remorph/app/README.md b/src/databricks/labs/remorph/app/README.md index e69de29bb2..4e8e6624a6 100644 --- a/src/databricks/labs/remorph/app/README.md +++ b/src/databricks/labs/remorph/app/README.md @@ -0,0 +1,55 @@ +# Pre requisites: + +1. Setup the CLI. +2. Clone the Remorph repo +3. Checkout feature/reconcile-databricks-app +4. Open app.yaml in the app module + +![YAML Path](assets/yaml_path.png) + +5. Update the REMORPH_METADATA_SCHEMA value with your Remorph reconcile schema (Use the same schema you used while installing Remorph). +6. If you dont have remorph, feel free to use any other where you have access. + + + + +# Steps to deploy app + +1. Create the app + +>> databricks apps create + +2. Sync the app directory to local path and upload the files to Workspace.First navigate to app directory + +>> cd src/databricks/labs/remorph/app/ + + +Then, upload the app files to workspace. Do this in a new terminal tab an leave it open to sync. + +>> databricks sync --watch . /Workspace/Users/user..name@databricks.com/ + + +3. Deploy the app + +>> databricks apps deploy +--source-code-path /Workspace/Users/user..name@databricks.com/ + + + + + +# Fix permission issues +(TODO: Do this programmatically) + +1. Copy service principle ID of your app. Goto Compute > Apps Your app > Authorization tab + +![Fix permission](assets/app_service_principle.png) + + + +2Provide this service principle access to your remorph Schema. Data editor access should be fine: + +![Catalog permission](assets/catalog_permission.png) + +Once done, launch the app and you should see a message that some tables have been created. If yes then app is successfully deployed. + diff --git a/src/databricks/labs/remorph/app/app.yaml b/src/databricks/labs/remorph/app/app.yaml index 3779bca52d..0c737b6fb0 100644 --- a/src/databricks/labs/remorph/app/app.yaml +++ b/src/databricks/labs/remorph/app/app.yaml @@ -9,5 +9,10 @@ env: value: "kushagra_remorph.reconcile" - name: STREAMLIT_BROWSER_GATHER_USAGE_STATS value: "false" - - name: RECON_APP_RESOURCES - value: /Workspace/Users/kushagra.parashar@databricks.com/Swiggy/ + - name: DATABRICKS_CLUSTER_ID + value: "0709-132523-cnhxf2p6" + - name: RECON_CONFIG_TABLE_NAME + value: "recon_app_config_table" + - name: RECON_JOB_RUN_DETAILS_TABLE_NAME + value: "job_run_details" + diff --git a/src/databricks/labs/remorph/app/assets/app_service_principle.png b/src/databricks/labs/remorph/app/assets/app_service_principle.png new file mode 100644 index 0000000000000000000000000000000000000000..53a3137e26bf525937a8b6130fb36c6646611b29 GIT binary patch literal 21871 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 literal 0 HcmV?d00001 diff --git a/src/databricks/labs/remorph/app/assets/yaml_path.png b/src/databricks/labs/remorph/app/assets/yaml_path.png new file mode 100644 index 0000000000000000000000000000000000000000..84f5023ea0fe2a1f3a4f1432951a53b2e853a028 GIT binary patch literal 15665 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