algomancy-gui 0.3.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algomancy_gui/__init__.py +0 -0
- algomancy_gui/admin_page/__init__.py +1 -0
- algomancy_gui/admin_page/admin.py +362 -0
- algomancy_gui/admin_page/sessions.py +57 -0
- algomancy_gui/appconfiguration.py +291 -0
- algomancy_gui/compare_page/__init__.py +1 -0
- algomancy_gui/compare_page/compare.py +360 -0
- algomancy_gui/compare_page/kpicard.py +236 -0
- algomancy_gui/compare_page/scenarioselector.py +99 -0
- algomancy_gui/componentids.py +177 -0
- algomancy_gui/contentregistry.py +167 -0
- algomancy_gui/cqmloader.py +58 -0
- algomancy_gui/data_page/__init__.py +1 -0
- algomancy_gui/data_page/data.py +77 -0
- algomancy_gui/data_page/datamanagementdeletemodal.py +260 -0
- algomancy_gui/data_page/datamanagementderivemodal.py +201 -0
- algomancy_gui/data_page/datamanagementdownloadmodal.py +193 -0
- algomancy_gui/data_page/datamanagementimportmodal.py +438 -0
- algomancy_gui/data_page/datamanagementsavemodal.py +191 -0
- algomancy_gui/data_page/datamanagementtopbar.py +123 -0
- algomancy_gui/data_page/datamanagementuploadmodal.py +366 -0
- algomancy_gui/data_page/dialogcallbacks.py +51 -0
- algomancy_gui/data_page/filenamematcher.py +109 -0
- algomancy_gui/defaultloader.py +36 -0
- algomancy_gui/gui_launcher.py +183 -0
- algomancy_gui/home_page/__init__.py +1 -0
- algomancy_gui/home_page/home.py +16 -0
- algomancy_gui/layout.py +199 -0
- algomancy_gui/layouthelpers.py +30 -0
- algomancy_gui/managergetters.py +28 -0
- algomancy_gui/overview_page/__init__.py +1 -0
- algomancy_gui/overview_page/overview.py +20 -0
- algomancy_gui/py.typed +0 -0
- algomancy_gui/scenario_page/__init__.py +0 -0
- algomancy_gui/scenario_page/delete_confirmation.py +29 -0
- algomancy_gui/scenario_page/new_scenario_creator.py +104 -0
- algomancy_gui/scenario_page/new_scenario_parameters_window.py +154 -0
- algomancy_gui/scenario_page/scenario_badge.py +36 -0
- algomancy_gui/scenario_page/scenario_cards.py +119 -0
- algomancy_gui/scenario_page/scenarios.py +596 -0
- algomancy_gui/sessionmanager.py +168 -0
- algomancy_gui/settingsmanager.py +43 -0
- algomancy_gui/stylingconfigurator.py +740 -0
- algomancy_gui-0.3.16.dist-info/METADATA +71 -0
- algomancy_gui-0.3.16.dist-info/RECORD +46 -0
- algomancy_gui-0.3.16.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from dash import html
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# todo rename to cqm_loading_animation
|
|
5
|
+
def cqm_loader(text="Loading...", scale: float | None = None):
|
|
6
|
+
"""
|
|
7
|
+
Creates a custom CQM animated loader with three letters (C, Q, M) that fade in sequence.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
scale (float): float to control the size of the spinner. min: 0.5, max: 5
|
|
11
|
+
text (str): The text to display below the animation
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
html.Div: A div containing the animated loader
|
|
15
|
+
"""
|
|
16
|
+
assert (
|
|
17
|
+
not scale or 0.5 <= scale <= 5
|
|
18
|
+
), f"Invalid scale for loader: {scale}. (min: 0.5, max: 5)"
|
|
19
|
+
|
|
20
|
+
default_height = 100
|
|
21
|
+
default_width = 100
|
|
22
|
+
default_font_size = 1.5
|
|
23
|
+
|
|
24
|
+
letter_style = {}
|
|
25
|
+
if scale is not None:
|
|
26
|
+
letter_style["height"] = f"{scale * default_height}px"
|
|
27
|
+
letter_style["width"] = f"{scale * default_width}px"
|
|
28
|
+
|
|
29
|
+
text_style = {}
|
|
30
|
+
if scale is not None:
|
|
31
|
+
text_style["font-size"] = f"{scale * default_font_size}rem"
|
|
32
|
+
|
|
33
|
+
return html.Div(
|
|
34
|
+
[
|
|
35
|
+
html.Div(
|
|
36
|
+
[
|
|
37
|
+
html.Img(
|
|
38
|
+
src="/assets/letter-c.svg",
|
|
39
|
+
className="cqm-letter c",
|
|
40
|
+
style=letter_style,
|
|
41
|
+
),
|
|
42
|
+
html.Img(
|
|
43
|
+
src="/assets/letter-q.svg",
|
|
44
|
+
className="cqm-letter q",
|
|
45
|
+
style=letter_style,
|
|
46
|
+
),
|
|
47
|
+
html.Img(
|
|
48
|
+
src="/assets/letter-m.svg",
|
|
49
|
+
className="cqm-letter m",
|
|
50
|
+
style=letter_style,
|
|
51
|
+
),
|
|
52
|
+
],
|
|
53
|
+
className="cqm-loader",
|
|
54
|
+
),
|
|
55
|
+
html.Div(text, className="cqm-loader-text", style=text_style),
|
|
56
|
+
],
|
|
57
|
+
style={"textAlign": "center", "padding": "20px"},
|
|
58
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file is intentionally left empty to make the directory a Python package.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from dash import html, get_app, callback, Output, Input, State
|
|
2
|
+
|
|
3
|
+
from algomancy_scenario import ScenarioManager
|
|
4
|
+
|
|
5
|
+
from ..componentids import (
|
|
6
|
+
DATA_PAGE_CONTENT,
|
|
7
|
+
DATA_SELECTOR_DROPDOWN,
|
|
8
|
+
ACTIVE_SESSION,
|
|
9
|
+
DATA_PAGE,
|
|
10
|
+
)
|
|
11
|
+
from ..data_page.datamanagementtopbar import top_bar
|
|
12
|
+
from algomancy_gui.managergetters import get_scenario_manager
|
|
13
|
+
from ..layouthelpers import create_wrapped_content_div
|
|
14
|
+
from ..contentregistry import ContentRegistry
|
|
15
|
+
|
|
16
|
+
from ..data_page import dialogcallbacks # noqa
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def data_page() -> html.Div:
|
|
20
|
+
"""
|
|
21
|
+
Creates the data page layout with raw data view and warehouse layout visualization.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
html.Div: A Dash HTML component representing the data page
|
|
25
|
+
"""
|
|
26
|
+
# Placeholder will be filled by callback
|
|
27
|
+
return html.Div(
|
|
28
|
+
id=DATA_PAGE,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@callback(
|
|
33
|
+
Output(DATA_PAGE, "children"),
|
|
34
|
+
Input(ACTIVE_SESSION, "data"),
|
|
35
|
+
)
|
|
36
|
+
def render_data_page(active_session_name):
|
|
37
|
+
"""Creates the data page layout with raw data view and warehouse layout visualization."""
|
|
38
|
+
if not active_session_name:
|
|
39
|
+
return html.Div("No active session selected")
|
|
40
|
+
|
|
41
|
+
sm: ScenarioManager = get_scenario_manager(get_app().server, active_session_name)
|
|
42
|
+
|
|
43
|
+
settings = get_app().server.settings
|
|
44
|
+
|
|
45
|
+
main_div = create_wrapped_content_div(
|
|
46
|
+
content_div(),
|
|
47
|
+
settings.show_loading_on_datapage,
|
|
48
|
+
settings.use_cqm_loader,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return [html.H1("Data"), top_bar(sm), main_div]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def content_div() -> html.Div:
|
|
55
|
+
return html.Div(
|
|
56
|
+
html.Div(className="data-page-content"), # placeholder
|
|
57
|
+
id=DATA_PAGE_CONTENT,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@callback(
|
|
62
|
+
Output(DATA_PAGE_CONTENT, "children"),
|
|
63
|
+
Input(DATA_SELECTOR_DROPDOWN, "value"),
|
|
64
|
+
State(ACTIVE_SESSION, "data"),
|
|
65
|
+
prevent_initial_call=True,
|
|
66
|
+
)
|
|
67
|
+
def fill_data_page_content(data_key: str, session_id: str):
|
|
68
|
+
sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
|
|
69
|
+
cr: ContentRegistry = get_app().server.content_registry
|
|
70
|
+
|
|
71
|
+
if data_key not in sm.get_data_keys():
|
|
72
|
+
return [html.P("Select a dataset.")]
|
|
73
|
+
|
|
74
|
+
data = sm.get_data(data_key)
|
|
75
|
+
page = cr.data_content(data)
|
|
76
|
+
|
|
77
|
+
return page
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
import dash
|
|
4
|
+
import dash_bootstrap_components as dbc
|
|
5
|
+
from dash import html, dcc, callback, Output, Input, no_update, get_app, State
|
|
6
|
+
|
|
7
|
+
from algomancy_scenario import ScenarioManager
|
|
8
|
+
from ..componentids import (
|
|
9
|
+
DM_DELETE_SET_SELECTOR,
|
|
10
|
+
DM_DELETE_COLLAPSE,
|
|
11
|
+
DM_DELETE_CONFIRM_INPUT,
|
|
12
|
+
DM_DELETE_SUBMIT_BUTTON,
|
|
13
|
+
DM_DELETE_CLOSE_BUTTON,
|
|
14
|
+
DM_DELETE_MODAL,
|
|
15
|
+
DM_LIST_UPDATER_STORE,
|
|
16
|
+
DATA_MAN_SUCCESS_ALERT,
|
|
17
|
+
DATA_MAN_ERROR_ALERT,
|
|
18
|
+
DM_DELETE_OPEN_BUTTON,
|
|
19
|
+
ACTIVE_SESSION,
|
|
20
|
+
)
|
|
21
|
+
from algomancy_gui.managergetters import get_scenario_manager
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
Modal component for deleting datasets from the application.
|
|
25
|
+
|
|
26
|
+
This module provides a modal dialog that allows users to select and delete
|
|
27
|
+
datasets, with additional confirmation required for master data deletion.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def data_management_delete_modal(sm: ScenarioManager, themed_styling):
|
|
32
|
+
"""
|
|
33
|
+
Creates a modal dialog component for deleting datasets.
|
|
34
|
+
|
|
35
|
+
The modal contains a dropdown to select the dataset to delete and a confirmation
|
|
36
|
+
input field that appears when master data is selected. The confirmation field
|
|
37
|
+
requires the user to type "DELETE" to proceed with deletion of master data.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
sm: ScenarioManager instance used to populate the dataset dropdown
|
|
41
|
+
themed_styling: Dictionary of CSS styling properties
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
dbc.Modal: A Dash Bootstrap Components modal dialog
|
|
45
|
+
"""
|
|
46
|
+
return dbc.Modal(
|
|
47
|
+
[
|
|
48
|
+
dbc.ModalHeader(dbc.ModalTitle("Delete"), close_button=False),
|
|
49
|
+
dbc.ModalBody(
|
|
50
|
+
dbc.Row(
|
|
51
|
+
[
|
|
52
|
+
dbc.Col(
|
|
53
|
+
html.Div(
|
|
54
|
+
html.P("Set to delete: "),
|
|
55
|
+
),
|
|
56
|
+
width=3,
|
|
57
|
+
className="justify-content-right",
|
|
58
|
+
),
|
|
59
|
+
dbc.Col(
|
|
60
|
+
[
|
|
61
|
+
dcc.Dropdown(
|
|
62
|
+
id=DM_DELETE_SET_SELECTOR,
|
|
63
|
+
options=[
|
|
64
|
+
{"label": ds, "value": ds}
|
|
65
|
+
for ds in sm.get_data_keys()
|
|
66
|
+
],
|
|
67
|
+
value="",
|
|
68
|
+
placeholder="Select dataset",
|
|
69
|
+
),
|
|
70
|
+
],
|
|
71
|
+
width=9,
|
|
72
|
+
),
|
|
73
|
+
dbc.Collapse(
|
|
74
|
+
children=[
|
|
75
|
+
html.P(
|
|
76
|
+
"WARNING: you are about to delete master data. "
|
|
77
|
+
"Associated files will be permanently removed.",
|
|
78
|
+
className="mt-2",
|
|
79
|
+
),
|
|
80
|
+
html.P(
|
|
81
|
+
"Enter DELETE to confirm deletion:",
|
|
82
|
+
),
|
|
83
|
+
dcc.Input(
|
|
84
|
+
id=DM_DELETE_CONFIRM_INPUT,
|
|
85
|
+
placeholder="DELETE",
|
|
86
|
+
className="mt-2",
|
|
87
|
+
),
|
|
88
|
+
],
|
|
89
|
+
id=DM_DELETE_COLLAPSE,
|
|
90
|
+
is_open=False,
|
|
91
|
+
class_name="mt-2",
|
|
92
|
+
),
|
|
93
|
+
],
|
|
94
|
+
className="mb-4",
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
dbc.ModalFooter(
|
|
98
|
+
[
|
|
99
|
+
dbc.Button(
|
|
100
|
+
"Delete",
|
|
101
|
+
id=DM_DELETE_SUBMIT_BUTTON,
|
|
102
|
+
class_name="dm-delete-modal-confirm-btn",
|
|
103
|
+
),
|
|
104
|
+
dbc.Button(
|
|
105
|
+
"Close",
|
|
106
|
+
id=DM_DELETE_CLOSE_BUTTON,
|
|
107
|
+
class_name="dm-delete-modal-cancel-btn ms-auto",
|
|
108
|
+
n_clicks=0,
|
|
109
|
+
),
|
|
110
|
+
]
|
|
111
|
+
),
|
|
112
|
+
],
|
|
113
|
+
id=DM_DELETE_MODAL,
|
|
114
|
+
is_open=False,
|
|
115
|
+
centered=True,
|
|
116
|
+
class_name="themed-modal",
|
|
117
|
+
style=themed_styling,
|
|
118
|
+
keyboard=False,
|
|
119
|
+
backdrop="static",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@callback(
|
|
124
|
+
Output(DM_DELETE_SET_SELECTOR, "value"),
|
|
125
|
+
Input(DM_DELETE_MODAL, "is_open"),
|
|
126
|
+
prevent_initial_call=True,
|
|
127
|
+
)
|
|
128
|
+
def reset_on_close(modal_is_open: bool):
|
|
129
|
+
"""
|
|
130
|
+
Resets the delete dataset selector when the delete modal is closed.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
modal_is_open: Boolean indicating if the modal is open
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
None if the modal is closed, no_update otherwise
|
|
137
|
+
"""
|
|
138
|
+
if not modal_is_open:
|
|
139
|
+
return None
|
|
140
|
+
return no_update
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@callback(
|
|
144
|
+
[
|
|
145
|
+
Output(DM_DELETE_COLLAPSE, "is_open"),
|
|
146
|
+
Output(DM_DELETE_CONFIRM_INPUT, "value"),
|
|
147
|
+
],
|
|
148
|
+
Input(DM_DELETE_SET_SELECTOR, "value"),
|
|
149
|
+
State(ACTIVE_SESSION, "data"),
|
|
150
|
+
prevent_initial_call=True,
|
|
151
|
+
)
|
|
152
|
+
def open_confirm_section(selected_data_key, session_id: str):
|
|
153
|
+
"""
|
|
154
|
+
Controls the visibility of the confirmation section in the delete modal.
|
|
155
|
+
|
|
156
|
+
Shows the confirmation section if a dataset is selected and sets the confirmation
|
|
157
|
+
input value based on whether the selected dataset is master data.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
selected_data_key: Key of the selected dataset
|
|
161
|
+
session_id: ID of the active session
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
tuple: (is_open, confirm_input_value) where:
|
|
165
|
+
- is_open: Boolean indicating if the confirmation section should be visible
|
|
166
|
+
- confirm_input_value: Initial value for the confirmation input field
|
|
167
|
+
"""
|
|
168
|
+
if not selected_data_key:
|
|
169
|
+
return False, ""
|
|
170
|
+
sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
|
|
171
|
+
|
|
172
|
+
is_master_data = sm.get_data(selected_data_key).is_master_data()
|
|
173
|
+
if is_master_data:
|
|
174
|
+
return True, ""
|
|
175
|
+
else:
|
|
176
|
+
return False, "DELETE"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@callback(
|
|
180
|
+
[
|
|
181
|
+
Output(DM_LIST_UPDATER_STORE, "data", allow_duplicate=True),
|
|
182
|
+
Output(DATA_MAN_SUCCESS_ALERT, "children", allow_duplicate=True),
|
|
183
|
+
Output(DATA_MAN_SUCCESS_ALERT, "is_open", allow_duplicate=True),
|
|
184
|
+
Output(DATA_MAN_ERROR_ALERT, "children", allow_duplicate=True),
|
|
185
|
+
Output(DATA_MAN_ERROR_ALERT, "is_open", allow_duplicate=True),
|
|
186
|
+
Output(DM_DELETE_MODAL, "is_open", allow_duplicate=True),
|
|
187
|
+
],
|
|
188
|
+
[Input(DM_DELETE_SUBMIT_BUTTON, "n_clicks")],
|
|
189
|
+
[
|
|
190
|
+
State(DM_DELETE_SET_SELECTOR, "value"),
|
|
191
|
+
State(DM_DELETE_CONFIRM_INPUT, "value"),
|
|
192
|
+
State(ACTIVE_SESSION, "data"),
|
|
193
|
+
],
|
|
194
|
+
prevent_initial_call=True,
|
|
195
|
+
)
|
|
196
|
+
def delete_data_callback(n_clicks, selected_data_key, confirm_str, session_id: str):
|
|
197
|
+
"""
|
|
198
|
+
Deletes the selected dataset when the delete button is clicked.
|
|
199
|
+
|
|
200
|
+
Requires confirmation by typing "DELETE" in the confirmation input field.
|
|
201
|
+
Updates dropdown options across the application with the new dataset list,
|
|
202
|
+
displays success or error messages, and closes the modal upon completion.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
n_clicks: Number of times the submit button has been clicked
|
|
206
|
+
selected_data_key: Key of the dataset to delete
|
|
207
|
+
confirm_str: Confirmation string that must equal "DELETE" to proceed
|
|
208
|
+
session_id: ID of the active session
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Tuple containing updated dropdown options, alert messages, and modal state
|
|
212
|
+
"""
|
|
213
|
+
if not selected_data_key:
|
|
214
|
+
return (
|
|
215
|
+
no_update,
|
|
216
|
+
no_update,
|
|
217
|
+
no_update,
|
|
218
|
+
no_update,
|
|
219
|
+
"",
|
|
220
|
+
False,
|
|
221
|
+
"Select a dataset to delete!",
|
|
222
|
+
True,
|
|
223
|
+
False,
|
|
224
|
+
)
|
|
225
|
+
if confirm_str != "DELETE":
|
|
226
|
+
return no_update, no_update, no_update, no_update, no_update, no_update
|
|
227
|
+
sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
|
|
228
|
+
try:
|
|
229
|
+
sm.delete_data(selected_data_key)
|
|
230
|
+
return datetime.now(), "Dataset deleted successfully!", True, "", False, False
|
|
231
|
+
except AssertionError as e:
|
|
232
|
+
return no_update, "", False, f"Problem with deletion: {str(e)}", True, False
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@callback(
|
|
236
|
+
Output(DM_DELETE_MODAL, "is_open"),
|
|
237
|
+
[
|
|
238
|
+
Input(DM_DELETE_OPEN_BUTTON, "n_clicks"),
|
|
239
|
+
Input(DM_DELETE_CLOSE_BUTTON, "n_clicks"),
|
|
240
|
+
],
|
|
241
|
+
[dash.dependencies.State(DM_DELETE_MODAL, "is_open")],
|
|
242
|
+
)
|
|
243
|
+
def toggle_modal_delete(open_clicks, close_clicks, is_open):
|
|
244
|
+
"""
|
|
245
|
+
Toggles the visibility of the delete modal dialog.
|
|
246
|
+
|
|
247
|
+
Opens the modal when the open button is clicked and closes it when
|
|
248
|
+
the close button is clicked.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
open_clicks: Number of times the open button has been clicked
|
|
252
|
+
close_clicks: Number of times the close button has been clicked
|
|
253
|
+
is_open: Current state of the modal (open or closed)
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
bool: New state for the modal
|
|
257
|
+
"""
|
|
258
|
+
if open_clicks or close_clicks:
|
|
259
|
+
return not is_open
|
|
260
|
+
return is_open
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
import dash
|
|
5
|
+
import dash_bootstrap_components as dbc
|
|
6
|
+
from dash import html, dcc, callback, Output, Input, State, no_update, get_app
|
|
7
|
+
|
|
8
|
+
from algomancy_scenario import ScenarioManager
|
|
9
|
+
from ..componentids import (
|
|
10
|
+
DM_DERIVE_SET_SELECTOR,
|
|
11
|
+
DM_DERIVE_MODAL_SUBMIT_BTN,
|
|
12
|
+
DM_DERIVE_SET_NAME_INPUT,
|
|
13
|
+
DM_DERIVE_MODAL_CLOSE_BTN,
|
|
14
|
+
DM_DERIVE_MODAL,
|
|
15
|
+
DM_LIST_UPDATER_STORE,
|
|
16
|
+
DATA_MAN_SUCCESS_ALERT,
|
|
17
|
+
DATA_MAN_ERROR_ALERT,
|
|
18
|
+
DM_DERIVE_OPEN_BTN,
|
|
19
|
+
ACTIVE_SESSION,
|
|
20
|
+
)
|
|
21
|
+
from algomancy_gui.managergetters import get_scenario_manager
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
Modal component for deriving new datasets from existing ones.
|
|
25
|
+
|
|
26
|
+
This module provides a modal dialog that allows users to create derived datasets
|
|
27
|
+
by selecting an existing dataset and providing a name for the new derived dataset.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def data_management_derive_modal(sm: ScenarioManager, themed_styling):
|
|
32
|
+
"""
|
|
33
|
+
Creates a modal dialog component for deriving new datasets.
|
|
34
|
+
|
|
35
|
+
The modal contains a dropdown to select the source dataset and an input field
|
|
36
|
+
for naming the new derived dataset, along with submit and close buttons.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
sm: ScenarioManager instance used to populate the dataset dropdown
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
dbc.Modal: A Dash Bootstrap Components modal dialog
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
return dbc.Modal(
|
|
46
|
+
[
|
|
47
|
+
dbc.ModalHeader(dbc.ModalTitle("Derive"), close_button=False),
|
|
48
|
+
dbc.ModalBody(
|
|
49
|
+
html.Div(
|
|
50
|
+
[
|
|
51
|
+
dbc.Row(
|
|
52
|
+
[
|
|
53
|
+
dbc.Col(
|
|
54
|
+
html.Div(
|
|
55
|
+
html.P("Set to derive: "),
|
|
56
|
+
),
|
|
57
|
+
width=3,
|
|
58
|
+
className="justify-content-right",
|
|
59
|
+
),
|
|
60
|
+
dbc.Col(
|
|
61
|
+
[
|
|
62
|
+
dcc.Dropdown(
|
|
63
|
+
id=DM_DERIVE_SET_SELECTOR,
|
|
64
|
+
options=[
|
|
65
|
+
{"label": ds, "value": ds}
|
|
66
|
+
for ds in sm.get_data_keys()
|
|
67
|
+
],
|
|
68
|
+
value=sm.get_data_keys()[0]
|
|
69
|
+
if sm.get_data_keys()
|
|
70
|
+
else "",
|
|
71
|
+
placeholder="Select dataset",
|
|
72
|
+
),
|
|
73
|
+
],
|
|
74
|
+
width=9,
|
|
75
|
+
),
|
|
76
|
+
],
|
|
77
|
+
className="mb-4",
|
|
78
|
+
),
|
|
79
|
+
dbc.Row(
|
|
80
|
+
[
|
|
81
|
+
dbc.Col(html.Div(html.P("Name: ")), width=3),
|
|
82
|
+
dbc.Col(
|
|
83
|
+
dbc.Input(
|
|
84
|
+
id=DM_DERIVE_SET_NAME_INPUT,
|
|
85
|
+
placeholder="Name of new dataset",
|
|
86
|
+
),
|
|
87
|
+
width=9,
|
|
88
|
+
),
|
|
89
|
+
]
|
|
90
|
+
),
|
|
91
|
+
]
|
|
92
|
+
)
|
|
93
|
+
),
|
|
94
|
+
dbc.ModalFooter(
|
|
95
|
+
[
|
|
96
|
+
dbc.Button(
|
|
97
|
+
"Derive",
|
|
98
|
+
id=DM_DERIVE_MODAL_SUBMIT_BTN,
|
|
99
|
+
class_name="dm-derive-modal-confirm-btn",
|
|
100
|
+
),
|
|
101
|
+
dbc.Button(
|
|
102
|
+
"Close",
|
|
103
|
+
id=DM_DERIVE_MODAL_CLOSE_BTN,
|
|
104
|
+
class_name="dm-derive-modal-cancel-btn ms-auto",
|
|
105
|
+
n_clicks=0,
|
|
106
|
+
),
|
|
107
|
+
]
|
|
108
|
+
),
|
|
109
|
+
],
|
|
110
|
+
id=DM_DERIVE_MODAL,
|
|
111
|
+
is_open=False,
|
|
112
|
+
centered=True,
|
|
113
|
+
class_name="themed-modal",
|
|
114
|
+
style=themed_styling,
|
|
115
|
+
keyboard=False,
|
|
116
|
+
backdrop="static",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _sanitize(name: str) -> str:
|
|
121
|
+
# keep ascii-safe filename characters only
|
|
122
|
+
return re.sub(r"[^A-Za-z0-9_.-]", "_", name)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@callback(
|
|
126
|
+
[
|
|
127
|
+
Output(DM_LIST_UPDATER_STORE, "data", allow_duplicate=True),
|
|
128
|
+
Output(DATA_MAN_SUCCESS_ALERT, "children", allow_duplicate=True),
|
|
129
|
+
Output(DATA_MAN_SUCCESS_ALERT, "is_open", allow_duplicate=True),
|
|
130
|
+
Output(DATA_MAN_ERROR_ALERT, "children", allow_duplicate=True),
|
|
131
|
+
Output(DATA_MAN_ERROR_ALERT, "is_open", allow_duplicate=True),
|
|
132
|
+
Output(DM_DERIVE_MODAL, "is_open", allow_duplicate=True),
|
|
133
|
+
],
|
|
134
|
+
[Input(DM_DERIVE_MODAL_SUBMIT_BTN, "n_clicks")],
|
|
135
|
+
[
|
|
136
|
+
State(DM_DERIVE_SET_SELECTOR, "value"),
|
|
137
|
+
State(DM_DERIVE_SET_NAME_INPUT, "value"),
|
|
138
|
+
State(ACTIVE_SESSION, "data"),
|
|
139
|
+
],
|
|
140
|
+
prevent_initial_call=True,
|
|
141
|
+
)
|
|
142
|
+
def derive_data_callback(n_clicks, selected_data_key, derived_name, session_id: str):
|
|
143
|
+
"""
|
|
144
|
+
Creates a derived dataset from an existing one when the derive button is clicked.
|
|
145
|
+
|
|
146
|
+
Updates dropdown options across the application with the new dataset list,
|
|
147
|
+
displays success or error messages, and closes the modal upon completion.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
n_clicks: Number of times the submit button has been clicked
|
|
151
|
+
selected_data_key: Key of the dataset to derive from
|
|
152
|
+
derived_name: Name for the new derived dataset
|
|
153
|
+
session_id: ID of the active session
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Tuple containing updated dropdown options, alert messages, and modal state
|
|
157
|
+
"""
|
|
158
|
+
if not selected_data_key or not derived_name:
|
|
159
|
+
return no_update, "", False, "Choose a dataset and enter a name!", True, False
|
|
160
|
+
sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
|
|
161
|
+
try:
|
|
162
|
+
sanitized_name = _sanitize(derived_name)
|
|
163
|
+
sm.derive_data(selected_data_key, sanitized_name)
|
|
164
|
+
return (
|
|
165
|
+
datetime.now(),
|
|
166
|
+
"Successfully created derived dataset!",
|
|
167
|
+
True,
|
|
168
|
+
"",
|
|
169
|
+
False,
|
|
170
|
+
False,
|
|
171
|
+
)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
return no_update, "", False, f"Problem with deriving: {str(e)}", True, False
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@callback(
|
|
177
|
+
Output(DM_DERIVE_MODAL, "is_open"),
|
|
178
|
+
[
|
|
179
|
+
Input(DM_DERIVE_OPEN_BTN, "n_clicks"),
|
|
180
|
+
Input(DM_DERIVE_MODAL_CLOSE_BTN, "n_clicks"),
|
|
181
|
+
],
|
|
182
|
+
[dash.dependencies.State(DM_DERIVE_MODAL, "is_open")],
|
|
183
|
+
)
|
|
184
|
+
def toggle_modal_derive(open_clicks, close_clicks, is_open):
|
|
185
|
+
"""
|
|
186
|
+
Toggles the visibility of the derive modal dialog.
|
|
187
|
+
|
|
188
|
+
Opens the modal when the open button is clicked and closes it when
|
|
189
|
+
the close button is clicked.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
open_clicks: Number of times the open button has been clicked
|
|
193
|
+
close_clicks: Number of times the close button has been clicked
|
|
194
|
+
is_open: Current state of the modal (open or closed)
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
bool: New state for the modal
|
|
198
|
+
"""
|
|
199
|
+
if open_clicks or close_clicks:
|
|
200
|
+
return not is_open
|
|
201
|
+
return is_open
|