GuglielmoTor commited on
Commit
eb46c40
·
verified ·
1 Parent(s): 4761ff7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -144
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 = 23 # This should match len(plot_configs)
 
 
 
 
 
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
- # Example for profiling one plot:
92
- # plot_name = "posts_activity"
93
- # plot_start_time = time.time()
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
- # plot_figs.append(fig_mentions_activity_shared)
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)) # Assuming this is intended, though label says "Clicks"
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) # Re-adding shared plot for "Mention Analysis (Detailed)"
122
- plot_figs.append(fig_mention_sentiment_shared) # Re-adding shared plot for "Mention Analysis (Detailed)"
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): # Check if it's a Matplotlib figure
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": "li_eb_label"
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") # type="date" might be better if time not used
207
- custom_end_date_picker = gr.DateTime(label="End Date", visible=False, include_time=False, type="datetime") # type="date"
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"}, # Corrected label based on function
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) # Stores {"plot_id": "action_type"} e.g. {"posts_activity": "insights"} or None
249
- explored_plot_id_state = gr.State(None) # Stores plot_id of the currently explored plot, or None
250
 
251
- plot_ui_objects = {} # Will be populated by build_analytics_tab_plot_area
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 # Closing the panel
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: # Activating or switching
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 using current_token_state_val if needed
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
- # TODO: Implement actual formula display
299
- content_text = f"**Formula/Methodology for: {clicked_plot_label}**\n\nPlot ID: `{plot_id_clicked}`.\n(Methodology details placeholder)"
 
 
 
 
 
 
 
 
 
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: # Should not happen if plot_ui_objects is complete
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 # This is the new value for active_panel_action_state
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: # Safeguard
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 # Un-exploring
343
  logging.info(f"Un-exploring plot: {plot_id_clicked}")
344
  else:
345
- new_explored_id_to_set = plot_id_clicked # Exploring this plot
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
- # Special handling for paired plots in the same section when one is explored
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: # All other plots, or if un-exploring
366
  panel_and_button_updates.append(gr.update(value=EXPLORE_ICON))
367
- else: # Should not happen
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 # The gr.State object itself
382
  ]
383
- for cfg_item_action in plot_configs: # Iterate in defined order
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: # Should not happen if plot_ui_objects is correctly populated
389
- action_buttons_outputs_list.extend([None, None]) # Add placeholders to maintain length
390
 
391
- # Outputs for handle_explore_click
392
- explore_buttons_outputs_list = [explored_plot_id_state] # The gr.State object
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"]) # For visibility
397
- explore_buttons_outputs_list.append(plot_ui_objects[pid_explore]["explore_button"]) # For icon
398
- else: # Should not happen
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] # Only needs 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: # Ensure component exists
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. This might lead to issues.")
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 23 figures or placeholders
443
 
444
- all_updates = [status_message_update] # For analytics_status_md
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: # Should not happen if update_analytics_plots_figures pads correctly
451
- all_updates.append(create_placeholder_plot("Figure Error", f"Missing figure for plot {i}"))
452
 
453
- # Updates for global action column and its content, and active_panel_action_state
454
- all_updates.append(gr.update(visible=False)) # global_actions_column_ui
455
- all_updates.append(gr.update(value="Click a button (💣, ƒ) on a plot...")) # global_actions_markdown_ui
456
- all_updates.append(None) # Reset active_panel_action_state
457
 
458
- # Updates for all action buttons (bomb, formula), explore buttons, and panel visibility
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)) # Reset bomb_button
463
- all_updates.append(gr.update(value=FORMULA_ICON)) # Reset formula_button
464
- all_updates.append(gr.update(value=EXPLORE_ICON)) # Reset explore_button
465
- all_updates.append(gr.update(visible=True)) # Reset panel_component visibility (all visible initially)
466
- else: # Should not happen
467
  all_updates.extend([None, None, None, None])
468
 
469
- all_updates.append(None) # Reset explored_plot_id_state
470
-
471
  logging.info(f"Prepared {len(all_updates)} updates for analytics refresh.")
472
  return all_updates
473
 
474
- # --- Define outputs for the apply_filter_btn and sync.then() ---
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
- # 2. Plot components (23 of them)
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: # Should not happen
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 # The State object itself
491
  ])
492
 
493
- # 4. Action buttons (bomb, formula), explore buttons, and panel visibility for each plot (23 * 4 = 92 items)
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"]) # For panel visibility
501
- else: # Should not happen
502
  apply_filter_and_sync_outputs_list.extend([None, None, None, None])
503
 
504
- # 5. Explored plot ID state (1 item)
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, # Use the carefully constructed 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") # This is a gr.Plot component
521
  refresh_mentions_display_btn.click(
522
  fn=run_mentions_tab_display, inputs=[token_state],
523
- outputs=[mentions_html, mentions_sentiment_dist_plot], # run_mentions_tab_display returns (html_str, fig_object)
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, # This fn returns (status_msg, new_state, btn_update)
548
  inputs=[url_user_token_display, org_urn_display, token_state],
549
- outputs=[status_box, token_state, sync_data_btn], show_progress=False # btn_update is for sync_data_btn
550
  )
551
  sync_event_part3 = sync_event_part2.then(
552
- fn=display_main_dashboard, # This fn returns html_string
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, # This fn returns a list of updates
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" # Use the carefully constructed list
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
+