import streamlit as st
import pandas as pd
import json
from datetime import datetime
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
from typing import Dict, List, Any, Optional
# Configure page
st.set_page_config(
page_title="Snowflake Query Optimizer",
page_icon="❄️",
layout="wide",
initial_sidebar_state="collapsed"
)
# Custom CSS for beautiful UI
st.markdown("""
<style>
/* Import Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/* Global Styles */
* {
font-family: 'Inter', sans-serif;
}
.main {
padding: 1rem 2rem;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
}
/* Header Styles */
.header-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 3rem 2rem;
border-radius: 20px;
margin-bottom: 2rem;
color: white;
text-align: center;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
}
.header-container::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: rotate 20s linear infinite;
}
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.header-title {
font-size: 3rem;
font-weight: 700;
margin-bottom: 1rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
position: relative;
z-index: 1;
}
.header-subtitle {
font-size: 1.3rem;
opacity: 0.95;
font-weight: 400;
position: relative;
z-index: 1;
}
/* Metrics Cards */
.metrics-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.metric-card {
background: white;
padding: 2rem;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
border: 1px solid rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.metric-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
}
.metric-card:hover {
transform: translateY(-8px);
box-shadow: 0 16px 48px rgba(0,0,0,0.15);
}
.metric-value {
font-size: 2.5rem;
font-weight: 700;
color: #2c3e50;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.metric-label {
color: #7f8c8d;
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 500;
}
/* Table Styles */
.table-container {
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0,0,0,0.1);
margin: 2rem 0;
border: 1px solid rgba(255,255,255,0.2);
}
.table-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
text-align: center;
position: relative;
}
.table-title {
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.table-subtitle {
font-size: 1rem;
opacity: 0.9;
font-weight: 400;
}
/* Streamlit Table Row Styles */
.table-row {
display: flex;
align-items: center;
padding: 0.5rem 0;
border-bottom: 1px solid #f1f3f4;
transition: all 0.3s ease;
}
.table-row:hover {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 100%);
border-radius: 8px;
margin: 0 0.5rem;
}
.table-header-row {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
font-weight: 600;
color: #495057;
border-bottom: 2px solid #dee2e6;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 1rem 0;
margin-bottom: 0.5rem;
border-radius: 8px;
}
/* User cell styling */
.user-cell-style {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
font-weight: 700 !important;
border-radius: 12px !important;
border: none !important;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
height: 50px !important;
}
/* Clickable metric button styles */
.metric-button {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%) !important;
color: #1565c0 !important;
font-weight: 600 !important;
border-radius: 8px !important;
border: 2px solid transparent !important;
transition: all 0.3s ease !important;
height: 45px !important;
}
.metric-button:hover {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%) !important;
color: white !important;
transform: scale(1.05) !important;
box-shadow: 0 4px 15px rgba(25, 118, 210, 0.3) !important;
}
.zero-button {
background: #f8f9fa !important;
color: #6c757d !important;
font-weight: 500 !important;
border-radius: 8px !important;
border: none !important;
cursor: default !important;
height: 45px !important;
}
.zero-button:hover {
background: #f8f9fa !important;
color: #6c757d !important;
transform: none !important;
box-shadow: none !important;
}
/* Status button styles */
.status-high {
background: linear-gradient(135deg, #ff5722 0%, #d32f2f 100%) !important;
color: white !important;
border-radius: 20px !important;
font-weight: 600 !important;
font-size: 0.85rem !important;
box-shadow: 0 4px 15px rgba(255, 87, 34, 0.3) !important;
border: none !important;
height: 45px !important;
}
.status-normal {
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%) !important;
color: white !important;
border-radius: 20px !important;
font-weight: 600 !important;
font-size: 0.85rem !important;
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3) !important;
border: none !important;
height: 45px !important;
}
/* Query Details Styles */
.query-details-container {
background: white;
border-radius: 20px;
padding: 2rem;
margin: 2rem 0;
box-shadow: 0 20px 60px rgba(0,0,0,0.1);
border: 1px solid rgba(255,255,255,0.2);
}
.query-details-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 16px;
margin-bottom: 2rem;
text-align: center;
position: relative;
overflow: hidden;
}
.query-details-title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.query-details-subtitle {
font-size: 1.1rem;
opacity: 0.9;
font-weight: 400;
}
.query-box {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px solid #dee2e6;
border-radius: 12px;
padding: 1.5rem;
margin: 1rem 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9rem;
line-height: 1.6;
white-space: pre-wrap;
max-height: 400px;
overflow-y: auto;
box-shadow: inset 0 2px 8px rgba(0,0,0,0.05);
position: relative;
}
.query-box::before {
content: 'SQL';
position: absolute;
top: 10px;
right: 15px;
background: #667eea;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
}
/* Button Styles */
.stButton > button {
transition: all 0.3s ease !important;
}
/* Dropdown Styles */
.stSelectbox > div > div {
background: white;
border: 2px solid #e9ecef;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
/* Metric Cards in Details */
.detail-metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin: 2rem 0;
}
.detail-metric-card {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
padding: 1.5rem;
border-radius: 12px;
text-align: center;
border: 1px solid #dee2e6;
transition: all 0.3s ease;
}
.detail-metric-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
}
.detail-metric-value {
font-size: 1.8rem;
font-weight: 700;
color: #495057;
margin-bottom: 0.5rem;
}
.detail-metric-label {
color: #6c757d;
font-size: 0.9rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Animation Classes */
.fade-in {
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in {
animation: slideIn 0.8s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Scrollbar Styles */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5a6fd8, #6a42a0);
}
/* Loading Animation */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(102, 126, 234, 0.3);
border-radius: 50%;
border-top-color: #667eea;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Hide default streamlit button styling for table */
.element-container:has(.user-cell-style) .stButton > button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
font-weight: 700 !important;
border-radius: 12px !important;
border: none !important;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
}
</style>
""", unsafe_allow_html=True)
# Initialize session state
def init_session_state():
"""Initialize session state variables"""
if 'page' not in st.session_state:
st.session_state.page = 'overview'
if 'selected_user' not in st.session_state:
st.session_state.selected_user = None
if 'selected_metric' not in st.session_state:
st.session_state.selected_metric = None
if 'selected_count' not in st.session_state:
st.session_state.selected_count = 0
if 'selected_query_id' not in st.session_state:
st.session_state.selected_query_id = None
if 'sample_queries' not in st.session_state:
st.session_state.sample_queries = []
# Sample data generator (replace with your actual data loading function)
@st.cache_data
def load_sample_data():
"""Load sample data that matches your Snowflake query output"""
np.random.seed(42)
users = ['john_doe', 'jane_smith', 'mike_wilson', 'sarah_jones', 'alex_brown',
'chris_taylor', 'emma_davis', 'ryan_clark', 'lisa_white', 'david_hall']
data = []
for user in users:
# Generate realistic sample queries for each metric type
sample_queries = {
'spilled': [
{
'query_id': f'Q{np.random.randint(1000, 9999)}',
'query_text': f'SELECT * FROM large_table_{user} WHERE date > \'2024-01-01\' ORDER BY timestamp, amount DESC',
'execution_time_ms': np.random.randint(30000, 120000),
'bytes_spilled_to_local_storage': np.random.randint(500000, 5000000),
'bytes_spilled_to_remote_storage': np.random.randint(0, 1000000),
'warehouse_size': np.random.choice(['MEDIUM', 'LARGE', 'X-LARGE']),
'start_time': '2024-01-15 14:30:00'
} for _ in range(np.random.randint(0, 5))
],
'over_provisioned': [
{
'query_id': f'Q{np.random.randint(1000, 9999)}',
'query_text': f'SELECT COUNT(*) FROM small_table_{user}',
'execution_time_ms': np.random.randint(1000, 5000),
'bytes_scanned': np.random.randint(1000, 100000),
'warehouse_size': 'X-LARGE',
'start_time': '2024-01-15 10:15:00'
} for _ in range(np.random.randint(0, 8))
],
'peak_hour_long_running': [
{
'query_id': f'Q{np.random.randint(1000, 9999)}',
'query_text': f'SELECT a.*, b.* FROM big_table_a_{user} a JOIN big_table_b_{user} b ON a.id = b.id WHERE a.status = \'active\'',
'execution_time_ms': np.random.randint(300000, 600000),
'bytes_scanned': np.random.randint(10000, 50000),
'warehouse_size': np.random.choice(['LARGE', 'X-LARGE', '2X-LARGE']),
'start_time': '2024-01-15 14:30:00'
} for _ in range(np.random.randint(0, 6))
],
'select_star': [
{
'query_id': f'Q{np.random.randint(1000, 9999)}',
'query_text': f'SELECT * FROM user_data_{user} LIMIT 1000',
'execution_time_ms': np.random.randint(5000, 30000),
'bytes_scanned': np.random.randint(1000, 10000),
'warehouse_size': np.random.choice(['SMALL', 'MEDIUM', 'LARGE']),
'start_time': '2024-01-15 11:20:00'
} for _ in range(np.random.randint(0, 10))
],
'unpartitioned_scan': [
{
'query_id': f'Q{np.random.randint(1000, 9999)}',
'query_text': f'SELECT * FROM events_{user} WHERE event_date BETWEEN \'2024-01-01\' AND \'2024-12-31\'',
'execution_time_ms': np.random.randint(60000, 180000),
'bytes_scanned': np.random.randint(5000, 30000),
'partitions_scanned': 365,
'partitions_total': 365,
'start_time': '2024-01-15 16:45:00'
} for _ in range(np.random.randint(0, 7))
],
'zero_result': [
{
'query_id': f'Q{np.random.randint(1000, 9999)}',
'query_text': f'SELECT * FROM transactions_{user} WHERE amount < 0 AND status = \'invalid\'',
'execution_time_ms': np.random.randint(10000, 60000),
'bytes_scanned': np.random.randint(500, 5000),
'rows_produced': 0,
'start_time': '2024-01-15 09:30:00'
} for _ in range(np.random.randint(0, 4))
],
'slow': [
{
'query_id': f'Q{np.random.randint(1000, 9999)}',
'query_text': f'SELECT customer_id, SUM(amount) FROM transactions_{user} GROUP BY customer_id HAVING SUM(amount) > 10000 ORDER BY SUM(amount) DESC',
'execution_time_ms': np.random.randint(120000, 300000),
'bytes_scanned': np.random.randint(10000, 50000),
'warehouse_size': np.random.choice(['MEDIUM', 'LARGE', 'X-LARGE']),
'start_time': '2024-01-15 13:15:00'
} for _ in range(np.random.randint(0, 6))
],
'cartesian_join': [
{
'query_id': f'Q{np.random.randint(1000, 9999)}',
'query_text': f'SELECT * FROM table_a_{user} CROSS JOIN table_b_{user}',
'execution_time_ms': np.random.randint(180000, 600000),
'bytes_scanned': np.random.randint(20000, 100000),
'rows_produced': np.random.randint(100, 10000),
'start_time': '2024-01-15 15:20:00'
} for _ in range(np.random.randint(0, 2))
]
}
user_data = {
'USER_NAME': user,
'TOTAL_QUERIES': np.random.randint(500, 3000),
'WAREHOUSES_USED': np.random.randint(2, 8),
'DATABASES_ACCESSED': np.random.randint(3, 10),
'TOTAL_CREDITS': round(np.random.uniform(50.0, 500.0), 2),
'AVG_EXECUTION_TIME_MS': np.random.randint(1000, 8000),
'AVG_BYTES_PER_ROW': round(np.random.uniform(100.0, 2000.0), 2),
'TOTAL_DATA_SCANNED_GB': round(np.random.uniform(100.0, 2000.0), 2),
'FAILURE_CANCELLATION_RATE_PCT': round(np.random.uniform(0.5, 8.0), 2),
'SPILLED_QUERIES': len(sample_queries['spilled']),
'OVER_PROVISIONED_QUERIES': len(sample_queries['over_provisioned']),
'PEAK_HOUR_LONG_RUNNING_QUERIES': len(sample_queries['peak_hour_long_running']),
'SELECT_STAR_QUERIES': len(sample_queries['select_star']),
'UNPARTITIONED_SCAN_QUERIES': len(sample_queries['unpartitioned_scan']),
'REPEATED_QUERIES': np.random.randint(50, 500),
'COMPLEX_JOIN_QUERIES': np.random.randint(10, 100),
'ZERO_RESULT_QUERIES': len(sample_queries['zero_result']),
'HIGH_COMPILE_QUERIES': np.random.randint(5, 80),
'UNTAGGED_QUERIES': np.random.randint(20, 200),
'UNLIMITED_ORDER_BY_QUERIES': np.random.randint(5, 50),
'LARGE_GROUP_BY_QUERIES': np.random.randint(3, 30),
'SLOW_QUERIES': len(sample_queries['slow']),
'EXPENSIVE_DISTINCT_QUERIES': np.random.randint(2, 25),
'INEFFICIENT_LIKE_QUERIES': np.random.randint(10, 80),
'NO_RESULTS_WITH_SCAN_QUERIES': np.random.randint(5, 40),
'CARTESIAN_JOIN_QUERIES': len(sample_queries['cartesian_join']),
'HIGH_COMPILE_RATIO_QUERIES': np.random.randint(3, 30),
'WEIGHTED_SCORE': round(np.random.uniform(500.0, 3000.0), 2),
'COST_STATUS': np.random.choice(['Normal', 'High Cost'], p=[0.7, 0.3]),
'QUERY_SAMPLES': sample_queries
}
data.append(user_data)
return pd.DataFrame(data)
def render_header():
"""Render the beautiful header section"""
st.markdown("""
<div class="header-container fade-in">
<div class="header-title">❄️ Snowflake Query Optimizer</div>
<div class="header-subtitle">Advanced Query Performance Analysis & Optimization Dashboard</div>
</div>
""", unsafe_allow_html=True)
def render_overview_metrics(df):
"""Render overview metrics cards"""
total_users = len(df)
total_queries = df['TOTAL_QUERIES'].sum()
total_credits = df['TOTAL_CREDITS'].sum()
high_cost_users = len(df[df['COST_STATUS'] == 'High Cost'])
metrics_html = f"""
<div class="metrics-container fade-in">
<div class="metric-card">
<div class="metric-value">{total_users:,}</div>
<div class="metric-label">Total Users</div>
</div>
<div class="metric-card">
<div class="metric-value">{total_queries:,}</div>
<div class="metric-label">Total Queries</div>
</div>
<div class="metric-card">
<div class="metric-value">${total_credits:,.2f}</div>
<div class="metric-label">Total Credits</div>
</div>
<div class="metric-card">
<div class="metric-value">{high_cost_users}</div>
<div class="metric-label">High Cost Users</div>
</div>
</div>
"""
st.markdown(metrics_html, unsafe_allow_html=True)
def handle_cell_click(user_name: str, metric_name: str, count: int, sample_queries: List[Dict]):
"""Handle cell click and navigate to details page"""
st.session_state.page = 'query_list'
st.session_state.selected_user = user_name
st.session_state.selected_metric = metric_name
st.session_state.selected_count = count
st.session_state.sample_queries = sample_queries
st.session_state.selected_query_id = None
def render_interactive_table(df):
"""Render the beautiful interactive table using Streamlit components"""
st.markdown("""
<div class="table-container slide-in">
<div class="table-header">
<div class="table-title">Query Performance Analysis</div>
<div class="table-subtitle">Click on any query count to view detailed analysis</div>
</div>
</div>
""", unsafe_allow_html=True)
# Define metrics to display (excluding recommendations and query samples)
metrics_config = [
('SPILLED_QUERIES', 'Spilled', 'spilled'),
('OVER_PROVISIONED_QUERIES', 'Over Provisioned', 'over_provisioned'),
('PEAK_HOUR_LONG_RUNNING_QUERIES', 'Peak Long Running', 'peak_hour_long_running'),
('SELECT_STAR_QUERIES', 'Select *', 'select_star'),
('UNPARTITIONED_SCAN_QUERIES', 'Unpartitioned Scan', 'unpartitioned_scan'),
('ZERO_RESULT_QUERIES', 'Zero Results', 'zero_result'),
('SLOW_QUERIES', 'Slow Queries', 'slow'),
('CARTESIAN_JOIN_QUERIES', 'Cartesian Joins', 'cartesian_join')
]
# Create table header using columns
header_cols = st.columns([2] + [1.2] * len(metrics_config) + [1.5])
# Style the header row
st.markdown('<div class="table-header-row">', unsafe_allow_html=True)
header_cols[0].markdown("**User**")
for i, (_, label, _) in enumerate(metrics_config):
header_cols[i+1].markdown(f"**{label}**")
header_cols[-1].markdown("**Cost Status**")
st.markdown('</div>', unsafe_allow_html=True)
# Create table rows
for idx, row in df.iterrows():
cols = st.columns([2] + [1.2] * len(metrics_config) + [1.5])
# User column - non-clickable display
with cols[0]:
st.button(
row['USER_NAME'],
key=f"user_{idx}",
disabled=True,
help=f"User: {row['USER_NAME']}",
use_container_width=True
)
# Apply custom styling
st.markdown(f"""
<style>
div[data-testid="stButton"]:has(button[key="user_{idx}"]) button {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
font-weight: 700 !important;
border-radius: 12px !important;
border: none !important;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
cursor: default !important;
}}
</style>
""", unsafe_allow_html=True)
# Metric columns with clickable buttons
for i, (col_name, _, sample_key) in enumerate(metrics_config):
value = int(row[col_name])
sample_queries = row['QUERY_SAMPLES'].get(sample_key, []) if isinstance(row['QUERY_SAMPLES'], dict) else []
with cols[i+1]:
if value == 0:
# Zero value - non-clickable
st.button(
str(value),
key=f"zero_{row['USER_NAME']}_{col_name}_{idx}",
disabled=True,
use_container_width=True
)
# Apply zero styling
st.markdown(f"""
<style>
div[data-testid="stButton"]:has(button[key="zero_{row['USER_NAME']}_{col_name}_{idx}"]) button {{
background: #f8f9fa !important;
color: #6c757d !important;
font-weight: 500 !important;
border-radius: 8px !important;
border: none !important;
cursor: default !important;
}}
</style>
""", unsafe_allow_html=True)
else:
# Clickable metric button
if st.button(
str(value),
key=f"metric_{row['USER_NAME']}_{col_name}_{idx}",
help=f"View {value} {col_name.replace('_', ' ').lower()}",
use_container_width=True
):
handle_cell_click(row['USER_NAME'], col_name, value, sample_queries)
st.rerun()
# Apply metric button styling
st.markdown(f"""
<style>
div[data-testid="stButton"]:has(button[key="metric_{row['USER_NAME']}_{col_name}_{idx}"]) button {{
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%) !important;
color: #1565c0 !important;
font-weight: 600 !important;
border-radius: 8px !important;
border: 2px solid transparent !important;
transition: all 0.3s ease !important;
}}
div[data-testid="stButton"]:has(button[key="metric_{row['USER_NAME']}_{col_name}_{idx}"]) button:hover {{
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%) !important;
color: white !important;
transform: scale(1.05) !important;
box-shadow: 0 4px 15px rgba(25, 118, 210, 0.3) !important;
}}
</style>
""", unsafe_allow_html=True)
# Cost status column
with cols[-1]:
status_text = row['COST_STATUS']
st.button(
status_text,
key=f"status_{row['USER_NAME']}_{idx}",
disabled=True,
use_container_width=True
)
# Apply status styling
status_class = "status-high" if status_text == 'High Cost' else "status-normal"
bg_color = "linear-gradient(135deg, #ff5722 0%, #d32f2f 100%)" if status_text == 'High Cost' else "linear-gradient(135deg, #4caf50 0%, #388e3c 100%)"
st.markdown(f"""
<style>
div[data-testid="stButton"]:has(button[key="status_{row['USER_NAME']}_{idx}"]) button {{
background: {bg_color} !important;
color: white !important;
border-radius: 20px !important;
font-weight: 600 !important;
font-size: 0.85rem !important;
box-shadow: 0 4px 15px rgba(255, 87, 34, 0.3) !important;
border: none !important;
cursor: default !important;
}}
</style>
""", unsafe_allow_html=True)
def render_query_list_page():
"""Render the query list page with dropdown selection"""
# Back button
col1, col2 = st.columns([1, 4])
with col1:
if st.button("← Back to Overview", key="back_to_overview", help="Return to main table"):
st.session_state.page = 'overview'
st.rerun()
# Page header
metric_display = st.session_state.selected_metric.replace('_', ' ').title()
st.markdown(f"""
<div class="query-details-container fade-in">
<div class="query-details-header">
<div class="query-details-title">{st.session_state.selected_user}</div>
<div class="query-details-subtitle">{metric_display} - {st.session_state.selected_count} queries found</div>
</div>
</div>
""", unsafe_allow_html=True)
# Show sample queries if available
if st.session_state.sample_queries:
st.markdown("### 📋 Sample Queries")
# Create dropdown for query selection
query_options = []
for i, query in enumerate(st.session_state.sample_queries):
query_preview = query.get('query_text', 'No query text')[:50] + "..."
query_options.append(f"Query {i+1}: {query.get('query_id', 'Unknown')} - {query_preview}")
selected_query_idx = st.selectbox(
"Select a query to view details:",
range(len(query_options)),
format_func=lambda x: query_options[x],
key="query_selector"
)
if st.button("View Query Details", key="view_details", type="primary"):
st.session_state.selected_query_id = selected_query_idx
st.session_state.page = 'query_details'
st.rerun()
# Show preview of selected query
if selected_query_idx is not None:
selected_query = st.session_state.sample_queries[selected_query_idx]
col1, col2 = st.columns([2, 1])
with col1:
st.markdown("**Query Preview:**")
query_text = selected_query.get('query_text', 'No query text available')
st.markdown(f'<div class="query-box">{query_text}</div>', unsafe_allow_html=True)
with col2:
st.markdown("**Quick Metrics:**")
metrics_html = '<div class="detail-metrics">'
if 'execution_time_ms' in selected_query:
metrics_html += f'''
<div class="detail-metric-card">
<div class="detail-metric-value">{selected_query["execution_time_ms"]:,}ms</div>
<div class="detail-metric-label">Execution Time</div>
</div>
'''
if 'bytes_scanned' in selected_query:
bytes_gb = selected_query['bytes_scanned'] / (1024**3)
metrics_html += f'''
<div class="detail-metric-card">
<div class="detail-metric-value">{bytes_gb:.2f}GB</div>
<div class="detail-metric-label">Data Scanned</div>
</div>
'''
if 'warehouse_size' in selected_query:
metrics_html += f'''
<div class="detail-metric-card">
<div class="detail-metric-value">{selected_query["warehouse_size"]}</div>
<div class="detail-metric-label">Warehouse</div>
</div>
'''
metrics_html += '</div>'
st.markdown(metrics_html, unsafe_allow_html=True)
else:
st.info("No sample queries available for this metric.")
def render_query_details_page():
"""Render detailed view of a specific query"""
# Back button
col1, col2 = st.columns([1, 4])
with col1:
if st.button("← Back to Query List", key="back_to_list", help="Return to query list"):
st.session_state.page = 'query_list'
st.rerun()
# Get selected query
if (st.session_state.selected_query_id is not None and
st.session_state.selected_query_id < len(st.session_state.sample_queries)):
query = st.session_state.sample_queries[st.session_state.selected_query_id]
# Page header
st.markdown(f"""
<div class="query-details-container fade-in">
<div class="query-details-header">
<div class="query-details-title">Query Details: {query.get('query_id', 'Unknown')}</div>
<div class="query-details-subtitle">{st.session_state.selected_user} - {st.session_state.selected_metric.replace('_', ' ').title()}</div>
</div>
</div>
""", unsafe_allow_html=True)
# Query metrics
st.markdown("### 📊 Query Metrics")
# Create metrics grid
metrics_data = []
if 'execution_time_ms' in query:
metrics_data.append(("Execution Time", f"{query['execution_time_ms']:,} ms"))
if 'bytes_scanned' in query:
bytes_gb = query['bytes_scanned'] / (1024**3)
metrics_data.append(("Data Scanned", f"{bytes_gb:.2f} GB"))
if 'warehouse_size' in query:
metrics_data.append(("Warehouse Size", query['warehouse_size']))
if 'rows_produced' in query:
metrics_data.append(("Rows Produced", f"{query['rows_produced']:,}"))
if 'bytes_spilled_to_local_storage' in query:
spilled_mb = query['bytes_spilled_to_local_storage'] / (1024**2)
metrics_data.append(("Local Spill", f"{spilled_mb:.2f} MB"))
if 'bytes_spilled_to_remote_storage' in query:
spilled_mb = query['bytes_spilled_to_remote_storage'] / (1024**2)
metrics_data.append(("Remote Spill", f"{spilled_mb:.2f} MB"))
if 'partitions_scanned' in query and 'partitions_total' in query:
metrics_data.append(("Partitions", f"{query['partitions_scanned']}/{query['partitions_total']}"))
if 'start_time' in query:
metrics_data.append(("Start Time", query['start_time']))
# Display metrics in a grid
cols = st.columns(min(4, len(metrics_data)))
for i, (label, value) in enumerate(metrics_data):
with cols[i % 4]:
st.metric(label, value)
# Query text
st.markdown("### 📝 Query Text")
query_text = query.get('query_text', 'No query text available')
st.markdown(f'<div class="query-box">{query_text}</div>', unsafe_allow_html=True)
# Performance analysis
st.markdown("### 🔍 Performance Analysis")
analysis_points = []
# Add analysis based on query type and metrics
if 'execution_time_ms' in query and query['execution_time_ms'] > 60000:
analysis_points.append("⚠️ **Long execution time detected** - Consider optimizing query logic or adding appropriate indexes")
if 'bytes_scanned' in query and query['bytes_scanned'] > 10000: # > 1GB
analysis_points.append("📊 **High data scan volume** - Consider using clustering keys or more selective WHERE clauses")
if 'bytes_spilled_to_local_storage' in query and query['bytes_spilled_to_local_storage'] > 0:
analysis_points.append("💾 **Memory spilling detected** - Consider increasing warehouse size or optimizing query complexity")
if 'warehouse_size' in query and query['warehouse_size'] in ['X-LARGE', '2X-LARGE', '3X-LARGE', '4X-LARGE']:
if 'execution_time_ms' in query and query['execution_time_ms'] < 10000:
analysis_points.append("💰 **Potential over-provisioning** - Query completed quickly on large warehouse")
if 'partitions_scanned' in query and 'partitions_total' in query:
if query['partitions_scanned'] == query['partitions_total']:
analysis_points.append("🔄 **Full table scan** - Consider adding partition pruning conditions")
if query.get('rows_produced', 1) == 0:
analysis_points.append("❌ **Zero results returned** - Review query logic and data availability")
if analysis_points:
for point in analysis_points:
st.markdown(point)
else:
st.success("✅ No major performance issues detected for this query")
# Recommendations
st.markdown("### 💡 Optimization Recommendations")
recommendations = []
# Generate recommendations based on query characteristics
if 'SELECT *' in query_text.upper():
recommendations.append("🎯 **Specify columns explicitly** instead of using SELECT * to reduce data transfer")
if 'ORDER BY' in query_text.upper() and 'LIMIT' not in query_text.upper():
recommendations.append("📏 **Add LIMIT clause** to ORDER BY queries to prevent unnecessary sorting of large result sets")
if 'JOIN' in query_text.upper() and 'ON' not in query_text.upper():
recommendations.append("⚠️ **Review JOIN conditions** to ensure proper join predicates are specified")
if 'GROUP BY' in query_text.upper():
recommendations.append("📊 **Consider pre-aggregation** for frequently used GROUP BY operations")
if 'DISTINCT' in query_text.upper():
recommendations.append("🔄 **Evaluate DISTINCT usage** - consider if GROUP BY might be more efficient")
if recommendations:
for rec in recommendations:
st.markdown(rec)
else:
st.info("No specific recommendations available for this query pattern")
else:
st.error("Query not found or invalid selection")
def render_analytics_charts(df):
"""Render analytics charts"""
st.markdown("### 📊 Analytics Overview")
col1, col2 = st.columns(2)
with col1:
# Top users by weighted score
top_users = df.nlargest(10, 'WEIGHTED_SCORE')
fig = px.bar(
top_users,
x='USER_NAME',
y='WEIGHTED_SCORE',
title='Top Users by Weighted Score',
color='WEIGHTED_SCORE',
color_continuous_scale='Viridis',
template='plotly_white'
)
fig.update_layout(
showlegend=False,
title_font_size=16,
title_font_color='#2c3e50'
)
st.plotly_chart(fig, use_container_width=True)
with col2:
# Cost status distribution
cost_dist = df['COST_STATUS'].value_counts()
fig = px.pie(
values=cost_dist.values,
names=cost_dist.index,
title='Cost Status Distribution',
color_discrete_map={'Normal': '#4caf50', 'High Cost': '#f44336'},
template='plotly_white'
)
fig.update_layout(
title_font_size=16,
title_font_color='#2c3e50'
)
st.plotly_chart(fig, use_container_width=True)
def main():
"""Main application function"""
# Initialize session state
init_session_state()
# Load data (replace this with your actual data loading function)
df = load_sample_data()
# Render header
render_header()
# Route to appropriate page
if st.session_state.page == 'overview':
render_overview_metrics(df)
render_interactive_table(df)
render_analytics_charts(df)
elif st.session_state.page == 'query_list':
render_query_list_page()
elif st.session_state.page == 'query_details':
render_query_details_page()
# Function to replace load_sample_data with your actual data
def load_your_data(df_from_snowflake):
"""
Replace this function with your actual data loading logic
Args:
df_from_snowflake: DataFrame from your Snowflake query
Returns:
Processed DataFrame ready for the dashboard
"""
# Your data processing logic here
# Make sure the DataFrame has all required columns
return df_from_snowflake
if __name__ == "__main__":
main()
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)