| """ |
| Musora Sentiment Analysis Dashboard |
| Main Streamlit Application |
| |
| Run with: streamlit run app.py |
| """ |
| import streamlit as st |
| import sys |
| from pathlib import Path |
| import json |
|
|
| |
| parent_dir = Path(__file__).resolve().parent |
| sys.path.append(str(parent_dir)) |
|
|
| from data.data_loader import SentimentDataLoader |
| from data.helpscout_data_loader import HelpScoutDataLoader |
| from components.dashboard import render_dashboard |
| from components.sentiment_analysis import render_sentiment_analysis |
| from components.reply_required import render_reply_required |
| from components.helpscout_dashboard import render_helpscout_dashboard |
| from components.helpscout_analysis import render_helpscout_analysis |
| from utils.auth import check_authentication, render_login_page, logout, get_current_user |
|
|
| |
| config_path = parent_dir / "config" / "viz_config.json" |
| with open(config_path, 'r') as f: |
| config = json.load(f) |
|
|
| |
| st.set_page_config( |
| page_title=config['page_config']['page_title'], |
| page_icon=config['page_config']['page_icon'], |
| layout=config['page_config']['layout'], |
| initial_sidebar_state=config['page_config']['initial_sidebar_state'] |
| ) |
|
|
| st.markdown(""" |
| <style> |
| .block-container { |
| max-width: 100% !important; |
| padding-left: 2rem !important; |
| padding-right: 2rem !important; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| |
| |
| |
| if not check_authentication(): |
| render_login_page() |
|
|
| |
| data_loader = SentimentDataLoader() |
| helpscout_loader = HelpScoutDataLoader() |
|
|
|
|
| def _ensure_dashboard_data(): |
| """Load comment dashboard data once and store in session_state.""" |
| if 'dashboard_df' not in st.session_state or st.session_state['dashboard_df'] is None: |
| with st.spinner("Loading dashboard dataβ¦"): |
| df = data_loader.load_dashboard_data() |
| st.session_state['dashboard_df'] = df |
| return st.session_state['dashboard_df'] |
|
|
|
|
| def _ensure_helpscout_data(): |
| """Load HelpScout dashboard data once and store in session_state.""" |
| if 'helpscout_df' not in st.session_state or st.session_state['helpscout_df'] is None: |
| with st.spinner("Loading HelpScout dataβ¦"): |
| hs_df = helpscout_loader.load_dashboard_data() |
| st.session_state['helpscout_df'] = hs_df |
| return st.session_state['helpscout_df'] |
|
|
|
|
| def main(): |
| |
| with st.sidebar: |
| st.image("visualization/img/musora.png", use_container_width=True) |
|
|
| |
| current_user = get_current_user() |
| if current_user: |
| st.caption(f"Logged in as **{current_user}**") |
| if st.button("π Logout", use_container_width=True): |
| logout() |
| st.rerun() |
|
|
| st.markdown("---") |
| st.title("Navigation") |
|
|
| page = st.radio( |
| "Select Page", |
| [ |
| "π Sentiment Dashboard", |
| "π Custom Sentiment Queries", |
| "π¬ Reply Required", |
| "π§ HelpScout Dashboard", |
| "π¬ HelpScout Analysis", |
| ], |
| index=0 |
| ) |
|
|
| st.markdown("---") |
| st.markdown("### π Global Filters") |
|
|
| |
| dashboard_df = _ensure_dashboard_data() |
| _ensure_helpscout_data() |
|
|
| if dashboard_df.empty: |
| st.error("No data available. Please check your Snowflake connection.") |
| return |
|
|
| filter_options = data_loader.get_filter_options(dashboard_df) |
|
|
| |
| prev = st.session_state.get('global_filters', {}) |
|
|
| selected_platforms = st.multiselect( |
| "Platforms", |
| options=filter_options['platforms'], |
| default=prev.get('platforms', []) |
| ) |
| selected_brands = st.multiselect( |
| "Brands", |
| options=filter_options['brands'], |
| default=prev.get('brands', []) |
| ) |
| selected_sentiments = st.multiselect( |
| "Sentiments", |
| options=filter_options['sentiments'], |
| default=prev.get('sentiments', []) |
| ) |
|
|
| |
| if 'comment_timestamp' in dashboard_df.columns and not dashboard_df.empty: |
| min_date = dashboard_df['comment_timestamp'].min().date() |
| max_date = dashboard_df['comment_timestamp'].max().date() |
|
|
| prev_range = prev.get('date_range') |
| default_range = ( |
| (prev_range[0], prev_range[1]) if prev_range and len(prev_range) == 2 |
| else (min_date, max_date) |
| ) |
| date_range = st.date_input( |
| "Date Range", |
| value=default_range, |
| min_value=min_date, |
| max_value=max_date |
| ) |
| else: |
| date_range = None |
|
|
| |
| if st.button("π Apply Filters", use_container_width=True): |
| st.session_state['global_filters'] = { |
| 'platforms': selected_platforms, |
| 'brands': selected_brands, |
| 'sentiments': selected_sentiments, |
| 'date_range': date_range if date_range and len(date_range) == 2 else None, |
| } |
| st.session_state['filters_applied'] = True |
|
|
| if st.button("π Reset Filters", use_container_width=True): |
| st.session_state['global_filters'] = {} |
| st.session_state['filters_applied'] = False |
| st.rerun() |
|
|
| st.markdown("---") |
|
|
| |
| st.markdown("### π Data Management") |
| if st.button("β»οΈ Reload Data", use_container_width=True): |
| st.cache_data.clear() |
| st.session_state.pop('dashboard_df', None) |
| st.session_state.pop('helpscout_df', None) |
| st.rerun() |
|
|
| |
| st.markdown("---") |
| st.markdown("### βΉοΈ Data Info") |
| st.info(f"**Comments:** {len(dashboard_df):,}") |
| hs_df_info = st.session_state.get('helpscout_df') |
| if hs_df_info is not None and not hs_df_info.empty: |
| st.info(f"**HelpScout:** {len(hs_df_info):,} conversations") |
| if 'processed_at' in dashboard_df.columns and not dashboard_df.empty: |
| last_update = dashboard_df['processed_at'].max() |
| if hasattr(last_update, 'strftime'): |
| st.info(f"**Last Updated:** {last_update.strftime('%Y-%m-%d %H:%M')}") |
|
|
| |
| _hs_page = page in ("π§ HelpScout Dashboard", "π¬ HelpScout Analysis") |
| filters_applied = st.session_state.get('filters_applied', False) |
| global_filters = st.session_state.get('global_filters', {}) |
|
|
| if not _hs_page and filters_applied and global_filters: |
| filtered_df = data_loader.apply_filters( |
| dashboard_df, |
| platforms=global_filters.get('platforms') or None, |
| brands=global_filters.get('brands') or None, |
| sentiments=global_filters.get('sentiments') or None, |
| date_range=global_filters.get('date_range') or None, |
| ) |
| if filtered_df.empty: |
| st.warning("No data matches the selected filters. Please adjust your filters.") |
| return |
| st.info(f"Showing **{len(filtered_df):,}** records after applying filters") |
| else: |
| filtered_df = dashboard_df |
|
|
| |
| if page == "π Sentiment Dashboard": |
| render_dashboard(filtered_df) |
|
|
| elif page == "π Custom Sentiment Queries": |
| |
| render_sentiment_analysis(data_loader) |
|
|
| elif page == "π¬ Reply Required": |
| |
| render_reply_required(data_loader) |
|
|
| elif page == "π§ HelpScout Dashboard": |
| hs_date_range = global_filters.get('date_range') if filters_applied else None |
| render_helpscout_dashboard(helpscout_loader, date_range=hs_date_range) |
|
|
| elif page == "π¬ HelpScout Analysis": |
| render_helpscout_analysis(helpscout_loader) |
|
|
| |
| st.markdown("---") |
| st.markdown( |
| """ |
| <div style='text-align: center; color: gray; padding: 20px;'> |
| <p>Musora Sentiment Analysis Dashboard v1.0</p> |
| <p>Powered by Streamlit | Data from Snowflake</p> |
| </div> |
| """, |
| unsafe_allow_html=True |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| try: |
| main() |
| except Exception as e: |
| st.error(f"An error occurred: {str(e)}") |
| st.exception(e) |