diff --git a/samples/databricks.ipynb b/samples/databricks.ipynb
index aadcbc5..93ee107 100644
--- a/samples/databricks.ipynb
+++ b/samples/databricks.ipynb
@@ -159,6 +159,59 @@
" print(f\"\\n✗ Found {mismatches} mismatches - investigate!\")"
]
},
+ {
+ "cell_type": "markdown",
+ "source": "## Create Analytics Dashboard with Detokenization\n\nNow let's create a simple dashboard that demonstrates using the detokenize function in SQL queries for analytics. This shows how your BI tools and dashboards can work seamlessly with Skyflow-protected data.\n\n**Key Benefits:**\n- Query tokenized data tables directly (fast, no PII exposure)\n- Selectively detokenize only when needed (e.g., for display or domain analysis)\n- Use standard SQL with batched detokenization (efficient API usage)\n- Perfect for Databricks SQL dashboards, Tableau, PowerBI, etc.",
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "source": "# ============================================================================\n# Dashboard Query 1: Email Domain Distribution\n# ============================================================================\n# This query detokenizes emails to analyze which email domains are most common\n\nprint(\"=\" * 70)\nprint(\"Dashboard: Email Domain Distribution\")\nprint(\"=\" * 70)\nprint(\"Demonstrating: Detokenize → Extract domain → Aggregate\\n\")\n\ndomain_analysis = spark.sql(f\"\"\"\n WITH detokenized AS (\n SELECT\n user_id,\n username,\n {CATALOG}.{SCHEMA}.skyflow_detokenize(email_token) as email\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n ),\n domains AS (\n SELECT\n SUBSTRING_INDEX(email, '@', -1) as email_domain,\n COUNT(*) as user_count\n FROM detokenized\n GROUP BY email_domain\n ORDER BY user_count DESC\n )\n SELECT * FROM domains\n\"\"\")\n\ndisplay(domain_analysis)\n\nprint(\"\\n✓ Email domain analysis complete\")\nprint(\" This query batched detokenization of all emails efficiently\")\n\n# ============================================================================\n# Dashboard Query 2: Recent User Activity with Selective Detokenization\n# ============================================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"Dashboard: Recent User Activity\")\nprint(\"=\" * 70)\nprint(\"Demonstrating: Show tokens by default, detokenize only on demand\\n\")\n\nrecent_users = spark.sql(f\"\"\"\n SELECT\n user_id,\n username,\n email_token,\n {CATALOG}.{SCHEMA}.skyflow_detokenize(email_token) as email_plaintext,\n DATE(created_at) as registration_date\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n ORDER BY created_at DESC\n LIMIT 20\n\"\"\")\n\ndisplay(recent_users)\n\nprint(\"\\n✓ Recent user activity dashboard complete\")\nprint(\" Shows both tokenized (for auditing) and detokenized (for display) values\")\n\n# ============================================================================\n# Dashboard Query 3: User Summary Statistics\n# ============================================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"Dashboard: User Summary Statistics\")\nprint(\"=\" * 70)\nprint(\"Demonstrating: Aggregate analytics without detokenization\\n\")\n\nsummary_stats = spark.sql(f\"\"\"\n SELECT\n COUNT(*) as total_users,\n COUNT(DISTINCT email_token) as unique_emails,\n DATE(MIN(created_at)) as first_registration,\n DATE(MAX(created_at)) as last_registration\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n\"\"\")\n\ndisplay(summary_stats)\n\nprint(\"\\n✓ Summary statistics complete\")\nprint(\" This query ran entirely on tokenized data - no detokenization needed!\")\nprint(\"\\n\" + \"=\" * 70)\nprint(\"Dashboard Created Successfully!\")\nprint(\"=\" * 70)\nprint(\"\\n💡 Key Insights:\")\nprint(\" 1. Detokenization is batched automatically for efficiency\")\nprint(\" 2. You can mix tokenized and detokenized columns in the same query\")\nprint(\" 3. Many analytics queries don't need detokenization at all\")\nprint(\" 4. Use Unity Catalog permissions to control who can see plaintext data\")\nprint(\"\\n🎯 BI Tool Integration:\")\nprint(\" - These queries can be saved as Databricks SQL dashboards\")\nprint(\" - Connect Tableau, PowerBI, or other tools to the detokenized view\")\nprint(\" - Access control ensures only authorized users see plaintext PII\")",
+ "metadata": {},
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": "## Create Interactive Dashboard Visualizations\n\nNow let's create visual charts from our queries. These visualizations can be:\n- Viewed interactively in the notebook\n- Scheduled to run automatically (Databricks Jobs)\n- Shared with stakeholders via notebook links\n- Exported to Databricks SQL Dashboards\n\n**Note:** After running the cell below, click the chart icons in the output to configure visualization types (bar charts, pie charts, etc.).",
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "source": "# ============================================================================\n# Create Dashboard Visualizations\n# ============================================================================\n\nprint(\"=\" * 70)\nprint(\"Creating Interactive Dashboard Visualizations\")\nprint(\"=\" * 70)\nprint()\n\n# Visualization 1: Email Domain Distribution (Bar Chart)\nprint(\"📊 Visualization 1: Email Domain Distribution\")\nprint(\"-\" * 70)\n\ndomain_viz = spark.sql(f\"\"\"\n WITH detokenized AS (\n SELECT\n user_id,\n {CATALOG}.{SCHEMA}.skyflow_detokenize(email_token) as email\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n ),\n domains AS (\n SELECT\n SUBSTRING_INDEX(email, '@', -1) as email_domain,\n COUNT(*) as user_count\n FROM detokenized\n GROUP BY email_domain\n ORDER BY user_count DESC\n )\n SELECT * FROM domains\n\"\"\")\n\ndisplay(domain_viz)\nprint(\"💡 Tip: Click the chart icon above to visualize as a bar chart\")\nprint(\" X-axis: email_domain, Y-axis: user_count\\n\")\n\n# Visualization 2: User Registration Timeline\nprint(\"📊 Visualization 2: User Registration Timeline\")\nprint(\"-\" * 70)\n\ntimeline_viz = spark.sql(f\"\"\"\n SELECT\n DATE(created_at) as registration_date,\n COUNT(*) as new_users\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n GROUP BY DATE(created_at)\n ORDER BY registration_date\n\"\"\")\n\ndisplay(timeline_viz)\nprint(\"💡 Tip: Click the chart icon above to visualize as a line chart\")\nprint(\" X-axis: registration_date, Y-axis: new_users\\n\")\n\n# Visualization 3: Summary Metrics\nprint(\"📊 Visualization 3: Key Metrics Summary\")\nprint(\"-\" * 70)\n\nmetrics_viz = spark.sql(f\"\"\"\n SELECT\n 'Total Users' as metric,\n CAST(COUNT(*) AS STRING) as value\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n UNION ALL\n SELECT\n 'Unique Emails' as metric,\n CAST(COUNT(DISTINCT email_token) AS STRING) as value\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n UNION ALL\n SELECT\n 'Days Active' as metric,\n CAST(DATEDIFF(MAX(created_at), MIN(created_at)) AS STRING) as value\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n\"\"\")\n\ndisplay(metrics_viz)\nprint(\"💡 This shows key performance indicators (KPIs)\\n\")\n\n# Visualization 4: Sample User Records with Detokenization\nprint(\"📊 Visualization 4: Recent Users (with PII)\")\nprint(\"-\" * 70)\nprint(\"⚠️ This visualization detokenizes PII - ensure proper access controls!\")\n\nrecent_users_viz = spark.sql(f\"\"\"\n SELECT\n user_id,\n username,\n {CATALOG}.{SCHEMA}.skyflow_detokenize(email_token) as email,\n DATE(created_at) as registration_date\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n ORDER BY created_at DESC\n LIMIT 10\n\"\"\")\n\ndisplay(recent_users_viz)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"✓ Dashboard Visualizations Created Successfully!\")\nprint(\"=\" * 70)\nprint(\"\\n📌 Next Steps:\")\nprint(\" 1. Configure chart types by clicking the visualization icons\")\nprint(\" 2. Save this notebook and schedule it to run periodically\")\nprint(\" 3. Share the notebook URL with stakeholders\")\nprint(\" 4. Export specific queries to Databricks SQL for persistent dashboards\")\nprint(\"\\n🔒 Security Reminder:\")\nprint(\" - Use Unity Catalog permissions to control who can run this notebook\")\nprint(\" - Only authorized users should access visualizations with detokenized data\")\nprint(\" - Consider creating separate dashboards for tokenized vs. detokenized views\")",
+ "metadata": {},
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": "## Create Custom HTML Dashboard (Optional)\n\nFor a more polished look, you can create a custom HTML dashboard using `displayHTML()`. This is great for executive reports and stakeholder presentations.",
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "source": "# ============================================================================\n# Create Custom HTML Dashboard\n# ============================================================================\n\n# Fetch metrics data\nmetrics_data = spark.sql(f\"\"\"\n SELECT\n COUNT(*) as total_users,\n COUNT(DISTINCT email_token) as unique_emails,\n DATE(MIN(created_at)) as first_registration,\n DATE(MAX(created_at)) as last_registration\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n\"\"\").collect()[0]\n\n# Fetch domain distribution\ndomain_data = spark.sql(f\"\"\"\n WITH detokenized AS (\n SELECT\n user_id,\n {CATALOG}.{SCHEMA}.skyflow_detokenize(email_token) as email\n FROM {CATALOG}.{SCHEMA}.tokenized_users\n ),\n domains AS (\n SELECT\n SUBSTRING_INDEX(email, '@', -1) as email_domain,\n COUNT(*) as user_count\n FROM detokenized\n GROUP BY email_domain\n ORDER BY user_count DESC\n LIMIT 5\n )\n SELECT * FROM domains\n\"\"\").collect()\n\n# Build HTML dashboard\nhtml = f\"\"\"\n\n\n
\n \n\n\n \n \n \n
\n
\n
Total Users
\n
{metrics_data.total_users:,}
\n
\n
\n
Unique Emails
\n
{metrics_data.unique_emails:,}
\n
\n
\n
First Registration
\n
{metrics_data.first_registration}
\n
\n
\n
Latest Registration
\n
{metrics_data.last_registration}
\n
\n
\n \n
\n
Top Email Domains
\n {\"\".join([f'''\n
\n
{row.email_domain}
\n
\n
\n {row.user_count}\n
\n
\n
\n ''' for row in domain_data])}\n
\n \n \n
\n\n\n\"\"\"\n\ndisplayHTML(html)\n\nprint(\"✓ Custom HTML dashboard created successfully!\")\nprint(\"\\n💡 Benefits of HTML dashboards:\")\nprint(\" - Professional, polished appearance\")\nprint(\" - Fully customizable styling and branding\")\nprint(\" - Can be scheduled and emailed automatically\")\nprint(\" - Great for executive reports and stakeholder updates\")",
+ "metadata": {},
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": "## Publishing to Databricks SQL Dashboards\n\nTo create a persistent Databricks SQL Dashboard from these queries:\n\n### Method 1: Manual Export (Recommended)\n1. Navigate to **Databricks SQL** in your workspace\n2. Create a new **Query** for each visualization:\n - Copy the SQL query from the cells above\n - Save as a named query (e.g., \"User Email Domains\")\n3. Go to **Dashboards** → **Create Dashboard**\n4. Add your saved queries as widgets\n5. Configure visualizations (bar charts, line charts, etc.)\n6. Set up automatic refresh schedules\n\n### Method 2: Scheduled Notebook\n- Schedule this notebook to run on a regular cadence (hourly, daily, etc.)\n- Share the notebook URL with stakeholders\n- Users can view the latest results by opening the notebook\n\n### Method 3: Databricks Apps (New)\nIf you have access to Databricks Apps, you can create an interactive web application:\n- Export queries as REST API endpoints\n- Build a custom frontend with React/Vue/etc.\n- Deploy as a Databricks App for production use\n\n### Key Advantages of Databricks SQL Dashboards:\n- ✅ **Persistent** - Dashboards survive cluster restarts\n- ✅ **Scheduled refresh** - Automatic data updates\n- ✅ **Access control** - Fine-grained permissions via Unity Catalog\n- ✅ **Sharing** - Easy to share links with stakeholders\n- ✅ **Interactive** - Click to drill down, filter, and explore\n- ✅ **Alerting** - Set up alerts on metric thresholds\n\n### Security Best Practices:\n- Create separate dashboards for tokenized vs. detokenized views\n- Use Unity Catalog permissions to control access to detokenization functions\n- Audit who accesses dashboards with PII using Databricks audit logs\n- Consider using Skyflow's column-level redaction policies for fine-grained control",
+ "metadata": {}
+ },
+ {
+ "cell_type": "markdown",
+ "source": "## Generate Importable Dashboard JSON\n\nCreate a Databricks Lakeview dashboard file (`.lvdash.json`) that can be imported directly into Databricks SQL.",
+ "metadata": {}
+ },
+ {
+ "cell_type": "code",
+ "source": "import json\nimport os\n\n# ============================================================================\n# Generate Databricks Lakeview Dashboard JSON\n# ============================================================================\n\ndashboard_json = {\n \"datasets\": [\n {\n \"name\": \"skyflow_users_detokenized\",\n \"displayName\": \"Skyflow Users (Detokenized)\",\n \"queryLines\": [\n \"SELECT\\n\",\n \" user_id,\\n\",\n \" username,\\n\",\n f\" {CATALOG}.{SCHEMA}.skyflow_detokenize(email_token) as email,\\n\",\n \" email_token,\\n\",\n \" phone,\\n\",\n \" DATE(created_at) as registration_date,\\n\",\n \" created_at\\n\",\n f\"FROM {CATALOG}.{SCHEMA}.tokenized_users;\"\n ]\n },\n {\n \"name\": \"skyflow_email_domains\",\n \"displayName\": \"Email Domain Distribution\",\n \"queryLines\": [\n \"WITH detokenized AS (\\n\",\n \" SELECT\\n\",\n \" user_id,\\n\",\n f\" {CATALOG}.{SCHEMA}.skyflow_detokenize(email_token) as email\\n\",\n f\" FROM {CATALOG}.{SCHEMA}.tokenized_users\\n\",\n \"),\\n\",\n \"domains AS (\\n\",\n \" SELECT\\n\",\n \" SUBSTRING_INDEX(email, '@', -1) as email_domain,\\n\",\n \" COUNT(*) as user_count\\n\",\n \" FROM detokenized\\n\",\n \" GROUP BY email_domain\\n\",\n \")\\n\",\n \"SELECT * FROM domains\\n\",\n \"ORDER BY user_count DESC;\"\n ]\n },\n {\n \"name\": \"skyflow_summary_metrics\",\n \"displayName\": \"Summary Metrics\",\n \"queryLines\": [\n \"SELECT\\n\",\n \" COUNT(*) as total_users,\\n\",\n \" COUNT(DISTINCT email_token) as unique_emails,\\n\",\n \" DATE(MIN(created_at)) as first_registration,\\n\",\n \" DATE(MAX(created_at)) as last_registration,\\n\",\n \" DATEDIFF(MAX(created_at), MIN(created_at)) as days_active\\n\",\n f\"FROM {CATALOG}.{SCHEMA}.tokenized_users;\"\n ]\n },\n {\n \"name\": \"skyflow_registration_timeline\",\n \"displayName\": \"Registration Timeline\",\n \"queryLines\": [\n \"SELECT\\n\",\n \" DATE(created_at) as registration_date,\\n\",\n \" COUNT(*) as new_users\\n\",\n f\"FROM {CATALOG}.{SCHEMA}.tokenized_users\\n\",\n \"GROUP BY DATE(created_at)\\n\",\n \"ORDER BY registration_date;\"\n ]\n }\n ],\n \"pages\": [\n {\n \"name\": \"skyflow_analytics\",\n \"displayName\": \"Skyflow User Analytics\",\n \"layout\": [\n {\n \"widget\": {\n \"name\": \"header\",\n \"multilineTextboxSpec\": {\n \"lines\": [\n \"\\n\",\n \"# Skyflow User Analytics Dashboard\\n\",\n \"Secure analytics with on-demand detokenization powered by Skyflow + Databricks Unity Catalog\\n\",\n \"🔒 All PII is tokenized at rest and detokenized in batches for optimal performance\\n\"\n ]\n }\n },\n \"position\": {\n \"x\": 0,\n \"y\": 0,\n \"width\": 6,\n \"height\": 2\n }\n },\n {\n \"widget\": {\n \"name\": \"total_users_metric\",\n \"queries\": [\n {\n \"name\": \"main_query\",\n \"query\": {\n \"datasetName\": \"skyflow_summary_metrics\",\n \"fields\": [\n {\n \"name\": \"total_users\",\n \"expression\": \"`total_users`\"\n }\n ],\n \"disaggregated\": True\n }\n }\n ],\n \"spec\": {\n \"version\": 2,\n \"widgetType\": \"counter\",\n \"encodings\": {\n \"value\": {\n \"fieldName\": \"total_users\",\n \"displayName\": \"Total Users\"\n }\n },\n \"frame\": {\n \"title\": \"Total Users\",\n \"showTitle\": True\n }\n }\n },\n \"position\": {\n \"x\": 0,\n \"y\": 2,\n \"width\": 2,\n \"height\": 2\n }\n },\n {\n \"widget\": {\n \"name\": \"unique_emails_metric\",\n \"queries\": [\n {\n \"name\": \"main_query\",\n \"query\": {\n \"datasetName\": \"skyflow_summary_metrics\",\n \"fields\": [\n {\n \"name\": \"unique_emails\",\n \"expression\": \"`unique_emails`\"\n }\n ],\n \"disaggregated\": True\n }\n }\n ],\n \"spec\": {\n \"version\": 2,\n \"widgetType\": \"counter\",\n \"encodings\": {\n \"value\": {\n \"fieldName\": \"unique_emails\",\n \"displayName\": \"Unique Emails\"\n }\n },\n \"frame\": {\n \"title\": \"Unique Email Addresses\",\n \"showTitle\": True\n }\n }\n },\n \"position\": {\n \"x\": 2,\n \"y\": 2,\n \"width\": 2,\n \"height\": 2\n }\n },\n {\n \"widget\": {\n \"name\": \"days_active_metric\",\n \"queries\": [\n {\n \"name\": \"main_query\",\n \"query\": {\n \"datasetName\": \"skyflow_summary_metrics\",\n \"fields\": [\n {\n \"name\": \"days_active\",\n \"expression\": \"`days_active`\"\n }\n ],\n \"disaggregated\": True\n }\n }\n ],\n \"spec\": {\n \"version\": 2,\n \"widgetType\": \"counter\",\n \"encodings\": {\n \"value\": {\n \"fieldName\": \"days_active\",\n \"displayName\": \"Days Active\"\n }\n },\n \"frame\": {\n \"title\": \"Days of Activity\",\n \"showTitle\": True\n }\n }\n },\n \"position\": {\n \"x\": 4,\n \"y\": 2,\n \"width\": 2,\n \"height\": 2\n }\n },\n {\n \"widget\": {\n \"name\": \"email_domains_bar\",\n \"queries\": [\n {\n \"name\": \"main_query\",\n \"query\": {\n \"datasetName\": \"skyflow_email_domains\",\n \"fields\": [\n {\n \"name\": \"email_domain\",\n \"expression\": \"`email_domain`\"\n },\n {\n \"name\": \"user_count\",\n \"expression\": \"`user_count`\"\n }\n ],\n \"disaggregated\": True\n }\n }\n ],\n \"spec\": {\n \"version\": 3,\n \"widgetType\": \"bar\",\n \"encodings\": {\n \"x\": {\n \"fieldName\": \"email_domain\",\n \"scale\": {\n \"type\": \"categorical\",\n \"sort\": {\n \"by\": \"y-reversed\"\n }\n },\n \"displayName\": \"Email Domain\"\n },\n \"y\": {\n \"fieldName\": \"user_count\",\n \"scale\": {\n \"type\": \"quantitative\"\n },\n \"displayName\": \"Number of Users\"\n }\n },\n \"frame\": {\n \"title\": \"User Distribution by Email Domain\",\n \"showTitle\": True,\n \"description\": \"Shows which email domains are most common among registered users\"\n }\n }\n },\n \"position\": {\n \"x\": 0,\n \"y\": 4,\n \"width\": 3,\n \"height\": 4\n }\n },\n {\n \"widget\": {\n \"name\": \"registration_timeline\",\n \"queries\": [\n {\n \"name\": \"main_query\",\n \"query\": {\n \"datasetName\": \"skyflow_registration_timeline\",\n \"fields\": [\n {\n \"name\": \"registration_date\",\n \"expression\": \"`registration_date`\"\n },\n {\n \"name\": \"new_users\",\n \"expression\": \"`new_users`\"\n }\n ],\n \"disaggregated\": True\n }\n }\n ],\n \"spec\": {\n \"version\": 3,\n \"widgetType\": \"line\",\n \"encodings\": {\n \"x\": {\n \"fieldName\": \"registration_date\",\n \"scale\": {\n \"type\": \"temporal\"\n },\n \"displayName\": \"Registration Date\"\n },\n \"y\": {\n \"fieldName\": \"new_users\",\n \"scale\": {\n \"type\": \"quantitative\"\n },\n \"displayName\": \"New Users\"\n }\n },\n \"frame\": {\n \"title\": \"User Registration Timeline\",\n \"showTitle\": True,\n \"description\": \"Daily new user registrations over time\"\n }\n }\n },\n \"position\": {\n \"x\": 3,\n \"y\": 4,\n \"width\": 3,\n \"height\": 4\n }\n },\n {\n \"widget\": {\n \"name\": \"recent_users_table\",\n \"queries\": [\n {\n \"name\": \"main_query\",\n \"query\": {\n \"datasetName\": \"skyflow_users_detokenized\",\n \"fields\": [\n {\n \"name\": \"user_id\",\n \"expression\": \"`user_id`\"\n },\n {\n \"name\": \"username\",\n \"expression\": \"`username`\"\n },\n {\n \"name\": \"email\",\n \"expression\": \"`email`\"\n },\n {\n \"name\": \"email_token\",\n \"expression\": \"`email_token`\"\n },\n {\n \"name\": \"registration_date\",\n \"expression\": \"`registration_date`\"\n }\n ],\n \"disaggregated\": True\n }\n }\n ],\n \"spec\": {\n \"version\": 2,\n \"widgetType\": \"table\",\n \"encodings\": {\n \"columns\": [\n {\n \"fieldName\": \"user_id\",\n \"displayName\": \"User ID\"\n },\n {\n \"fieldName\": \"username\",\n \"displayName\": \"Username\"\n },\n {\n \"fieldName\": \"email\",\n \"displayName\": \"Email (Detokenized)\"\n },\n {\n \"fieldName\": \"email_token\",\n \"displayName\": \"Email Token\"\n },\n {\n \"fieldName\": \"registration_date\",\n \"displayName\": \"Registration Date\"\n }\n ]\n },\n \"frame\": {\n \"title\": \"Recent Users (with Detokenized PII)\",\n \"showTitle\": True,\n \"description\": \"⚠️ Contains detokenized PII - Access controlled via Unity Catalog permissions\"\n }\n }\n },\n \"position\": {\n \"x\": 0,\n \"y\": 8,\n \"width\": 6,\n \"height\": 5\n }\n }\n ],\n \"pageType\": \"PAGE_TYPE_CANVAS\"\n }\n ]\n}\n\n# Convert to formatted JSON string\ndashboard_json_str = json.dumps(dashboard_json, indent=2)\n\n# Save to local /tmp first\ntmp_path = \"/tmp/skyflow_analytics_dashboard.lvdash.json\"\nwith open(tmp_path, 'w') as f:\n f.write(dashboard_json_str)\n\nprint(\"=\" * 70)\nprint(\"✓ Databricks Lakeview Dashboard JSON Generated!\")\nprint(\"=\" * 70)\nprint(f\"\\n📊 Dashboard Configuration:\")\nprint(f\" - Name: Skyflow User Analytics\")\nprint(f\" - Datasets: {len(dashboard_json['datasets'])}\")\nprint(f\" - Widgets: {len(dashboard_json['pages'][0]['layout'])}\")\nprint(f\" - Catalog: {CATALOG}\")\nprint(f\" - Schema: {SCHEMA}\")\n\nprint(f\"\\n📥 How to Use This Dashboard:\")\nprint(f\"\\n Option 1: Copy JSON from output below (manual)\")\nprint(f\" Option 2: Automatically save to DBFS (uncomment Option 2 code)\")\nprint(f\" Option 3: Automatically import to Workspace (uncomment Option 3 code)\")\n\nprint(f\"\\n📄 Complete Dashboard JSON:\")\nprint(\"=\" * 70)\nprint(dashboard_json_str)\nprint(\"=\" * 70)\n\n# ============================================================================\n# OPTION 2: Save to DBFS (Recommended for artifacts)\n# ============================================================================\n# Uncomment the lines below to automatically save to DBFS and get a download link\n\n# dbfs_path = \"dbfs:/FileStore/skyflow/skyflow_analytics_dashboard.lvdash.json\"\n# dbutils.fs.cp(f\"file:{tmp_path}\", dbfs_path, True)\n# print(f\"\\n✓ OPTION 2 COMPLETE: Saved to DBFS\")\n# print(f\" DBFS Path: {dbfs_path}\")\n# print(f\" Download URL: /files/skyflow/skyflow_analytics_dashboard.lvdash.json\")\n# print(f\" Access from browser: /files/skyflow/skyflow_analytics_dashboard.lvdash.json\")\n\n# ============================================================================\n# OPTION 3: Import to Workspace (makes it visible alongside notebooks)\n# ============================================================================\n# Uncomment the lines below to automatically import into your Workspace\n# Note: Update the email/path to match your Databricks username\n\n# try:\n# # Get current user (works in most Databricks environments)\n# current_user = spark.sql(\"SELECT current_user()\").collect()[0][0]\n# workspace_path = f\"/Users/{current_user}/skyflow_analytics_dashboard.lvdash.json\"\n# except:\n# # Fallback: manually specify your email\n# workspace_path = \"/Users/your.email@company.com/skyflow_analytics_dashboard.lvdash.json\"\n# \n# with open(tmp_path, \"r\", encoding=\"utf-8\") as f:\n# content = f.read()\n# \n# # Import into Workspace (will overwrite if exists)\n# dbutils.workspace.import_(workspace_path, content, overwrite=True)\n# print(f\"\\n✓ OPTION 3 COMPLETE: Imported to Workspace\")\n# print(f\" Workspace Path: {workspace_path}\")\n# print(f\" Navigate to Workspace → Users → {current_user} to find the file\")\n\nprint(f\"\\n💾 Manual Import Instructions:\")\nprint(f\" 1. Copy the JSON above (between the === lines)\")\nprint(f\" 2. Save to a local file: skyflow_analytics_dashboard.lvdash.json\")\nprint(f\" 3. Navigate to Databricks SQL → Dashboards → Import\")\nprint(f\" 4. Upload the .lvdash.json file\")\n\nprint(f\"\\n🎨 Dashboard Includes:\")\nprint(f\" ✓ Header with security description\")\nprint(f\" ✓ 3 metric counters (Total Users, Unique Emails, Days Active)\")\nprint(f\" ✓ Email domain distribution bar chart\")\nprint(f\" ✓ User registration timeline (line chart)\")\nprint(f\" ✓ Recent users table with detokenized PII\")\n\nprint(f\"\\n🔒 Security Note:\")\nprint(f\" Only users with EXECUTE permissions on {CATALOG}.{SCHEMA}.skyflow_detokenize()\")\nprint(f\" can view detokenized data in this dashboard\")\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"✓ Dashboard JSON ready! Choose your preferred option above.\")\nprint(\"=\" * 70)",
+ "metadata": {},
+ "execution_count": null,
+ "outputs": []
+ },
{
"cell_type": "markdown",
"metadata": {},