From 32163a9759823e919b686df8746cb5ea659ca170 Mon Sep 17 00:00:00 2001 From: Norbert <37236152+KatunaNorbert@users.noreply.github.com> Date: Fri, 4 Oct 2024 11:42:22 +0300 Subject: [PATCH] Fix #1630: PdrDashboard cache predictoor addrs on browser (#1635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * load selected predictoors into table * ppss addrs conflict fixes * write test for configuring predictoor addresses --------- Co-authored-by: Călina Cenan --- .../pdr_dashboard/callbacks/callbacks_home.py | 104 ++++++++++++++---- pdr_backend/pdr_dashboard/pages/home.py | 56 +++++++++- .../pdr_dashboard/test/test_callbacks_home.py | 94 +++++++++++++++- 3 files changed, 230 insertions(+), 24 deletions(-) diff --git a/pdr_backend/pdr_dashboard/callbacks/callbacks_home.py b/pdr_backend/pdr_dashboard/callbacks/callbacks_home.py index 7ab32157a..0b2439f75 100644 --- a/pdr_backend/pdr_dashboard/callbacks/callbacks_home.py +++ b/pdr_backend/pdr_dashboard/callbacks/callbacks_home.py @@ -85,16 +85,15 @@ def get_display_data_from_db( @app.callback( Output("predictoors_table", "data", allow_duplicate=True), - Output("predictoors_table", "selected_rows"), + Output("predictoors_table", "selected_rows", allow_duplicate=True), [ Input("search-input-Predictoors", "value"), Input("predictoors_table", "selected_rows"), Input("show-favourite-addresses", "value"), Input("general-lake-date-period-radio-items", "value"), + Input("predictoor-addrs-local-store", "data"), ], - [ - State("predictoors_table", "data"), - ], + [State("predictoors_table", "data")], prevent_initial_call=True, ) def update_predictoors_table_on_search( @@ -102,6 +101,7 @@ def update_predictoors_table_on_search( selected_rows, show_favourite_addresses, date_period, + stored_predictoor_addrs, predictoors_table, ): if ( @@ -119,21 +119,24 @@ def update_predictoors_table_on_search( ] if "show-favourite-addresses.value" in dash.callback_context.triggered_prop_ids: - custom_predictoors = formatted_predictoors_data.filter( - formatted_predictoors_data["full_addr"].is_in( - app.data.favourite_addresses + if len(stored_predictoor_addrs) > 0: + selected_predictoors_addrs = stored_predictoor_addrs + else: + custom_predictoors = formatted_predictoors_data.filter( + formatted_predictoors_data["full_addr"].is_in( + app.data.favourite_addresses + ) ) - ) - custom_predictoors_addrs = list(custom_predictoors["full_addr"]) + custom_predictoors_addrs = list(custom_predictoors["full_addr"]) - if show_favourite_addresses: - selected_predictoors_addrs += custom_predictoors_addrs - else: - selected_predictoors_addrs = [ - predictoor_addr - for predictoor_addr in selected_predictoors_addrs - if predictoor_addr not in custom_predictoors_addrs - ] + if show_favourite_addresses: + selected_predictoors_addrs += custom_predictoors_addrs + else: + selected_predictoors_addrs = [ + predictoor_addr + for predictoor_addr in selected_predictoors_addrs + if predictoor_addr not in custom_predictoors_addrs + ] filtered_data = formatted_predictoors_data.clone() if search_value: @@ -149,13 +152,13 @@ def update_predictoors_table_on_search( ) filtered_data = pl.concat([selected_predictoors, filtered_data]) - selected_predictoor_indices = list(range(len(selected_predictoors_addrs))) + selected_predictoor_indices = list(range(len(selected_predictoors))) return (filtered_data.to_dicts(), selected_predictoor_indices) @app.callback( Output("feeds_table", "data"), - Output("feeds_table", "selected_rows"), + Output("feeds_table", "selected_rows", allow_duplicate=True), [ Input("search-input-Feeds", "value"), Input("toggle-switch-predictoor-feeds", "value"), @@ -165,6 +168,7 @@ def update_predictoors_table_on_search( [ State("feeds_table", "selected_rows"), State("feeds_table", "data"), + State("predictoor-addrs-local-store", "data"), ], prevent_initial_call=True, ) @@ -176,6 +180,7 @@ def update_feeds_table_on_search( predictoors_table, selected_rows, feeds_table, + stored_predictoors_addrs, ): selected_feeds = [feeds_table[i]["contract"] for i in selected_rows] # Extract selected predictoor addresses @@ -187,6 +192,14 @@ def update_feeds_table_on_search( predictoor_feeds_only, predictoors_addrs, search_value, selected_feeds ) + # if sorted(predictoors_addrs) == sorted(stored_predictoors_addrs) + # it means we are not on the initial page load, so we use + # the feeds selected by the user + if stored_predictoors_addrs and sorted(predictoors_addrs) == sorted( + stored_predictoors_addrs + ): + selected_feeds = list(range(len(filtered_data))) + selected_feed_indices = list(range(len(selected_feeds))) return filtered_data, selected_feed_indices @@ -224,3 +237,56 @@ def select_or_clear_all_predictoors(_, __, rows): ctx = dash.callback_context return select_or_clear_all_by_table(ctx, "predictoors_table", rows) + + @app.callback( + [ + Output("predictoor_config_modal", "is_open"), + Output("predictoor_addrs", "value"), + ], + [ + Input("configure_predictoors", "n_clicks"), + Input("predictoor-addrs-local-store", "data"), + ], + prevent_initial_call=True, + ) + def open_predictoors_config_modal(n_clicks, predictoor_addrs): + """ + Select or clear all rows in the feeds table. + """ + predictoor_addrs_str = "\n".join(predictoor_addrs) if predictoor_addrs else "" + + return (bool(n_clicks > 0), predictoor_addrs_str) + + @app.callback( + Output("predictoor-addrs-local-store", "data"), + Input("save_predictoors", "n_clicks"), + State("predictoor_addrs", "value"), + ) + def save_predictoors_to_browser_storage(n_clicks, value): + if not value and not n_clicks: + return dash.no_update + addresses = value.split("\n") + addresses = [ + addr.strip() for addr in addresses if addr.strip() + ] # Remove empty lines + + return addresses + + @app.callback( + Output("show-favourite-addresses", "value"), + Output("predictoors_table", "selected_rows"), + Output("feeds_table", "selected_rows"), + Input("predictoor-addrs-local-store", "data"), + prevent_initial_call=True, + ) + def update_show_favourite_check_and_selected_predictoor_rows( + saved_predictoor_addrs, + ): + if not saved_predictoor_addrs and not app.data.favourite_addresses: + return (False, [], []) + + return ( + True if saved_predictoor_addrs else dash.no_update, + dash.no_update, + dash.no_update, + ) diff --git a/pdr_backend/pdr_dashboard/pages/home.py b/pdr_backend/pdr_dashboard/pages/home.py index 259121312..f998a8140 100644 --- a/pdr_backend/pdr_dashboard/pages/home.py +++ b/pdr_backend/pdr_dashboard/pages/home.py @@ -22,7 +22,12 @@ def layout(self): def get_main_container(self): return html.Div( - [self.get_input_column(), self.get_graphs_column()], + [ + self.get_input_column(), + self.get_graphs_column(), + self.getPredictoorsConfigModal(), + dcc.Store(id="predictoor-addrs-local-store", storage_type="local"), + ], className="main-container", ) @@ -251,6 +256,16 @@ def get_table(self, table_id, columns, data): ), html.Div( [ + ( + html.Button( + "Configure", + id="configure_predictoors", + n_clicks=0, + className="button-select-all", + ) + if table_id == "predictoors_table" + else None + ), html.Button( "Select All", id=f"select-all-{table_id}", @@ -288,3 +303,42 @@ def get_table(self, table_id, columns, data): ), ], ) + + def getPredictoorsConfigModal(self): + return dbc.Modal( + html.Div( + [ + html.H2("Configure Favourite Predictoor Addresses"), + html.P( + """Add predictoor addresses to be saved + and automatically selected when the app opens.""", + style={"marginBottom": 0}, + ), + html.P("Enter one address per line."), + dcc.Textarea( + id="predictoor_addrs", + placeholder="Predictoor addrs...", + value="", + style={"margin": "10px", "width": "60%", "height": 200}, + ), + html.Button( + "Save", + id="save_predictoors", + className="clear-filters-button", + style={ + "width": "100px", + "hight": "100%", + "padding": "5px", + }, + ), + ], + style={ + "display": "flex", + "flexDirection": "column", + "alignItems": "center", + "justifyContent": "center", + "widht": "100%", + }, + ), + id="predictoor_config_modal", + ) diff --git a/pdr_backend/pdr_dashboard/test/test_callbacks_home.py b/pdr_backend/pdr_dashboard/test/test_callbacks_home.py index 04d83eb1a..22cf31420 100644 --- a/pdr_backend/pdr_dashboard/test/test_callbacks_home.py +++ b/pdr_backend/pdr_dashboard/test/test_callbacks_home.py @@ -1,5 +1,7 @@ import time - +import platform +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.by import By from pdr_backend.pdr_dashboard.test.resources import ( _input_action, start_server_and_wait, @@ -89,8 +91,7 @@ def test_favourite_addresses_search_input( fav_addr_toggle.click() time.sleep(2) p_all, p_sel = _predictoor_count(dash_duo) - assert len(p_all) == 58 - assert len(p_sel) == 0 + assert len(p_sel) == 1 def test_checkbox_selection(_sample_app, dash_duo): @@ -125,7 +126,6 @@ def test_timeframe_metrics(_sample_app, dash_duo): time.sleep(2) dash_duo.find_element("#feeds_table tbody tr:nth-child(2) input").click() - time.sleep(2) table_profit = dash_duo.find_element( "#predictoors_table tbody tr:nth-child(2) td:nth-child(3)" @@ -169,3 +169,89 @@ def test_predictoors_feed_only_switch(_sample_app, dash_duo): feeds_table_len = len(dash_duo.find_elements("#feeds_table tbody tr")) assert feeds_table_len == 21 + + +def test_navigation(_sample_app, dash_duo): + app = _sample_app + start_server_and_wait(dash_duo, app) + + # Default page is Home + dash_duo.wait_for_element_by_id("plots_container", timeout=10) + + # Navigate to Feeds + dash_duo.wait_for_element("#navbar-container a[href='/feeds']").click() + dash_duo.wait_for_element_by_id("feeds_page_metrics_row", timeout=10) + dash_duo.wait_for_element_by_id("feeds_page_table", timeout=10) + + # Navigate to Home + dash_duo.wait_for_element("#navbar-container a[href='/']").click() + dash_duo.wait_for_element_by_id("plots_container", timeout=10) + + +def test_configure_predictoor_addresses(_sample_app, dash_duo): + app = _sample_app + start_server_and_wait(dash_duo, app) + predictoor_addrs = "0x35842372c513f8f217b968adc57a9296ba573d5c" + + # Clear selected predictoors + dash_duo.find_element("#clear-all-predictoors_table").click() + dash_duo.find_element("#clear-all-feeds_table").click() + _, p_sel = _predictoor_count(dash_duo) + _, f_sel = _feed_count(dash_duo) + assert len(p_sel) == 0 + assert len(f_sel) == 0 + + # Open config modal and save predictoor addrs + dash_duo.find_element("#configure_predictoors").click() + search_input = dash_duo.find_element("#predictoor_addrs") + search_input.clear() + search_input.send_keys(predictoor_addrs + Keys.ENTER) + dash_duo.find_element("#save_predictoors").click() + + # Check that tables were updated based on the saved predictoor addrs + _, p_sel = _predictoor_count(dash_duo) + _, f_sel = _feed_count(dash_duo) + assert len(p_sel) == 1 + assert len(f_sel) > 0 + + # Find selected predictoors and check that is the one specified inside the input + first_selected_row = p_sel[0].find_element(by=By.XPATH, value="./ancestor::tr") + first_row_second_col_value = first_selected_row.find_element( + by=By.XPATH, value="./td[2]" + ).text + assert first_row_second_col_value[:5] == predictoor_addrs[:5] + + # Refresh the page + dash_duo.driver.refresh() + + # Wait for the page to reload + dash_duo.wait_for_page() + + _, p_sel = _predictoor_count(dash_duo) + _, f_sel = _feed_count(dash_duo) + assert len(p_sel) == 1 + assert len(f_sel) > 0 + + # Find selected predictoors and check that is the one specified inside the input + first_selected_row = p_sel[0].find_element(by=By.XPATH, value="./ancestor::tr") + first_row_second_col_value = first_selected_row.find_element( + by=By.XPATH, value="./td[2]" + ).text + assert first_row_second_col_value[:5] == predictoor_addrs[:5] + + # open config modal by button click + dash_duo.find_element("#configure_predictoors").click() + + search_input = dash_duo.find_element("#predictoor_addrs") + select_all_key = Keys.COMMAND if platform.system() == "Darwin" else Keys.CONTROL + search_input.send_keys(select_all_key + "a") + search_input.send_keys(Keys.DELETE) + + dash_duo.find_element("#save_predictoors").click() + dash_duo.find_element("#predictoor_config_modal").click() + dash_duo.find_element(".modal").click() + + _, p_sel = _predictoor_count(dash_duo) + _, f_sel = _feed_count(dash_duo) + assert len(p_sel) == 0 + assert len(f_sel) == 0