Avaiga / taipy

Turns Data and AI algorithms into production-ready web applications in no time.
https://www.taipy.io
Apache License 2.0
10.94k stars 775 forks source link

[🐛 BUG] When a table has a lot of content, updates to it are not always reflected in the visuals #1397

Open gbritoda opened 2 months ago

gbritoda commented 2 months ago

What went wrong? 🤔

Raised in Discord first: https://discord.com/channels/1125797687476887563/1242498359093100667

I have a table that contains a bunch of different information, some of the rows have different "categories" though, and I want to be able to filter through these categories.

I did that through a selector on the side (not a dropdown one). The default category selected is "All", to show all, when a category is selected it changes a variable called selected_category, then on a callback called by the on_change of the selector will modify the contents of the table to only show the rows belonging to that category, followed by a state.refresh(table), because the table is part of another python object.

However that doesn't always work, in fact I can consistently get into a state where the table does not refresh, but if I refresh the page the changes are made. From the logs and from this it seems the table indeed does change but the change is not reflected visually. The table does have the rebuild property turned on.

Recorded a gif of an example code. Although it took a while for me to actually reproduce it because I made a much "lighter" version. You can see in the beginning of the gif that I was stuck, I refreshed and kept clicking multiple times until I managed to get it again. With this example was quite difficult but with my actual application (which is big) is a bit easy to get to that state

There might be a few quirks (e.g. taipy_thread) that you might find it unecessary. It's just something I made back in 2.4 and unsure if it still needed

This is the code used in the above Gif:

import threading
from taipy.gui import Gui, Markdown, get_state_id, invoke_callback
from time import sleep

# Categories
ALL_CAT = "All"
MEAT_CAT = "Meat"
SALAD_CAT = "Salad"
CHEESE_CAT = "Cheese"

class FoodMenu:
    def __init__(self) -> None:
        self.load_menu()
        self.columns = [
            "Food", "Price"
        ]
        self.current_selected_category = ALL_CAT
        self.update_content_being_shown()
        self.selector_active = True

    def load_menu(self):
        self.categories = [ALL_CAT, MEAT_CAT, SALAD_CAT, CHEESE_CAT]
        meat = [(f"fish{i}", f"£{i}.50") for i in range(100)]
        salad = [(f"lettuce{i}", "£1") for i in range(50)]
        cheese = [(f"brie{i}", f"£{i}.{i}") for i in range(200)]
        self.menu = {
            MEAT_CAT: meat,
            SALAD_CAT: salad,
            CHEESE_CAT: cheese,
            ALL_CAT: meat + salad + cheese
        }

    def update_content_being_shown(self):
        # self.showing_content is a filtered and reorganised version of self.menu
        self.showing_content = self.get_content_for_category(self.current_selected_category)

    def get_content_for_category(self, category:str):
        content = {}
        for i in range(len(self.columns)):
            content[self.columns[i]] = [menu_info[i] for menu_info in self.menu[category]]
        return content

FOOD_MENU = FoodMenu()

page = Markdown("""
<|{FOOD_MENU.current_selected_category}|selector|lov={FOOD_MENU.categories}|active={FOOD_MENU.selector_active}|on_change=on_category_selection_change|>

<|{FOOD_MENU.showing_content}|table|columns={FOOD_MENU.columns}|show_all|rebuild|filter|number_format=%,|hover_text=List of available food|width=100%|>
""")

def taipy_thread(state, callback, args):
    """Creates a thread that TaiPy can handle"""
    state_id = get_state_id(state)
    return threading.Thread(target=invoke_callback, args=[GUI_OBJ, state_id, callback, args], daemon=True)

def on_category_selection_change(state):
    print(f"Displayed category changed to {state.FOOD_MENU.current_selected_category}")
    if state.FOOD_MENU.selector_active == False:
        return
    else:
        # The updating is done on a different thread to allow TaiPy to swiftly re-render the selector as inactive.
        # If the updating is done in this callback the visual effects wont be seen live as this thread 'hogs' whatever
        # taipy does in the background to update visual elements. At least it is what I have seen?
        taipy_thread(state, update_displayed_content_and_reenable_selection, args=[]).run()

def update_displayed_content_and_reenable_selection(state):
    state.FOOD_MENU.selector_active = False
    state.refresh('FOOD_MENU')
    print("Updating displayed content")
    SIDE_MENU_INACTIVE_FOR = 0.4
    try:
        state.FOOD_MENU.update_content_being_shown()
        state.refresh('FOOD_MENU')
        # A little wait time to extend the inactive state of the selector
        # allows time for the web browser to load the filtered menu and enhances the UX a bit
        # There's no way to tell when an element has finished loading unfortunately
        print(f"Selection menu inactive for {SIDE_MENU_INACTIVE_FOR} seconds")
        sleep(SIDE_MENU_INACTIVE_FOR)
    finally:
        state.FOOD_MENU.selector_active = True
        state.refresh('FOOD_MENU')
        print("Finished updating displayed content")

GUI_OBJ = Gui(page=page)
GUI_OBJ.run(
    use_reloader=True
)

Expected Behavior

When clicking the buttons to change the table category, the table contents should update accordingly and not freeze

Steps to Reproduce Issue

Run the code snippet in the description, click the category buttons multiple times until the table freezes.

Solution Proposed

No response

Screenshots

No response

Runtime Environment

No response

Browsers

Chrome, Firefox

OS

Linux

Version of Taipy

3.0.0

Additional Context

No response

Code of Conduct

FlorianJacta commented 2 months ago

Thank you for writing the issue! We discussed it with @FredLL-Avaiga and shown it the issue first hand some time ago

github-actions[bot] commented 1 month ago

This issue has been labelled as "🥶Waiting for contributor" because it has been inactive for more than 14 days. If you would like to continue working on this issue, please add another comment or create a PR that links to this issue. If a PR has already been created which refers to this issue, then you should explicitly mention this issue in the relevant PR. Otherwise, you will be unassigned in 14 days. For more information please refer to the contributing guidelines.

github-actions[bot] commented 3 weeks ago

This issue has been unassigned automatically because it has been marked as "🥶Waiting for contributor" for more than 14 days with no activity.

FredLL-Avaiga commented 1 week ago

I can't replicate the issue ? Can you @FlorianJacta ?

FlorianJacta commented 1 week ago

I was, and from my memory, we had to force it to happen. I will try again. You can't replicate it on develop?

FlorianJacta commented 1 week ago

I can replicate it with develop. Just change the filters really quick