LinkedinMonitor / app.py
GuglielmoTor's picture
Update app.py
0c06300 verified
raw
history blame
11.3 kB
# app.py
# -- coding: utf-8 --
import gradio as gr
import pandas as pd
import os
import logging
import time # Added for simulating delay
# --- Module Imports ---
# Functions from your existing/provided custom modules
from analytics_fetch_and_rendering import fetch_and_render_analytics # Assuming this exists
from gradio_utils import get_url_user_token # For fetching URL parameters
# Functions from newly created/refactored modules
from config import (
LINKEDIN_CLIENT_ID_ENV_VAR, BUBBLE_APP_NAME_ENV_VAR,
BUBBLE_API_KEY_PRIVATE_ENV_VAR, BUBBLE_API_ENDPOINT_ENV_VAR
)
from state_manager import process_and_store_bubble_token
from sync_logic import sync_all_linkedin_data_orchestrator
from ui_generators import (
display_main_dashboard,
run_mentions_tab_display,
run_follower_stats_tab_display
)
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# --- Helper function to load HTML animation ---
def get_sync_animation_html():
"""Loads the HTML content for the sync animation."""
try:
# Ensure this path is correct relative to where app.py is run
with open("sync_animation.html", "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
logging.error("sync_animation.html not found. Please ensure it's in the same directory as app.py.")
return "<p style='text-align:center; color: red;'>Animation file not found. Syncing...</p>"
except Exception as e:
logging.error(f"Error loading sync_animation.html: {e}")
return f"<p style='text-align:center; color: red;'>Error loading animation: {e}. Syncing...</p>"
# --- Guarded Analytics Fetch ---
def guarded_fetch_analytics(token_state):
"""Guarded call to fetch_and_render_analytics, ensuring token and basic data structures."""
if not token_state or not token_state.get("token"):
logging.warning("Analytics fetch: Access denied. No token.")
return ("❌ Access denied. No token.", None, None, None, None, None, None, None)
posts_df_analytics = token_state.get("bubble_posts_df", pd.DataFrame())
mentions_df_analytics = token_state.get("bubble_mentions_df", pd.DataFrame())
follower_stats_df_analytics = token_state.get("bubble_follower_stats_df", pd.DataFrame())
logging.info("Calling fetch_and_render_analytics with current token_state data.")
try:
return fetch_and_render_analytics(
token_state.get("client_id"),
token_state.get("token"),
token_state.get("org_urn"),
posts_df_analytics,
mentions_df_analytics,
follower_stats_df_analytics
)
except Exception as e:
logging.error(f"Error in guarded_fetch_analytics calling fetch_and_render_analytics: {e}", exc_info=True)
return (f"❌ Error fetching analytics: {e}", None, None, None, None, None, None, None)
# --- Animation Test Functions ---
def show_animation_for_test():
"""Returns the animation HTML for the test button."""
logging.info("TEST BUTTON: Showing animation.")
return get_sync_animation_html()
def simulate_processing_after_animation():
"""Simulates a delay and then returns a completion message."""
logging.info("TEST BUTTON: Simulating processing after animation.")
time.sleep(5) # Simulate a 5-second process
logging.info("TEST BUTTON: Simulation complete.")
return "<p style='text-align:center; color: green; font-size: 1.2em;'>βœ… Animation Test Complete!</p>"
# --- Gradio UI Blocks ---
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
title="LinkedIn Organization Dashboard") as app:
token_state = gr.State(value={
"token": None, "client_id": None, "org_urn": None,
"bubble_posts_df": pd.DataFrame(), "fetch_count_for_api": 0,
"bubble_mentions_df": pd.DataFrame(),
"bubble_follower_stats_df": pd.DataFrame(),
"bubble_operations_log_df": pd.DataFrame(),
"mentions_should_sync_now": False,
"fs_should_sync_now": False,
"url_user_token_temp_storage": None
})
gr.Markdown("# πŸš€ LinkedIn Organization Dashboard")
url_user_token_display = gr.Textbox(label="User Token (from URL - Hidden)", interactive=False, visible=False)
status_box = gr.Textbox(label="Overall LinkedIn Token Status", interactive=False, value="Initializing...")
org_urn_display = gr.Textbox(label="Organization URN (from URL - Hidden)", interactive=False, visible=False)
app.load(fn=get_url_user_token, inputs=None, outputs=[url_user_token_display, org_urn_display], api_name="get_url_params", show_progress=False)
def initial_load_sequence(url_token, org_urn_val, current_state):
logging.info(f"Initial load sequence triggered. Org URN: {org_urn_val}, URL Token: {'Present' if url_token else 'Absent'}")
status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state)
dashboard_content = display_main_dashboard(new_state)
return status_msg, new_state, btn_update, dashboard_content
with gr.Tabs():
with gr.TabItem("1️⃣ Dashboard & Sync"):
gr.Markdown("System checks for existing data from Bubble. The 'Sync' button activates if new data needs to be fetched from LinkedIn based on the last sync times and data availability.")
with gr.Row(): # To place buttons side-by-side or in a sequence
sync_data_btn = gr.Button("πŸ”„ Sync LinkedIn Data", variant="primary", visible=False, interactive=False)
test_animation_btn = gr.Button("πŸ§ͺ Test Animation Display", variant="secondary") # New Test Button
sync_status_html_output = gr.HTML("<p style='text-align:center;'>Sync status will appear here.</p>")
dashboard_display_html = gr.HTML("<p style='text-align:center;'>Dashboard loading...</p>")
org_urn_display.change(
fn=initial_load_sequence,
inputs=[url_user_token_display, org_urn_display, token_state],
outputs=[status_box, token_state, sync_data_btn, dashboard_display_html],
show_progress="full"
)
# Original Sync Button Logic
sync_data_btn.click(
fn=lambda: get_sync_animation_html(),
inputs=None,
outputs=[sync_status_html_output],
show_progress=False
).then(
fn=sync_all_linkedin_data_orchestrator,
inputs=[token_state],
outputs=[sync_status_html_output, token_state],
show_progress=False # Animation is the progress
).then(
fn=process_and_store_bubble_token,
inputs=[url_user_token_display, org_urn_display, token_state],
outputs=[status_box, token_state, sync_data_btn],
show_progress=False
).then(
fn=display_main_dashboard,
inputs=[token_state],
outputs=[dashboard_display_html],
show_progress=False
)
# New Test Animation Button Logic
test_animation_btn.click(
fn=show_animation_for_test,
inputs=None,
outputs=[sync_status_html_output],
show_progress=False # Animation is the progress
).then(
fn=simulate_processing_after_animation,
inputs=None,
outputs=[sync_status_html_output],
show_progress=False # Message indicates completion
)
with gr.TabItem("2️⃣ Analytics"):
fetch_analytics_btn = gr.Button("πŸ“ˆ Fetch/Refresh Full Analytics", variant="primary")
follower_count_md = gr.Markdown("Analytics data will load here...")
with gr.Row(): follower_plot, growth_plot = gr.Plot(label="Follower Demographics"), gr.Plot(label="Follower Growth")
with gr.Row(): eng_rate_plot = gr.Plot(label="Engagement Rate")
with gr.Row(): interaction_plot = gr.Plot(label="Post Interactions")
with gr.Row(): eb_plot = gr.Plot(label="Engagement Benchmark")
with gr.Row(): mentions_vol_plot, mentions_sentiment_plot = gr.Plot(label="Mentions Volume"), gr.Plot(label="Mentions Sentiment")
fetch_analytics_btn.click(
fn=guarded_fetch_analytics, inputs=[token_state],
outputs=[follower_count_md, follower_plot, growth_plot, eng_rate_plot,
interaction_plot, eb_plot, mentions_vol_plot, mentions_sentiment_plot],
show_progress="full"
)
with gr.TabItem("3️⃣ Mentions"):
refresh_mentions_display_btn = gr.Button("πŸ”„ Refresh Mentions Display (from local data)", variant="secondary")
mentions_html = gr.HTML("Mentions data loads from Bubble after sync. Click refresh to view current local data.")
mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution")
refresh_mentions_display_btn.click(
fn=run_mentions_tab_display, inputs=[token_state],
outputs=[mentions_html, mentions_sentiment_dist_plot],
show_progress="full"
)
with gr.TabItem("4️⃣ Follower Stats"):
refresh_follower_stats_btn = gr.Button("πŸ”„ Refresh Follower Stats Display (from local data)", variant="secondary")
follower_stats_html = gr.HTML("Follower statistics load from Bubble after sync. Click refresh to view current local data.")
with gr.Row():
fs_plot_monthly_gains = gr.Plot(label="Monthly Follower Gains")
with gr.Row():
fs_plot_seniority = gr.Plot(label="Followers by Seniority (Top 10 Organic)")
fs_plot_industry = gr.Plot(label="Followers by Industry (Top 10 Organic)")
refresh_follower_stats_btn.click(
fn=run_follower_stats_tab_display, inputs=[token_state],
outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry],
show_progress="full"
)
if __name__ == "__main__":
if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR):
logging.warning(f"WARNING: '{LINKEDIN_CLIENT_ID_ENV_VAR}' environment variable not set. The app may not function correctly for LinkedIn API calls.")
if not os.environ.get(BUBBLE_APP_NAME_ENV_VAR) or \
not os.environ.get(BUBBLE_API_KEY_PRIVATE_ENV_VAR) or \
not os.environ.get(BUBBLE_API_ENDPOINT_ENV_VAR):
logging.warning("WARNING: One or more Bubble environment variables (BUBBLE_APP_NAME, BUBBLE_API_KEY_PRIVATE, BUBBLE_API_ENDPOINT) are not set. Bubble integration will fail.")
try:
import matplotlib
logging.info(f"Matplotlib version: {matplotlib.__version__} found. Backend: {matplotlib.get_backend()}")
except ImportError:
logging.error("Matplotlib is not installed. Plots will not be generated. Please install it: pip install matplotlib")
app.launch(server_name="0.0.0.0", server_port=7860, debug=True, share=False)