From 688f766f2c889b9c82b6b706586b7bc985c5e16b Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Wed, 20 Nov 2024 14:23:56 -0500 Subject: [PATCH 1/5] expose the current panel --- dp_wizard/app/__init__.py | 13 ++++++++++++- dp_wizard/app/analysis_panel.py | 2 ++ dp_wizard/app/dataset_panel.py | 9 ++++++++- dp_wizard/app/results_panel.py | 2 ++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/dp_wizard/app/__init__.py b/dp_wizard/app/__init__.py index 435e38d..14d5f29 100644 --- a/dp_wizard/app/__init__.py +++ b/dp_wizard/app/__init__.py @@ -1,7 +1,7 @@ from pathlib import Path import logging -from shiny import App, ui, reactive +from shiny import App, ui, reactive, render from dp_wizard.utils.argparse_helpers import get_cli_info from dp_wizard.app import analysis_panel, dataset_panel, results_panel, feedback_panel @@ -11,6 +11,7 @@ app_ui = ui.page_bootstrap( ui.head_content(ui.include_css(Path(__file__).parent / "css" / "styles.css")), + ui.output_text("current_panel"), ui.navset_tab( dataset_panel.dataset_ui(), analysis_panel.analysis_ui(), @@ -37,6 +38,10 @@ def server(input, output, session): # pragma: no cover weights = reactive.value({}) epsilon = reactive.value(1) + @render.text + def current_panel(): + return input.top_level_nav() + dataset_panel.dataset_server( input, output, @@ -44,6 +49,8 @@ def server(input, output, session): # pragma: no cover is_demo=cli_info.is_demo, csv_path=csv_path, contributions=contributions, + is_ahead=False, + is_behind=False, ) analysis_panel.analysis_server( input, @@ -57,6 +64,8 @@ def server(input, output, session): # pragma: no cover bin_counts=bin_counts, weights=weights, epsilon=epsilon, + is_ahead=False, + is_behind=False, ) results_panel.results_server( input, @@ -69,6 +78,8 @@ def server(input, output, session): # pragma: no cover bin_counts=bin_counts, weights=weights, epsilon=epsilon, + is_ahead=False, + is_behind=False, ) feedback_panel.feedback_server( input, diff --git a/dp_wizard/app/analysis_panel.py b/dp_wizard/app/analysis_panel.py index 8465c1b..a015494 100644 --- a/dp_wizard/app/analysis_panel.py +++ b/dp_wizard/app/analysis_panel.py @@ -59,6 +59,8 @@ def analysis_server( bin_counts, weights, epsilon, + is_ahead, + is_behind, ): # pragma: no cover @reactive.calc def button_enabled(): diff --git a/dp_wizard/app/dataset_panel.py b/dp_wizard/app/dataset_panel.py index 9cd9836..abc65c0 100644 --- a/dp_wizard/app/dataset_panel.py +++ b/dp_wizard/app/dataset_panel.py @@ -41,7 +41,14 @@ def dataset_ui(): def dataset_server( - input, output, session, csv_path=None, contributions=None, is_demo=None + input, + output, + session, + csv_path, + contributions, + is_demo, + is_ahead, + is_behind, ): # pragma: no cover @reactive.effect @reactive.event(input.csv_path) diff --git a/dp_wizard/app/results_panel.py b/dp_wizard/app/results_panel.py index 8d448c5..0d7c292 100644 --- a/dp_wizard/app/results_panel.py +++ b/dp_wizard/app/results_panel.py @@ -36,6 +36,8 @@ def results_server( bin_counts, weights, epsilon, + is_ahead, + is_behind, ): # pragma: no cover @reactive.calc def analysis_plan() -> AnalysisPlan: From c16ffeee649909806d5e0f9e5fec80a15c1877d1 Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Wed, 20 Nov 2024 15:12:49 -0500 Subject: [PATCH 2/5] state is updated by buttons but not by tabs --- dp_wizard/app/__init__.py | 15 ++++++--------- dp_wizard/app/analysis_panel.py | 16 ++++++++++++---- dp_wizard/app/dataset_panel.py | 16 ++++++++++++---- dp_wizard/app/results_panel.py | 12 +++++++++--- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/dp_wizard/app/__init__.py b/dp_wizard/app/__init__.py index 14d5f29..9ef7929 100644 --- a/dp_wizard/app/__init__.py +++ b/dp_wizard/app/__init__.py @@ -11,7 +11,6 @@ app_ui = ui.page_bootstrap( ui.head_content(ui.include_css(Path(__file__).parent / "css" / "styles.css")), - ui.output_text("current_panel"), ui.navset_tab( dataset_panel.dataset_ui(), analysis_panel.analysis_ui(), @@ -37,10 +36,11 @@ def server(input, output, session): # pragma: no cover bin_counts = reactive.value({}) weights = reactive.value({}) epsilon = reactive.value(1) + current_panel = reactive.value(dataset_panel.dataset_panel_id) @render.text - def current_panel(): - return input.top_level_nav() + def current_panel_text(): + return current_panel() dataset_panel.dataset_server( input, @@ -49,8 +49,7 @@ def current_panel(): is_demo=cli_info.is_demo, csv_path=csv_path, contributions=contributions, - is_ahead=False, - is_behind=False, + current_panel=current_panel, ) analysis_panel.analysis_server( input, @@ -64,8 +63,7 @@ def current_panel(): bin_counts=bin_counts, weights=weights, epsilon=epsilon, - is_ahead=False, - is_behind=False, + current_panel=current_panel, ) results_panel.results_server( input, @@ -78,8 +76,7 @@ def current_panel(): bin_counts=bin_counts, weights=weights, epsilon=epsilon, - is_ahead=False, - is_behind=False, + current_panel=current_panel, ) feedback_panel.feedback_server( input, diff --git a/dp_wizard/app/analysis_panel.py b/dp_wizard/app/analysis_panel.py index a015494..a6b6335 100644 --- a/dp_wizard/app/analysis_panel.py +++ b/dp_wizard/app/analysis_panel.py @@ -8,11 +8,15 @@ from dp_wizard.app.components.outputs import output_code_sample, demo_tooltip from dp_wizard.utils.code_generators import make_privacy_loss_block from dp_wizard.app.components.column_module import col_widths +from dp_wizard.app import results_panel + +analysis_panel_id = "2_analysis_panel" def analysis_ui(): return ui.nav_panel( "Define Analysis", + ui.output_text("current_panel_text_on_analysis"), ui.markdown( "Select numeric columns of interest, " "and for each numeric column indicate the expected range, " @@ -35,7 +39,7 @@ def analysis_ui(): ui.output_text("epsilon_text"), output_code_sample("Privacy Loss", "privacy_loss_python"), ui.output_ui("download_results_button_ui"), - value="analysis_panel", + value=analysis_panel_id, ) @@ -59,9 +63,12 @@ def analysis_server( bin_counts, weights, epsilon, - is_ahead, - is_behind, + current_panel, ): # pragma: no cover + @render.text + def current_panel_text_on_analysis(): + return current_panel() + @reactive.calc def button_enabled(): column_ids_selected = input.columns_checkbox_group() @@ -178,7 +185,8 @@ def privacy_loss_python(): @reactive.effect @reactive.event(input.go_to_results) def go_to_results(): - ui.update_navs("top_level_nav", selected="results_panel") + current_panel.set(results_panel.results_panel_id) + ui.update_navs("top_level_nav", selected=results_panel.results_panel_id) @render.ui def download_results_button_ui(): diff --git a/dp_wizard/app/dataset_panel.py b/dp_wizard/app/dataset_panel.py index abc65c0..59517f3 100644 --- a/dp_wizard/app/dataset_panel.py +++ b/dp_wizard/app/dataset_panel.py @@ -5,6 +5,9 @@ from dp_wizard.utils.argparse_helpers import get_cli_info from dp_wizard.app.components.outputs import output_code_sample, demo_tooltip from dp_wizard.utils.code_generators import make_privacy_unit_block +from dp_wizard.app import analysis_panel + +dataset_panel_id = "1_dataset_panel" def dataset_ui(): @@ -15,6 +18,7 @@ def dataset_ui(): return ui.nav_panel( "Select Dataset", + ui.output_text("current_panel_text_on_dataset"), # Doesn't seem to be possible to preset the actual value, # but the placeholder string is a good substitute. ui.input_file( @@ -36,7 +40,7 @@ def dataset_ui(): ui.output_ui("python_tooltip_ui"), output_code_sample("Unit of Privacy", "unit_of_privacy_python"), ui.output_ui("define_analysis_button_ui"), - value="dataset_panel", + value=dataset_panel_id, ) @@ -47,9 +51,12 @@ def dataset_server( csv_path, contributions, is_demo, - is_ahead, - is_behind, + current_panel, ): # pragma: no cover + @render.text + def current_panel_text_on_dataset(): + return current_panel() + @reactive.effect @reactive.event(input.csv_path) def _on_csv_path_change(): @@ -113,4 +120,5 @@ def unit_of_privacy_python(): @reactive.effect @reactive.event(input.go_to_analysis) def go_to_analysis(): - ui.update_navs("top_level_nav", selected="analysis_panel") + current_panel.set(analysis_panel.analysis_panel_id) + ui.update_navs("top_level_nav", selected=analysis_panel.analysis_panel_id) diff --git a/dp_wizard/app/results_panel.py b/dp_wizard/app/results_panel.py index 0d7c292..d98591a 100644 --- a/dp_wizard/app/results_panel.py +++ b/dp_wizard/app/results_panel.py @@ -8,10 +8,13 @@ ) from dp_wizard.utils.converters import convert_py_to_nb +results_panel_id = "3_results_panel" + def results_ui(): return ui.nav_panel( "Download results", + ui.output_text("current_panel_text_on_results"), ui.markdown("You can now make a differentially private release of your data."), ui.download_button( "download_script", @@ -21,7 +24,7 @@ def results_ui(): "download_notebook", "Download Notebook (.ipynb)", ), - value="results_panel", + value=results_panel_id, ) @@ -36,9 +39,12 @@ def results_server( bin_counts, weights, epsilon, - is_ahead, - is_behind, + current_panel, ): # pragma: no cover + @render.text + def current_panel_text_on_results(): + return current_panel() + @reactive.calc def analysis_plan() -> AnalysisPlan: # weights().keys() will reflect the desired columns: From 9efb2045a740d71b6eae9cdb4e1ce9d762a040e5 Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Wed, 20 Nov 2024 22:13:28 -0500 Subject: [PATCH 3/5] some warnings --- dp_wizard/app/analysis_panel.py | 19 +++++++++++++++---- dp_wizard/app/dataset_panel.py | 15 +++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/dp_wizard/app/analysis_panel.py b/dp_wizard/app/analysis_panel.py index a6b6335..b5f7c28 100644 --- a/dp_wizard/app/analysis_panel.py +++ b/dp_wizard/app/analysis_panel.py @@ -16,7 +16,7 @@ def analysis_ui(): return ui.nav_panel( "Define Analysis", - ui.output_text("current_panel_text_on_analysis"), + ui.output_ui("analysis_panel_warning"), ui.markdown( "Select numeric columns of interest, " "and for each numeric column indicate the expected range, " @@ -65,9 +65,20 @@ def analysis_server( epsilon, current_panel, ): # pragma: no cover - @render.text - def current_panel_text_on_analysis(): - return current_panel() + @render.ui + def analysis_panel_warning(): + if current_panel() > analysis_panel_id: + return """ + Once you've confirmed your analysis settings + they are locked. The privacy budget should be considered + a finite resource. + """ + if current_panel() < analysis_panel_id: + return """ + This form is locked until you've confirmed your + dataset and unit of privacy. + """ + return "" @reactive.calc def button_enabled(): diff --git a/dp_wizard/app/dataset_panel.py b/dp_wizard/app/dataset_panel.py index 59517f3..19b733f 100644 --- a/dp_wizard/app/dataset_panel.py +++ b/dp_wizard/app/dataset_panel.py @@ -18,7 +18,7 @@ def dataset_ui(): return ui.nav_panel( "Select Dataset", - ui.output_text("current_panel_text_on_dataset"), + ui.output_ui("dataset_panel_warning"), # Doesn't seem to be possible to preset the actual value, # but the placeholder string is a good substitute. ui.input_file( @@ -53,9 +53,16 @@ def dataset_server( is_demo, current_panel, ): # pragma: no cover - @render.text - def current_panel_text_on_dataset(): - return current_panel() + @render.ui + def dataset_panel_warning(): + if current_panel() > dataset_panel_id: + return """ + Once you've confirmed your dataset and the unit of privacy + they are locked. The unit of privacy is a characteristic + of your dataset and shouldn't be tweaked just to improve + utility. + """ + return "" @reactive.effect @reactive.event(input.csv_path) From 1b637a1ae73a7793039630ffafad1c190cf135be Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Wed, 20 Nov 2024 22:17:11 -0500 Subject: [PATCH 4/5] message on results --- dp_wizard/app/results_panel.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dp_wizard/app/results_panel.py b/dp_wizard/app/results_panel.py index d98591a..6cfef13 100644 --- a/dp_wizard/app/results_panel.py +++ b/dp_wizard/app/results_panel.py @@ -14,7 +14,7 @@ def results_ui(): return ui.nav_panel( "Download results", - ui.output_text("current_panel_text_on_results"), + ui.output_ui("results_panel_warning"), ui.markdown("You can now make a differentially private release of your data."), ui.download_button( "download_script", @@ -42,8 +42,13 @@ def results_server( current_panel, ): # pragma: no cover @render.text - def current_panel_text_on_results(): - return current_panel() + def results_panel_warning(): + if current_panel() < results_panel_id: + return """ + This tab is locked until you've confirmed your + analysis details. + """ + return "" @reactive.calc def analysis_plan() -> AnalysisPlan: From 2e3450b9d95efa8cef2c2aa9ce86bbc8091c21e2 Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Wed, 20 Nov 2024 22:39:54 -0500 Subject: [PATCH 5/5] put warnings in a box --- dp_wizard/app/analysis_panel.py | 11 ++++++++--- dp_wizard/app/components/outputs.py | 4 ++++ dp_wizard/app/dataset_panel.py | 7 +++++-- dp_wizard/app/results_panel.py | 5 ++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/dp_wizard/app/analysis_panel.py b/dp_wizard/app/analysis_panel.py index b5f7c28..5e86140 100644 --- a/dp_wizard/app/analysis_panel.py +++ b/dp_wizard/app/analysis_panel.py @@ -9,6 +9,7 @@ from dp_wizard.utils.code_generators import make_privacy_loss_block from dp_wizard.app.components.column_module import col_widths from dp_wizard.app import results_panel +from dp_wizard.app.components.outputs import info_box analysis_panel_id = "2_analysis_panel" @@ -68,16 +69,20 @@ def analysis_server( @render.ui def analysis_panel_warning(): if current_panel() > analysis_panel_id: - return """ + return info_box( + """ Once you've confirmed your analysis settings they are locked. The privacy budget should be considered a finite resource. """ + ) if current_panel() < analysis_panel_id: - return """ + return info_box( + """ This form is locked until you've confirmed your dataset and unit of privacy. """ + ) return "" @reactive.calc @@ -209,5 +214,5 @@ def download_results_button_ui(): return button return [ button, - "Select one or more columns before proceeding.", + info_box("Select one or more columns before proceeding."), ] diff --git a/dp_wizard/app/components/outputs.py b/dp_wizard/app/components/outputs.py index a66e2e2..4019ae3 100644 --- a/dp_wizard/app/components/outputs.py +++ b/dp_wizard/app/components/outputs.py @@ -17,3 +17,7 @@ def demo_tooltip(is_demo, text): # pragma: no cover text, placement="right", ) + + +def info_box(text): # pragma: no cover + return ui.div(text, class_="alert alert-info col-md-10 col-lg-8", role="alert") diff --git a/dp_wizard/app/dataset_panel.py b/dp_wizard/app/dataset_panel.py index 19b733f..8885806 100644 --- a/dp_wizard/app/dataset_panel.py +++ b/dp_wizard/app/dataset_panel.py @@ -6,6 +6,7 @@ from dp_wizard.app.components.outputs import output_code_sample, demo_tooltip from dp_wizard.utils.code_generators import make_privacy_unit_block from dp_wizard.app import analysis_panel +from dp_wizard.app.components.outputs import info_box dataset_panel_id = "1_dataset_panel" @@ -56,12 +57,14 @@ def dataset_server( @render.ui def dataset_panel_warning(): if current_panel() > dataset_panel_id: - return """ + return info_box( + """ Once you've confirmed your dataset and the unit of privacy they are locked. The unit of privacy is a characteristic of your dataset and shouldn't be tweaked just to improve utility. """ + ) return "" @reactive.effect @@ -117,7 +120,7 @@ def define_analysis_button_ui(): return button return [ button, - "Choose CSV and Contributions before proceeding.", + info_box("Choose CSV and Contributions before proceeding."), ] @render.code diff --git a/dp_wizard/app/results_panel.py b/dp_wizard/app/results_panel.py index 6cfef13..c3fedf6 100644 --- a/dp_wizard/app/results_panel.py +++ b/dp_wizard/app/results_panel.py @@ -7,6 +7,7 @@ AnalysisPlanColumn, ) from dp_wizard.utils.converters import convert_py_to_nb +from dp_wizard.app.components.outputs import info_box results_panel_id = "3_results_panel" @@ -44,10 +45,12 @@ def results_server( @render.text def results_panel_warning(): if current_panel() < results_panel_id: - return """ + return info_box( + """ This tab is locked until you've confirmed your analysis details. """ + ) return "" @reactive.calc