Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -27,7 +27,7 @@ from ui_generators import (
|
|
27 |
# Corrected import for analytics_data_processing
|
28 |
from analytics_data_processing import prepare_filtered_analytics_data
|
29 |
from analytics_plot_generator import (
|
30 |
-
generate_posts_activity_plot,
|
31 |
generate_mentions_activity_plot, generate_mention_sentiment_plot,
|
32 |
generate_followers_count_over_time_plot,
|
33 |
generate_followers_growth_rate_plot,
|
@@ -45,14 +45,47 @@ from analytics_plot_generator import (
|
|
45 |
generate_content_format_breakdown_plot,
|
46 |
generate_content_topic_breakdown_plot
|
47 |
)
|
|
|
48 |
|
49 |
# Configure logging
|
50 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
|
51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
# --- Analytics Tab: Plot Figure Generation Function ---
|
53 |
def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date):
|
54 |
logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
|
55 |
-
num_expected_plots
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
if not token_state_value or not token_state_value.get("token"):
|
58 |
message = "❌ Access denied. No token. Cannot generate analytics."
|
@@ -61,8 +94,6 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
61 |
return [message] + placeholder_figs
|
62 |
|
63 |
try:
|
64 |
-
# Optional: Start profiling data preparation
|
65 |
-
# data_prep_start_time = time.time()
|
66 |
(filtered_merged_posts_df,
|
67 |
filtered_mentions_df,
|
68 |
date_filtered_follower_stats_df,
|
@@ -71,7 +102,6 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
71 |
prepare_filtered_analytics_data(
|
72 |
token_state_value, date_filter_option, custom_start_date, custom_end_date
|
73 |
)
|
74 |
-
# logging.info(f"Data preparation took {time.time() - data_prep_start_time:.2f}s")
|
75 |
except Exception as e:
|
76 |
error_msg = f"❌ Error preparing analytics data: {e}"
|
77 |
logging.error(error_msg, exc_info=True)
|
@@ -81,26 +111,17 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
81 |
date_column_posts = token_state_value.get("config_date_col_posts", "published_at")
|
82 |
date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
|
83 |
media_type_col_name = token_state_value.get("config_media_type_col", "media_type")
|
84 |
-
eb_labels_col_name = token_state_value.get("config_eb_labels_col", "li_eb_label")
|
85 |
|
86 |
plot_figs = []
|
87 |
-
# Optional: For detailed profiling of each plot
|
88 |
-
# individual_plot_times = {}
|
89 |
-
# total_plot_gen_start_time = time.time()
|
90 |
try:
|
91 |
-
#
|
92 |
-
#
|
93 |
-
#
|
94 |
-
#plot_figs.append(generate_posts_activity_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
95 |
-
# individual_plot_times[plot_name] = time.time() - plot_start_time
|
96 |
-
# logging.info(f"Generated {plot_name} in {individual_plot_times[plot_name]:.2f}s")
|
97 |
-
|
98 |
-
# plot_figs.append(generate_engagement_type_plot(filtered_merged_posts_df))
|
99 |
fig_mentions_activity_shared = generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions)
|
100 |
fig_mention_sentiment_shared = generate_mention_sentiment_plot(filtered_mentions_df)
|
101 |
-
|
102 |
-
|
103 |
-
# plot_figs.append(fig_mention_sentiment_shared)
|
104 |
plot_figs.append(generate_followers_count_over_time_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
|
105 |
plot_figs.append(generate_followers_growth_rate_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
|
106 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_geo', plot_title="Followers by Location"))
|
@@ -108,7 +129,7 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
108 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_industry', plot_title="Followers by Industry"))
|
109 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_seniority', plot_title="Followers by Seniority"))
|
110 |
plot_figs.append(generate_engagement_rate_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
111 |
-
plot_figs.append(generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
112 |
plot_figs.append(generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
113 |
plot_figs.append(generate_likes_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
114 |
plot_figs.append(generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
@@ -117,12 +138,9 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
117 |
plot_figs.append(generate_comments_sentiment_breakdown_plot(filtered_merged_posts_df, sentiment_column='comment_sentiment'))
|
118 |
plot_figs.append(generate_post_frequency_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
119 |
plot_figs.append(generate_content_format_breakdown_plot(filtered_merged_posts_df, format_col=media_type_col_name))
|
120 |
-
plot_figs.append(generate_content_topic_breakdown_plot(filtered_merged_posts_df, topics_col=eb_labels_col_name))
|
121 |
-
plot_figs.append(fig_mentions_activity_shared) #
|
122 |
-
plot_figs.append(fig_mention_sentiment_shared) #
|
123 |
-
|
124 |
-
# logging.info(f"All plots generated in {time.time() - total_plot_gen_start_time:.2f}s")
|
125 |
-
# logging.info(f"Individual plot generation times: {individual_plot_times}")
|
126 |
|
127 |
message = f"📊 Analytics updated for period: {date_filter_option}"
|
128 |
if date_filter_option == "Custom Range":
|
@@ -132,17 +150,19 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
132 |
|
133 |
final_plot_figs = []
|
134 |
for i, p_fig in enumerate(plot_figs):
|
135 |
-
if p_fig is not None and not isinstance(p_fig, str):
|
136 |
final_plot_figs.append(p_fig)
|
137 |
else:
|
138 |
logging.warning(f"Plot figure generation failed or returned unexpected type for slot {i}, using placeholder. Figure: {p_fig}")
|
139 |
final_plot_figs.append(create_placeholder_plot(title="Plot Error", message="Failed to generate this plot figure."))
|
140 |
-
|
|
|
|
|
141 |
while len(final_plot_figs) < num_expected_plots:
|
142 |
logging.warning(f"Padding missing plot figure. Expected {num_expected_plots}, got {len(final_plot_figs)}.")
|
143 |
final_plot_figs.append(create_placeholder_plot(title="Missing Plot", message="Plot figure could not be generated."))
|
144 |
-
|
145 |
-
return [message] + final_plot_figs[:num_expected_plots]
|
146 |
|
147 |
except Exception as e:
|
148 |
error_msg = f"❌ Error generating analytics plot figures: {e}"
|
@@ -162,7 +182,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
162 |
"fetch_count_for_api": 0, "url_user_token_temp_storage": None,
|
163 |
"config_date_col_posts": "published_at", "config_date_col_mentions": "date",
|
164 |
"config_date_col_followers": "date", "config_media_type_col": "media_type",
|
165 |
-
"config_eb_labels_col": "
|
166 |
})
|
167 |
|
168 |
gr.Markdown("# 🚀 LinkedIn Organization Dashboard")
|
@@ -203,8 +223,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
203 |
label="Select Date Range", value="All Time", scale=3
|
204 |
)
|
205 |
with gr.Column(scale=2):
|
206 |
-
custom_start_date_picker = gr.DateTime(label="Start Date", visible=False, include_time=False, type="datetime")
|
207 |
-
custom_end_date_picker = gr.DateTime(label="End Date", visible=False, include_time=False, type="datetime")
|
208 |
|
209 |
apply_filter_btn = gr.Button("🔍 Apply Filter & Refresh Analytics", variant="primary")
|
210 |
|
@@ -219,10 +239,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
219 |
)
|
220 |
|
221 |
plot_configs = [
|
222 |
-
# {"label": "Posts Activity Over Time", "id": "posts_activity", "section": "Posts & Engagement Overview"},
|
223 |
-
# {"label": "Post Engagement Types", "id": "engagement_type", "section": "Posts & Engagement Overview"},
|
224 |
-
# {"label": "Mentions Activity Over Time", "id": "mentions_activity", "section": "Mentions Overview"},
|
225 |
-
# {"label": "Mention Sentiment Distribution", "id": "mention_sentiment", "section": "Mentions Overview"},
|
226 |
{"label": "Followers Count Over Time", "id": "followers_count", "section": "Follower Dynamics"},
|
227 |
{"label": "Followers Growth Rate", "id": "followers_growth_rate", "section": "Follower Dynamics"},
|
228 |
{"label": "Followers by Location", "id": "followers_by_location", "section": "Follower Demographics"},
|
@@ -230,7 +246,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
230 |
{"label": "Followers by Industry", "id": "followers_by_industry", "section": "Follower Demographics"},
|
231 |
{"label": "Followers by Seniority", "id": "followers_by_seniority", "section": "Follower Demographics"},
|
232 |
{"label": "Engagement Rate Over Time", "id": "engagement_rate", "section": "Post Performance Insights"},
|
233 |
-
{"label": "Reach Over Time", "id": "reach_over_time", "section": "Post Performance Insights"},
|
234 |
{"label": "Impressions Over Time", "id": "impressions_over_time", "section": "Post Performance Insights"},
|
235 |
{"label": "Reactions (Likes) Over Time", "id": "likes_over_time", "section": "Post Performance Insights"},
|
236 |
{"label": "Clicks Over Time", "id": "clicks_over_time", "section": "Detailed Post Engagement Over Time"},
|
@@ -245,35 +261,28 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
245 |
]
|
246 |
assert len(plot_configs) == 19, "Mismatch in plot_configs and expected plots."
|
247 |
|
248 |
-
active_panel_action_state = gr.State(None)
|
249 |
-
explored_plot_id_state = gr.State(None)
|
250 |
|
251 |
-
plot_ui_objects = {}
|
252 |
|
253 |
with gr.Row(equal_height=False):
|
254 |
with gr.Column(scale=8) as plots_area_col:
|
255 |
-
# Call the UI builder function and store its results
|
256 |
plot_ui_objects = build_analytics_tab_plot_area(plot_configs)
|
257 |
|
258 |
with gr.Column(scale=4, visible=False) as global_actions_column_ui:
|
259 |
gr.Markdown("### 💡 Generated Content")
|
260 |
global_actions_markdown_ui = gr.Markdown("Click a button (💣, ƒ) on a plot to see content here.")
|
261 |
|
262 |
-
|
263 |
# --- Event Handler for Insights and Formula Buttons ---
|
264 |
def handle_panel_action(plot_id_clicked, action_type, current_active_action_from_state, current_token_state_val):
|
265 |
logging.info(f"Action '{action_type}' for plot: {plot_id_clicked}. Current active from state: {current_active_action_from_state}")
|
266 |
|
267 |
-
# This check is crucial: plot_ui_objects must be populated before this handler is effectively used.
|
268 |
-
# It should be, as this handler is tied to buttons created by build_analytics_tab_plot_area.
|
269 |
if not plot_ui_objects or plot_id_clicked not in plot_ui_objects:
|
270 |
logging.error(f"plot_ui_objects not populated or plot_id {plot_id_clicked} not found during handle_panel_action.")
|
271 |
-
# Return updates that don't crash, perhaps just an error message or no-ops.
|
272 |
-
# Number of outputs for this handler: 3 (global_col_vis, global_md_val, active_state_val) + 2 * len(plot_configs) (buttons)
|
273 |
error_updates = [gr.update(visible=False), "Error: UI components not ready.", None] + [gr.update() for _ in range(2 * len(plot_configs))]
|
274 |
return error_updates
|
275 |
|
276 |
-
|
277 |
clicked_plot_label = plot_ui_objects.get(plot_id_clicked, {}).get("label", "Selected Plot")
|
278 |
|
279 |
hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
|
@@ -284,44 +293,50 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
284 |
action_col_visible = False
|
285 |
|
286 |
if is_toggling_off:
|
287 |
-
new_active_action_state_to_set = None
|
288 |
content_text = f"{action_type.capitalize()} panel for '{clicked_plot_label}' closed."
|
289 |
action_col_visible = False
|
290 |
logging.info(f"Closing {action_type} panel for {plot_id_clicked}")
|
291 |
-
else:
|
292 |
new_active_action_state_to_set = hypothetical_new_active_state
|
293 |
action_col_visible = True
|
294 |
if action_type == "insights":
|
295 |
-
# TODO: Implement actual insight generation
|
296 |
content_text = f"**Insights for: {clicked_plot_label}**\n\nPlot ID: `{plot_id_clicked}`.\n(AI insights generation placeholder)"
|
297 |
elif action_type == "formula":
|
298 |
-
|
299 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
300 |
logging.info(f"Opening/switching to {action_type} panel for {plot_id_clicked}")
|
301 |
|
302 |
-
# Prepare updates for ALL buttons based on new_active_action_state_to_set
|
303 |
all_button_updates = []
|
304 |
for cfg_item in plot_configs:
|
305 |
p_id_iter = cfg_item["id"]
|
306 |
if p_id_iter in plot_ui_objects:
|
307 |
-
# Bomb button state for this p_id_iter
|
308 |
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "insights"}:
|
309 |
all_button_updates.append(gr.update(value=ACTIVE_ICON))
|
310 |
else:
|
311 |
all_button_updates.append(gr.update(value=BOMB_ICON))
|
312 |
|
313 |
-
# Formula button state for this p_id_iter
|
314 |
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "formula"}:
|
315 |
all_button_updates.append(gr.update(value=ACTIVE_ICON))
|
316 |
else:
|
317 |
all_button_updates.append(gr.update(value=FORMULA_ICON))
|
318 |
-
else:
|
319 |
-
all_button_updates.extend([gr.update(), gr.update()])
|
320 |
|
321 |
final_updates = [
|
322 |
gr.update(visible=action_col_visible),
|
323 |
gr.update(value=content_text),
|
324 |
-
new_active_action_state_to_set
|
325 |
] + all_button_updates
|
326 |
|
327 |
return final_updates
|
@@ -330,83 +345,65 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
330 |
def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state):
|
331 |
logging.info(f"Explore clicked for: {plot_id_clicked}. Currently explored from state: {current_explored_plot_id_from_state}")
|
332 |
|
333 |
-
if not plot_ui_objects:
|
334 |
logging.error("plot_ui_objects not populated during handle_explore_click.")
|
335 |
-
return [current_explored_plot_id_from_state] + [gr.update() for _ in range(2 * len(plot_configs))]
|
336 |
-
|
337 |
|
338 |
new_explored_id_to_set = None
|
339 |
is_toggling_off = (plot_id_clicked == current_explored_plot_id_from_state)
|
340 |
|
341 |
if is_toggling_off:
|
342 |
-
new_explored_id_to_set = None
|
343 |
logging.info(f"Un-exploring plot: {plot_id_clicked}")
|
344 |
else:
|
345 |
-
new_explored_id_to_set = plot_id_clicked
|
346 |
logging.info(f"Exploring plot: {plot_id_clicked}")
|
347 |
|
348 |
-
# Prepare updates for panel visibility and explore button icons
|
349 |
panel_and_button_updates = []
|
350 |
for cfg in plot_configs:
|
351 |
p_id = cfg["id"]
|
352 |
if p_id in plot_ui_objects:
|
353 |
panel_visible = not new_explored_id_to_set or (p_id == new_explored_id_to_set)
|
|
|
354 |
|
355 |
-
|
356 |
-
# This logic might need refinement based on how build_analytics_tab_plot_area structures rows
|
357 |
-
# For now, the basic logic is: if new_explored_id_to_set is active, only that panel is visible.
|
358 |
-
# If new_explored_id_to_set is None, all panels are visible.
|
359 |
-
|
360 |
-
panel_and_button_updates.append(gr.update(visible=panel_visible)) # Panel visibility update
|
361 |
-
|
362 |
-
# Explore button icon update
|
363 |
-
if p_id == new_explored_id_to_set: # If this plot is the one being explored (or just became explored)
|
364 |
panel_and_button_updates.append(gr.update(value=ACTIVE_ICON))
|
365 |
-
else:
|
366 |
panel_and_button_updates.append(gr.update(value=EXPLORE_ICON))
|
367 |
-
else:
|
368 |
panel_and_button_updates.extend([gr.update(), gr.update()])
|
369 |
|
370 |
final_updates = [new_explored_id_to_set] + panel_and_button_updates
|
371 |
return final_updates
|
372 |
|
373 |
-
# --- Define outputs for event handlers ---
|
374 |
-
# These lists define which Gradio components will receive updates from the handlers.
|
375 |
-
# The order and number of components must match the list of updates returned by the handler.
|
376 |
-
|
377 |
-
# Outputs for handle_panel_action (Insights/Formula buttons)
|
378 |
action_buttons_outputs_list = [
|
379 |
global_actions_column_ui,
|
380 |
global_actions_markdown_ui,
|
381 |
-
active_panel_action_state
|
382 |
]
|
383 |
-
for cfg_item_action in plot_configs:
|
384 |
pid_action = cfg_item_action["id"]
|
385 |
if pid_action in plot_ui_objects:
|
386 |
action_buttons_outputs_list.append(plot_ui_objects[pid_action]["bomb_button"])
|
387 |
action_buttons_outputs_list.append(plot_ui_objects[pid_action]["formula_button"])
|
388 |
-
else:
|
389 |
-
action_buttons_outputs_list.extend([None, None])
|
390 |
|
391 |
-
|
392 |
-
|
393 |
-
for cfg_item_explore in plot_configs: # Iterate in defined order
|
394 |
pid_explore = cfg_item_explore["id"]
|
395 |
if pid_explore in plot_ui_objects:
|
396 |
-
explore_buttons_outputs_list.append(plot_ui_objects[pid_explore]["panel_component"])
|
397 |
-
explore_buttons_outputs_list.append(plot_ui_objects[pid_explore]["explore_button"])
|
398 |
-
else:
|
399 |
explore_buttons_outputs_list.extend([None, None])
|
400 |
|
401 |
-
|
402 |
-
# --- Connect Action Buttons ---
|
403 |
-
# Inputs for the click handlers that need current state values
|
404 |
action_click_inputs = [active_panel_action_state, token_state]
|
405 |
-
explore_click_inputs = [explored_plot_id_state]
|
406 |
|
407 |
for config_item in plot_configs:
|
408 |
plot_id = config_item["id"]
|
409 |
-
if plot_id in plot_ui_objects:
|
410 |
ui_obj = plot_ui_objects[plot_id]
|
411 |
|
412 |
ui_obj["bomb_button"].click(
|
@@ -428,99 +425,89 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
428 |
api_name=f"action_explore_{plot_id}"
|
429 |
)
|
430 |
else:
|
431 |
-
logging.warning(f"UI object for plot_id '{plot_id}' not found when trying to attach click handlers.
|
432 |
|
433 |
-
|
434 |
-
# --- Function to Refresh All Analytics UI ---
|
435 |
def refresh_all_analytics_ui_elements(current_token_state, date_filter_val, custom_start_val, custom_end_val):
|
436 |
logging.info("Refreshing all analytics UI elements and resetting actions.")
|
|
|
|
|
437 |
plot_generation_results = update_analytics_plots_figures(
|
438 |
current_token_state, date_filter_val, custom_start_val, custom_end_val
|
439 |
)
|
440 |
|
441 |
status_message_update = plot_generation_results[0]
|
442 |
-
generated_plot_figures = plot_generation_results[1:] # Should be list of
|
443 |
|
444 |
-
all_updates = [status_message_update]
|
445 |
|
446 |
-
# Updates for plot components
|
447 |
-
for i in range(len(plot_configs)):
|
448 |
if i < len(generated_plot_figures):
|
449 |
all_updates.append(generated_plot_figures[i])
|
450 |
-
else:
|
451 |
-
all_updates.append(create_placeholder_plot("Figure Error", f"Missing figure for plot {i}"))
|
452 |
|
453 |
-
|
454 |
-
all_updates.append(gr.update(
|
455 |
-
all_updates.append(
|
456 |
-
all_updates.append(None) # Reset active_panel_action_state
|
457 |
|
458 |
-
|
459 |
-
for cfg in plot_configs:
|
460 |
pid = cfg["id"]
|
461 |
if pid in plot_ui_objects:
|
462 |
-
all_updates.append(gr.update(value=BOMB_ICON))
|
463 |
-
all_updates.append(gr.update(value=FORMULA_ICON))
|
464 |
-
all_updates.append(gr.update(value=EXPLORE_ICON))
|
465 |
-
all_updates.append(gr.update(visible=True))
|
466 |
-
else:
|
467 |
all_updates.extend([None, None, None, None])
|
468 |
|
469 |
-
all_updates.append(None)
|
470 |
-
|
471 |
logging.info(f"Prepared {len(all_updates)} updates for analytics refresh.")
|
472 |
return all_updates
|
473 |
|
474 |
-
|
475 |
-
# This list must exactly match the structure of updates returned by refresh_all_analytics_ui_elements
|
476 |
-
apply_filter_and_sync_outputs_list = [analytics_status_md] # 1. Status MD
|
477 |
|
478 |
-
|
479 |
-
for config_item_filter_sync in plot_configs:
|
480 |
pid_filter_sync = config_item_filter_sync["id"]
|
481 |
if pid_filter_sync in plot_ui_objects and "plot_component" in plot_ui_objects[pid_filter_sync]:
|
482 |
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync]["plot_component"])
|
483 |
-
else:
|
484 |
-
apply_filter_and_sync_outputs_list.append(None)
|
485 |
|
486 |
-
# 3. Global action column, its markdown, and its state (3 items)
|
487 |
apply_filter_and_sync_outputs_list.extend([
|
488 |
global_actions_column_ui,
|
489 |
global_actions_markdown_ui,
|
490 |
-
active_panel_action_state
|
491 |
])
|
492 |
|
493 |
-
|
494 |
-
for cfg_filter_sync_btns in plot_configs:
|
495 |
pid_filter_sync_btns = cfg_filter_sync_btns["id"]
|
496 |
if pid_filter_sync_btns in plot_ui_objects:
|
497 |
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["bomb_button"])
|
498 |
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["formula_button"])
|
499 |
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["explore_button"])
|
500 |
-
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["panel_component"])
|
501 |
-
else:
|
502 |
apply_filter_and_sync_outputs_list.extend([None, None, None, None])
|
503 |
|
504 |
-
|
505 |
-
apply_filter_and_sync_outputs_list.append(explored_plot_id_state) # The State object itself
|
506 |
|
507 |
-
logging.info(f"Total outputs for apply_filter/sync: {len(apply_filter_and_sync_outputs_list)}")
|
508 |
-
|
509 |
|
510 |
apply_filter_btn.click(
|
511 |
fn=refresh_all_analytics_ui_elements,
|
512 |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
|
513 |
-
outputs=apply_filter_and_sync_outputs_list,
|
514 |
show_progress="full"
|
515 |
)
|
516 |
|
517 |
with gr.TabItem("3️⃣ Mentions", id="tab_mentions"):
|
518 |
refresh_mentions_display_btn = gr.Button("🔄 Refresh Mentions Display", variant="secondary")
|
519 |
mentions_html = gr.HTML("Mentions data...")
|
520 |
-
mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution")
|
521 |
refresh_mentions_display_btn.click(
|
522 |
fn=run_mentions_tab_display, inputs=[token_state],
|
523 |
-
outputs=[mentions_html, mentions_sentiment_dist_plot],
|
524 |
show_progress="full"
|
525 |
)
|
526 |
|
@@ -544,18 +531,18 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
544 |
inputs=[token_state], outputs=[sync_status_html_output, token_state], show_progress="full"
|
545 |
)
|
546 |
sync_event_part2 = sync_event_part1.then(
|
547 |
-
fn=process_and_store_bubble_token,
|
548 |
inputs=[url_user_token_display, org_urn_display, token_state],
|
549 |
-
outputs=[status_box, token_state, sync_data_btn], show_progress=False
|
550 |
)
|
551 |
sync_event_part3 = sync_event_part2.then(
|
552 |
-
fn=display_main_dashboard,
|
553 |
inputs=[token_state], outputs=[dashboard_display_html], show_progress=False
|
554 |
)
|
555 |
sync_event_final = sync_event_part3.then(
|
556 |
-
fn=refresh_all_analytics_ui_elements,
|
557 |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
|
558 |
-
outputs=apply_filter_and_sync_outputs_list, show_progress="full"
|
559 |
)
|
560 |
|
561 |
if __name__ == "__main__":
|
@@ -572,3 +559,4 @@ if __name__ == "__main__":
|
|
572 |
logging.error("Matplotlib is not installed. Plots will not be generated.")
|
573 |
|
574 |
app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
|
|
|
27 |
# Corrected import for analytics_data_processing
|
28 |
from analytics_data_processing import prepare_filtered_analytics_data
|
29 |
from analytics_plot_generator import (
|
30 |
+
generate_posts_activity_plot, # Make sure this is available if posts_activity is in map
|
31 |
generate_mentions_activity_plot, generate_mention_sentiment_plot,
|
32 |
generate_followers_count_over_time_plot,
|
33 |
generate_followers_growth_rate_plot,
|
|
|
45 |
generate_content_format_breakdown_plot,
|
46 |
generate_content_topic_breakdown_plot
|
47 |
)
|
48 |
+
from formulas import PLOT_FORMULAS # Import the formula descriptions
|
49 |
|
50 |
# Configure logging
|
51 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
|
52 |
|
53 |
+
# Mapping from plot_configs IDs to PLOT_FORMULAS keys
|
54 |
+
PLOT_ID_TO_FORMULA_KEY_MAP = {
|
55 |
+
"posts_activity": "posts_activity", # Example, if you add it back to plot_configs
|
56 |
+
"mentions_activity": "mentions_activity", # Example, if you add it back
|
57 |
+
"mention_sentiment": "mention_sentiment", # Example, if you add it back
|
58 |
+
"followers_count": "followers_count_over_time",
|
59 |
+
"followers_growth_rate": "followers_growth_rate",
|
60 |
+
"followers_by_location": "followers_by_demographics",
|
61 |
+
"followers_by_role": "followers_by_demographics",
|
62 |
+
"followers_by_industry": "followers_by_demographics",
|
63 |
+
"followers_by_seniority": "followers_by_demographics",
|
64 |
+
"engagement_rate": "engagement_rate_over_time",
|
65 |
+
"reach_over_time": "reach_over_time",
|
66 |
+
"impressions_over_time": "impressions_over_time",
|
67 |
+
"likes_over_time": "likes_over_time",
|
68 |
+
"clicks_over_time": "clicks_over_time",
|
69 |
+
"shares_over_time": "shares_over_time",
|
70 |
+
"comments_over_time": "comments_over_time",
|
71 |
+
"comments_sentiment": "comments_sentiment_breakdown",
|
72 |
+
"post_frequency_cs": "post_frequency",
|
73 |
+
"content_format_breakdown_cs": "content_format_breakdown",
|
74 |
+
"content_topic_breakdown_cs": "content_topic_breakdown",
|
75 |
+
"mention_analysis_volume": "mentions_activity",
|
76 |
+
"mention_analysis_sentiment": "mention_sentiment"
|
77 |
+
}
|
78 |
+
|
79 |
+
|
80 |
# --- Analytics Tab: Plot Figure Generation Function ---
|
81 |
def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date):
|
82 |
logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
|
83 |
+
# This num_expected_plots should align with the number of gr.Plot components expected as output.
|
84 |
+
# The plot_configs list has 19 items.
|
85 |
+
# If generate_posts_activity_plot, generate_mentions_activity_plot, generate_mention_sentiment_plot
|
86 |
+
# are added back to the main list of plots directly, this number might change.
|
87 |
+
# For now, it seems the detailed mention plots are re-using figures.
|
88 |
+
num_expected_plots = 19 # Adjusted to match plot_configs length for clarity in this function's direct output list construction
|
89 |
|
90 |
if not token_state_value or not token_state_value.get("token"):
|
91 |
message = "❌ Access denied. No token. Cannot generate analytics."
|
|
|
94 |
return [message] + placeholder_figs
|
95 |
|
96 |
try:
|
|
|
|
|
97 |
(filtered_merged_posts_df,
|
98 |
filtered_mentions_df,
|
99 |
date_filtered_follower_stats_df,
|
|
|
102 |
prepare_filtered_analytics_data(
|
103 |
token_state_value, date_filter_option, custom_start_date, custom_end_date
|
104 |
)
|
|
|
105 |
except Exception as e:
|
106 |
error_msg = f"❌ Error preparing analytics data: {e}"
|
107 |
logging.error(error_msg, exc_info=True)
|
|
|
111 |
date_column_posts = token_state_value.get("config_date_col_posts", "published_at")
|
112 |
date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
|
113 |
media_type_col_name = token_state_value.get("config_media_type_col", "media_type")
|
114 |
+
eb_labels_col_name = token_state_value.get("config_eb_labels_col", "li_eb_label") # Corrected from li_eb_label to li_eb_labels if that's the actual key in formulas
|
115 |
|
116 |
plot_figs = []
|
|
|
|
|
|
|
117 |
try:
|
118 |
+
# These plots are generated once and potentially re-used if their IDs match later plot_configs
|
119 |
+
# However, the current plot_configs doesn't list these explicitly at the start,
|
120 |
+
# but "mention_analysis_volume" and "mention_analysis_sentiment" will use them.
|
|
|
|
|
|
|
|
|
|
|
121 |
fig_mentions_activity_shared = generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions)
|
122 |
fig_mention_sentiment_shared = generate_mention_sentiment_plot(filtered_mentions_df)
|
123 |
+
|
124 |
+
# Order matters here, must match plot_configs
|
|
|
125 |
plot_figs.append(generate_followers_count_over_time_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
|
126 |
plot_figs.append(generate_followers_growth_rate_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
|
127 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_geo', plot_title="Followers by Location"))
|
|
|
129 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_industry', plot_title="Followers by Industry"))
|
130 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_seniority', plot_title="Followers by Seniority"))
|
131 |
plot_figs.append(generate_engagement_rate_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
132 |
+
plot_figs.append(generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
133 |
plot_figs.append(generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
134 |
plot_figs.append(generate_likes_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
135 |
plot_figs.append(generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
|
|
138 |
plot_figs.append(generate_comments_sentiment_breakdown_plot(filtered_merged_posts_df, sentiment_column='comment_sentiment'))
|
139 |
plot_figs.append(generate_post_frequency_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
140 |
plot_figs.append(generate_content_format_breakdown_plot(filtered_merged_posts_df, format_col=media_type_col_name))
|
141 |
+
plot_figs.append(generate_content_topic_breakdown_plot(filtered_merged_posts_df, topics_col=eb_labels_col_name)) # Ensure eb_labels_col_name matches key in formulas if different from 'li_eb_labels'
|
142 |
+
plot_figs.append(fig_mentions_activity_shared) # For "mention_analysis_volume"
|
143 |
+
plot_figs.append(fig_mention_sentiment_shared) # For "mention_analysis_sentiment"
|
|
|
|
|
|
|
144 |
|
145 |
message = f"📊 Analytics updated for period: {date_filter_option}"
|
146 |
if date_filter_option == "Custom Range":
|
|
|
150 |
|
151 |
final_plot_figs = []
|
152 |
for i, p_fig in enumerate(plot_figs):
|
153 |
+
if p_fig is not None and not isinstance(p_fig, str):
|
154 |
final_plot_figs.append(p_fig)
|
155 |
else:
|
156 |
logging.warning(f"Plot figure generation failed or returned unexpected type for slot {i}, using placeholder. Figure: {p_fig}")
|
157 |
final_plot_figs.append(create_placeholder_plot(title="Plot Error", message="Failed to generate this plot figure."))
|
158 |
+
|
159 |
+
# Ensure the list has exactly num_expected_plots items.
|
160 |
+
# This padding should ideally not be needed if plot_figs is constructed correctly to match num_expected_plots.
|
161 |
while len(final_plot_figs) < num_expected_plots:
|
162 |
logging.warning(f"Padding missing plot figure. Expected {num_expected_plots}, got {len(final_plot_figs)}.")
|
163 |
final_plot_figs.append(create_placeholder_plot(title="Missing Plot", message="Plot figure could not be generated."))
|
164 |
+
|
165 |
+
return [message] + final_plot_figs[:num_expected_plots] # Ensure correct number of plot figures are returned
|
166 |
|
167 |
except Exception as e:
|
168 |
error_msg = f"❌ Error generating analytics plot figures: {e}"
|
|
|
182 |
"fetch_count_for_api": 0, "url_user_token_temp_storage": None,
|
183 |
"config_date_col_posts": "published_at", "config_date_col_mentions": "date",
|
184 |
"config_date_col_followers": "date", "config_media_type_col": "media_type",
|
185 |
+
"config_eb_labels_col": "li_eb_labels" # Ensure this matches the column name used in plot generator and formulas.py
|
186 |
})
|
187 |
|
188 |
gr.Markdown("# 🚀 LinkedIn Organization Dashboard")
|
|
|
223 |
label="Select Date Range", value="All Time", scale=3
|
224 |
)
|
225 |
with gr.Column(scale=2):
|
226 |
+
custom_start_date_picker = gr.DateTime(label="Start Date", visible=False, include_time=False, type="datetime")
|
227 |
+
custom_end_date_picker = gr.DateTime(label="End Date", visible=False, include_time=False, type="datetime")
|
228 |
|
229 |
apply_filter_btn = gr.Button("🔍 Apply Filter & Refresh Analytics", variant="primary")
|
230 |
|
|
|
239 |
)
|
240 |
|
241 |
plot_configs = [
|
|
|
|
|
|
|
|
|
242 |
{"label": "Followers Count Over Time", "id": "followers_count", "section": "Follower Dynamics"},
|
243 |
{"label": "Followers Growth Rate", "id": "followers_growth_rate", "section": "Follower Dynamics"},
|
244 |
{"label": "Followers by Location", "id": "followers_by_location", "section": "Follower Demographics"},
|
|
|
246 |
{"label": "Followers by Industry", "id": "followers_by_industry", "section": "Follower Demographics"},
|
247 |
{"label": "Followers by Seniority", "id": "followers_by_seniority", "section": "Follower Demographics"},
|
248 |
{"label": "Engagement Rate Over Time", "id": "engagement_rate", "section": "Post Performance Insights"},
|
249 |
+
{"label": "Reach Over Time", "id": "reach_over_time", "section": "Post Performance Insights"},
|
250 |
{"label": "Impressions Over Time", "id": "impressions_over_time", "section": "Post Performance Insights"},
|
251 |
{"label": "Reactions (Likes) Over Time", "id": "likes_over_time", "section": "Post Performance Insights"},
|
252 |
{"label": "Clicks Over Time", "id": "clicks_over_time", "section": "Detailed Post Engagement Over Time"},
|
|
|
261 |
]
|
262 |
assert len(plot_configs) == 19, "Mismatch in plot_configs and expected plots."
|
263 |
|
264 |
+
active_panel_action_state = gr.State(None)
|
265 |
+
explored_plot_id_state = gr.State(None)
|
266 |
|
267 |
+
plot_ui_objects = {}
|
268 |
|
269 |
with gr.Row(equal_height=False):
|
270 |
with gr.Column(scale=8) as plots_area_col:
|
|
|
271 |
plot_ui_objects = build_analytics_tab_plot_area(plot_configs)
|
272 |
|
273 |
with gr.Column(scale=4, visible=False) as global_actions_column_ui:
|
274 |
gr.Markdown("### 💡 Generated Content")
|
275 |
global_actions_markdown_ui = gr.Markdown("Click a button (💣, ƒ) on a plot to see content here.")
|
276 |
|
|
|
277 |
# --- Event Handler for Insights and Formula Buttons ---
|
278 |
def handle_panel_action(plot_id_clicked, action_type, current_active_action_from_state, current_token_state_val):
|
279 |
logging.info(f"Action '{action_type}' for plot: {plot_id_clicked}. Current active from state: {current_active_action_from_state}")
|
280 |
|
|
|
|
|
281 |
if not plot_ui_objects or plot_id_clicked not in plot_ui_objects:
|
282 |
logging.error(f"plot_ui_objects not populated or plot_id {plot_id_clicked} not found during handle_panel_action.")
|
|
|
|
|
283 |
error_updates = [gr.update(visible=False), "Error: UI components not ready.", None] + [gr.update() for _ in range(2 * len(plot_configs))]
|
284 |
return error_updates
|
285 |
|
|
|
286 |
clicked_plot_label = plot_ui_objects.get(plot_id_clicked, {}).get("label", "Selected Plot")
|
287 |
|
288 |
hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
|
|
|
293 |
action_col_visible = False
|
294 |
|
295 |
if is_toggling_off:
|
296 |
+
new_active_action_state_to_set = None
|
297 |
content_text = f"{action_type.capitalize()} panel for '{clicked_plot_label}' closed."
|
298 |
action_col_visible = False
|
299 |
logging.info(f"Closing {action_type} panel for {plot_id_clicked}")
|
300 |
+
else:
|
301 |
new_active_action_state_to_set = hypothetical_new_active_state
|
302 |
action_col_visible = True
|
303 |
if action_type == "insights":
|
304 |
+
# TODO: Implement actual insight generation
|
305 |
content_text = f"**Insights for: {clicked_plot_label}**\n\nPlot ID: `{plot_id_clicked}`.\n(AI insights generation placeholder)"
|
306 |
elif action_type == "formula":
|
307 |
+
formula_key = PLOT_ID_TO_FORMULA_KEY_MAP.get(plot_id_clicked)
|
308 |
+
if formula_key and formula_key in PLOT_FORMULAS:
|
309 |
+
formula_data = PLOT_FORMULAS[formula_key]
|
310 |
+
content_text = f"### {formula_data['title']}\n\n"
|
311 |
+
content_text += f"**Description:**\n{formula_data['description']}\n\n"
|
312 |
+
content_text += "**Calculation Steps:**\n"
|
313 |
+
for step in formula_data['calculation_steps']:
|
314 |
+
content_text += f"- {step}\n"
|
315 |
+
else:
|
316 |
+
content_text = f"**Formula/Methodology for: {clicked_plot_label}**\n\nPlot ID: `{plot_id_clicked}`.\n(No detailed formula information found for this plot ID in `formulas.py`)"
|
317 |
+
logging.info(f"Displaying formula for {plot_id_clicked} (mapped to {formula_key})")
|
318 |
logging.info(f"Opening/switching to {action_type} panel for {plot_id_clicked}")
|
319 |
|
|
|
320 |
all_button_updates = []
|
321 |
for cfg_item in plot_configs:
|
322 |
p_id_iter = cfg_item["id"]
|
323 |
if p_id_iter in plot_ui_objects:
|
|
|
324 |
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "insights"}:
|
325 |
all_button_updates.append(gr.update(value=ACTIVE_ICON))
|
326 |
else:
|
327 |
all_button_updates.append(gr.update(value=BOMB_ICON))
|
328 |
|
|
|
329 |
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "formula"}:
|
330 |
all_button_updates.append(gr.update(value=ACTIVE_ICON))
|
331 |
else:
|
332 |
all_button_updates.append(gr.update(value=FORMULA_ICON))
|
333 |
+
else:
|
334 |
+
all_button_updates.extend([gr.update(), gr.update()])
|
335 |
|
336 |
final_updates = [
|
337 |
gr.update(visible=action_col_visible),
|
338 |
gr.update(value=content_text),
|
339 |
+
new_active_action_state_to_set
|
340 |
] + all_button_updates
|
341 |
|
342 |
return final_updates
|
|
|
345 |
def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state):
|
346 |
logging.info(f"Explore clicked for: {plot_id_clicked}. Currently explored from state: {current_explored_plot_id_from_state}")
|
347 |
|
348 |
+
if not plot_ui_objects:
|
349 |
logging.error("plot_ui_objects not populated during handle_explore_click.")
|
350 |
+
return [current_explored_plot_id_from_state] + [gr.update() for _ in range(2 * len(plot_configs))] # 2 updates per plot_config (panel, explore_button)
|
|
|
351 |
|
352 |
new_explored_id_to_set = None
|
353 |
is_toggling_off = (plot_id_clicked == current_explored_plot_id_from_state)
|
354 |
|
355 |
if is_toggling_off:
|
356 |
+
new_explored_id_to_set = None
|
357 |
logging.info(f"Un-exploring plot: {plot_id_clicked}")
|
358 |
else:
|
359 |
+
new_explored_id_to_set = plot_id_clicked
|
360 |
logging.info(f"Exploring plot: {plot_id_clicked}")
|
361 |
|
|
|
362 |
panel_and_button_updates = []
|
363 |
for cfg in plot_configs:
|
364 |
p_id = cfg["id"]
|
365 |
if p_id in plot_ui_objects:
|
366 |
panel_visible = not new_explored_id_to_set or (p_id == new_explored_id_to_set)
|
367 |
+
panel_and_button_updates.append(gr.update(visible=panel_visible))
|
368 |
|
369 |
+
if p_id == new_explored_id_to_set:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
panel_and_button_updates.append(gr.update(value=ACTIVE_ICON))
|
371 |
+
else:
|
372 |
panel_and_button_updates.append(gr.update(value=EXPLORE_ICON))
|
373 |
+
else:
|
374 |
panel_and_button_updates.extend([gr.update(), gr.update()])
|
375 |
|
376 |
final_updates = [new_explored_id_to_set] + panel_and_button_updates
|
377 |
return final_updates
|
378 |
|
|
|
|
|
|
|
|
|
|
|
379 |
action_buttons_outputs_list = [
|
380 |
global_actions_column_ui,
|
381 |
global_actions_markdown_ui,
|
382 |
+
active_panel_action_state
|
383 |
]
|
384 |
+
for cfg_item_action in plot_configs:
|
385 |
pid_action = cfg_item_action["id"]
|
386 |
if pid_action in plot_ui_objects:
|
387 |
action_buttons_outputs_list.append(plot_ui_objects[pid_action]["bomb_button"])
|
388 |
action_buttons_outputs_list.append(plot_ui_objects[pid_action]["formula_button"])
|
389 |
+
else:
|
390 |
+
action_buttons_outputs_list.extend([None, None])
|
391 |
|
392 |
+
explore_buttons_outputs_list = [explored_plot_id_state]
|
393 |
+
for cfg_item_explore in plot_configs:
|
|
|
394 |
pid_explore = cfg_item_explore["id"]
|
395 |
if pid_explore in plot_ui_objects:
|
396 |
+
explore_buttons_outputs_list.append(plot_ui_objects[pid_explore]["panel_component"])
|
397 |
+
explore_buttons_outputs_list.append(plot_ui_objects[pid_explore]["explore_button"])
|
398 |
+
else:
|
399 |
explore_buttons_outputs_list.extend([None, None])
|
400 |
|
|
|
|
|
|
|
401 |
action_click_inputs = [active_panel_action_state, token_state]
|
402 |
+
explore_click_inputs = [explored_plot_id_state]
|
403 |
|
404 |
for config_item in plot_configs:
|
405 |
plot_id = config_item["id"]
|
406 |
+
if plot_id in plot_ui_objects:
|
407 |
ui_obj = plot_ui_objects[plot_id]
|
408 |
|
409 |
ui_obj["bomb_button"].click(
|
|
|
425 |
api_name=f"action_explore_{plot_id}"
|
426 |
)
|
427 |
else:
|
428 |
+
logging.warning(f"UI object for plot_id '{plot_id}' not found when trying to attach click handlers.")
|
429 |
|
|
|
|
|
430 |
def refresh_all_analytics_ui_elements(current_token_state, date_filter_val, custom_start_val, custom_end_val):
|
431 |
logging.info("Refreshing all analytics UI elements and resetting actions.")
|
432 |
+
# The number of plots expected by update_analytics_plots_figures should match plot_configs length
|
433 |
+
# For this example, it's 19.
|
434 |
plot_generation_results = update_analytics_plots_figures(
|
435 |
current_token_state, date_filter_val, custom_start_val, custom_end_val
|
436 |
)
|
437 |
|
438 |
status_message_update = plot_generation_results[0]
|
439 |
+
generated_plot_figures = plot_generation_results[1:] # Should be list of 19 figures
|
440 |
|
441 |
+
all_updates = [status_message_update]
|
442 |
|
443 |
+
# Updates for plot components (19 of them)
|
444 |
+
for i in range(len(plot_configs)): # Iterate 19 times
|
445 |
if i < len(generated_plot_figures):
|
446 |
all_updates.append(generated_plot_figures[i])
|
447 |
+
else:
|
448 |
+
all_updates.append(create_placeholder_plot("Figure Error", f"Missing figure for plot {plot_configs[i]['id']}"))
|
449 |
|
450 |
+
all_updates.append(gr.update(visible=False))
|
451 |
+
all_updates.append(gr.update(value="Click a button (💣, ƒ) on a plot..."))
|
452 |
+
all_updates.append(None)
|
|
|
453 |
|
454 |
+
for cfg in plot_configs: # 19 plots
|
|
|
455 |
pid = cfg["id"]
|
456 |
if pid in plot_ui_objects:
|
457 |
+
all_updates.append(gr.update(value=BOMB_ICON))
|
458 |
+
all_updates.append(gr.update(value=FORMULA_ICON))
|
459 |
+
all_updates.append(gr.update(value=EXPLORE_ICON))
|
460 |
+
all_updates.append(gr.update(visible=True))
|
461 |
+
else:
|
462 |
all_updates.extend([None, None, None, None])
|
463 |
|
464 |
+
all_updates.append(None)
|
|
|
465 |
logging.info(f"Prepared {len(all_updates)} updates for analytics refresh.")
|
466 |
return all_updates
|
467 |
|
468 |
+
apply_filter_and_sync_outputs_list = [analytics_status_md]
|
|
|
|
|
469 |
|
470 |
+
for config_item_filter_sync in plot_configs: # 19 plots
|
|
|
471 |
pid_filter_sync = config_item_filter_sync["id"]
|
472 |
if pid_filter_sync in plot_ui_objects and "plot_component" in plot_ui_objects[pid_filter_sync]:
|
473 |
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync]["plot_component"])
|
474 |
+
else:
|
475 |
+
apply_filter_and_sync_outputs_list.append(None)
|
476 |
|
|
|
477 |
apply_filter_and_sync_outputs_list.extend([
|
478 |
global_actions_column_ui,
|
479 |
global_actions_markdown_ui,
|
480 |
+
active_panel_action_state
|
481 |
])
|
482 |
|
483 |
+
for cfg_filter_sync_btns in plot_configs: # 19 plots
|
|
|
484 |
pid_filter_sync_btns = cfg_filter_sync_btns["id"]
|
485 |
if pid_filter_sync_btns in plot_ui_objects:
|
486 |
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["bomb_button"])
|
487 |
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["formula_button"])
|
488 |
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["explore_button"])
|
489 |
+
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["panel_component"])
|
490 |
+
else:
|
491 |
apply_filter_and_sync_outputs_list.extend([None, None, None, None])
|
492 |
|
493 |
+
apply_filter_and_sync_outputs_list.append(explored_plot_id_state)
|
|
|
494 |
|
495 |
+
logging.info(f"Total outputs for apply_filter/sync: {len(apply_filter_and_sync_outputs_list)}") # Expected: 1 + 19 + 3 + (19*4) + 1 = 1 + 19 + 3 + 76 + 1 = 100
|
|
|
496 |
|
497 |
apply_filter_btn.click(
|
498 |
fn=refresh_all_analytics_ui_elements,
|
499 |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
|
500 |
+
outputs=apply_filter_and_sync_outputs_list,
|
501 |
show_progress="full"
|
502 |
)
|
503 |
|
504 |
with gr.TabItem("3️⃣ Mentions", id="tab_mentions"):
|
505 |
refresh_mentions_display_btn = gr.Button("🔄 Refresh Mentions Display", variant="secondary")
|
506 |
mentions_html = gr.HTML("Mentions data...")
|
507 |
+
mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution")
|
508 |
refresh_mentions_display_btn.click(
|
509 |
fn=run_mentions_tab_display, inputs=[token_state],
|
510 |
+
outputs=[mentions_html, mentions_sentiment_dist_plot],
|
511 |
show_progress="full"
|
512 |
)
|
513 |
|
|
|
531 |
inputs=[token_state], outputs=[sync_status_html_output, token_state], show_progress="full"
|
532 |
)
|
533 |
sync_event_part2 = sync_event_part1.then(
|
534 |
+
fn=process_and_store_bubble_token,
|
535 |
inputs=[url_user_token_display, org_urn_display, token_state],
|
536 |
+
outputs=[status_box, token_state, sync_data_btn], show_progress=False
|
537 |
)
|
538 |
sync_event_part3 = sync_event_part2.then(
|
539 |
+
fn=display_main_dashboard,
|
540 |
inputs=[token_state], outputs=[dashboard_display_html], show_progress=False
|
541 |
)
|
542 |
sync_event_final = sync_event_part3.then(
|
543 |
+
fn=refresh_all_analytics_ui_elements,
|
544 |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
|
545 |
+
outputs=apply_filter_and_sync_outputs_list, show_progress="full"
|
546 |
)
|
547 |
|
548 |
if __name__ == "__main__":
|
|
|
559 |
logging.error("Matplotlib is not installed. Plots will not be generated.")
|
560 |
|
561 |
app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
562 |
+
|