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.
Files changed (46) hide show
  1. algomancy_gui/__init__.py +0 -0
  2. algomancy_gui/admin_page/__init__.py +1 -0
  3. algomancy_gui/admin_page/admin.py +362 -0
  4. algomancy_gui/admin_page/sessions.py +57 -0
  5. algomancy_gui/appconfiguration.py +291 -0
  6. algomancy_gui/compare_page/__init__.py +1 -0
  7. algomancy_gui/compare_page/compare.py +360 -0
  8. algomancy_gui/compare_page/kpicard.py +236 -0
  9. algomancy_gui/compare_page/scenarioselector.py +99 -0
  10. algomancy_gui/componentids.py +177 -0
  11. algomancy_gui/contentregistry.py +167 -0
  12. algomancy_gui/cqmloader.py +58 -0
  13. algomancy_gui/data_page/__init__.py +1 -0
  14. algomancy_gui/data_page/data.py +77 -0
  15. algomancy_gui/data_page/datamanagementdeletemodal.py +260 -0
  16. algomancy_gui/data_page/datamanagementderivemodal.py +201 -0
  17. algomancy_gui/data_page/datamanagementdownloadmodal.py +193 -0
  18. algomancy_gui/data_page/datamanagementimportmodal.py +438 -0
  19. algomancy_gui/data_page/datamanagementsavemodal.py +191 -0
  20. algomancy_gui/data_page/datamanagementtopbar.py +123 -0
  21. algomancy_gui/data_page/datamanagementuploadmodal.py +366 -0
  22. algomancy_gui/data_page/dialogcallbacks.py +51 -0
  23. algomancy_gui/data_page/filenamematcher.py +109 -0
  24. algomancy_gui/defaultloader.py +36 -0
  25. algomancy_gui/gui_launcher.py +183 -0
  26. algomancy_gui/home_page/__init__.py +1 -0
  27. algomancy_gui/home_page/home.py +16 -0
  28. algomancy_gui/layout.py +199 -0
  29. algomancy_gui/layouthelpers.py +30 -0
  30. algomancy_gui/managergetters.py +28 -0
  31. algomancy_gui/overview_page/__init__.py +1 -0
  32. algomancy_gui/overview_page/overview.py +20 -0
  33. algomancy_gui/py.typed +0 -0
  34. algomancy_gui/scenario_page/__init__.py +0 -0
  35. algomancy_gui/scenario_page/delete_confirmation.py +29 -0
  36. algomancy_gui/scenario_page/new_scenario_creator.py +104 -0
  37. algomancy_gui/scenario_page/new_scenario_parameters_window.py +154 -0
  38. algomancy_gui/scenario_page/scenario_badge.py +36 -0
  39. algomancy_gui/scenario_page/scenario_cards.py +119 -0
  40. algomancy_gui/scenario_page/scenarios.py +596 -0
  41. algomancy_gui/sessionmanager.py +168 -0
  42. algomancy_gui/settingsmanager.py +43 -0
  43. algomancy_gui/stylingconfigurator.py +740 -0
  44. algomancy_gui-0.3.16.dist-info/METADATA +71 -0
  45. algomancy_gui-0.3.16.dist-info/RECORD +46 -0
  46. algomancy_gui-0.3.16.dist-info/WHEEL +4 -0
@@ -0,0 +1,191 @@
1
+ import dash
2
+ import dash_bootstrap_components as dbc
3
+ from dash import dcc, callback, Output, Input, no_update, State, get_app
4
+
5
+ from algomancy_scenario import ScenarioManager
6
+
7
+ from ..componentids import (
8
+ DM_SAVE_MODAL,
9
+ DM_SAVE_MODAL_CLOSE_BTN,
10
+ DM_SAVE_SET_SELECTOR,
11
+ DM_SAVE_SUBMIT_BUTTON,
12
+ DM_SAVE_OPEN_BUTTON,
13
+ DATA_MAN_SUCCESS_ALERT,
14
+ DATA_MAN_ERROR_ALERT,
15
+ ACTIVE_SESSION,
16
+ )
17
+ from algomancy_gui.managergetters import get_scenario_manager
18
+
19
+ """
20
+ Modal component for saving derived datasets as master data.
21
+
22
+ This module provides a modal dialog that allows users to select and save
23
+ derived datasets as master data, which persists the data to disk.
24
+ """
25
+
26
+
27
+ def create_derived_data_selector(sm: ScenarioManager):
28
+ """
29
+ Creates a dropdown component for selecting derived datasets.
30
+
31
+ Filters the available datasets to show only derived (non-master) datasets.
32
+
33
+ Args:
34
+ sm: ScenarioManager instance used to retrieve and filter datasets
35
+
36
+ Returns:
37
+ dcc.Dropdown: A Dash dropdown component populated with derived datasets
38
+ """
39
+ derived_options = [
40
+ {"label": ds, "value": ds}
41
+ for ds in sm.get_data_keys()
42
+ if not sm.get_data(ds).is_master_data()
43
+ ]
44
+
45
+ return dcc.Dropdown(
46
+ id=DM_SAVE_SET_SELECTOR,
47
+ value="",
48
+ options=derived_options,
49
+ placeholder="Select dataset",
50
+ )
51
+
52
+
53
+ def data_management_save_modal(sm: ScenarioManager, themed_styling):
54
+ """
55
+ Creates a modal dialog component for saving derived datasets as master data.
56
+
57
+ The modal contains a dropdown to select the derived dataset to save and
58
+ buttons to save or cancel the operation.
59
+
60
+ Args:
61
+ sm: ScenarioManager instance used to populate the dataset dropdown
62
+
63
+ Returns:
64
+ dbc.Modal: A Dash Bootstrap Components modal dialog
65
+ """
66
+ return dbc.Modal(
67
+ [
68
+ dbc.ModalHeader(dbc.ModalTitle("Save"), close_button=False),
69
+ dbc.ModalBody(
70
+ ["Select derived data to save.", create_derived_data_selector(sm)]
71
+ ),
72
+ dbc.ModalFooter(
73
+ [
74
+ dbc.Button(
75
+ "Save",
76
+ id=DM_SAVE_SUBMIT_BUTTON,
77
+ class_name="dm-save-modal-confirm-btn",
78
+ ),
79
+ dbc.Button(
80
+ "Close",
81
+ id=DM_SAVE_MODAL_CLOSE_BTN,
82
+ class_name="dm-save-modal-cancel-btn ms-auto",
83
+ ),
84
+ ]
85
+ ),
86
+ ],
87
+ id=DM_SAVE_MODAL,
88
+ is_open=False,
89
+ centered=True,
90
+ class_name="themed-modal",
91
+ style=themed_styling,
92
+ keyboard=False,
93
+ backdrop="static",
94
+ )
95
+
96
+
97
+ @callback(
98
+ Output(DM_SAVE_MODAL, "is_open"),
99
+ [
100
+ Input(DM_SAVE_OPEN_BUTTON, "n_clicks"),
101
+ Input(DM_SAVE_MODAL_CLOSE_BTN, "n_clicks"),
102
+ ],
103
+ [dash.dependencies.State(DM_SAVE_MODAL, "is_open")],
104
+ )
105
+ def toggle_modal_save(open_clicks, close_clicks, is_open):
106
+ """
107
+ Toggles the visibility of the save modal dialog.
108
+
109
+ Opens the modal when the open button is clicked and closes it when
110
+ the close button is clicked.
111
+
112
+ Args:
113
+ open_clicks: Number of times the open button has been clicked
114
+ close_clicks: Number of times the close button has been clicked
115
+ is_open: Current state of the modal (open or closed)
116
+
117
+ Returns:
118
+ bool: New state for the modal
119
+ """
120
+ if open_clicks or close_clicks:
121
+ return not is_open
122
+ return is_open
123
+
124
+
125
+ @callback(
126
+ Output(DM_SAVE_SET_SELECTOR, "value"),
127
+ Input(DM_SAVE_MODAL, "is_open"),
128
+ prevent_initial_call=True,
129
+ )
130
+ def reset_save_selection_on_close(modal_is_open: bool):
131
+ """
132
+ Resets the save dataset selector when the save modal is closed.
133
+
134
+ Args:
135
+ modal_is_open: Boolean indicating if the modal is open
136
+
137
+ Returns:
138
+ None if the modal is closed, no_update otherwise
139
+ """
140
+ if not modal_is_open:
141
+ return None
142
+ return no_update
143
+
144
+
145
+ @callback(
146
+ Output(DM_SAVE_MODAL, "is_open", allow_duplicate=True),
147
+ Output(DATA_MAN_SUCCESS_ALERT, "children", allow_duplicate=True),
148
+ Output(DATA_MAN_SUCCESS_ALERT, "is_open", allow_duplicate=True),
149
+ Output(DATA_MAN_ERROR_ALERT, "children", allow_duplicate=True),
150
+ Output(DATA_MAN_ERROR_ALERT, "is_open", allow_duplicate=True),
151
+ Input(DM_SAVE_SUBMIT_BUTTON, "n_clicks"),
152
+ State(DM_SAVE_SET_SELECTOR, "value"),
153
+ State(ACTIVE_SESSION, "data"),
154
+ prevent_initial_call=True,
155
+ )
156
+ def save_derived_data(
157
+ n_clicks,
158
+ set_name: str,
159
+ session_id: str,
160
+ ):
161
+ """
162
+ Saves a derived dataset as master data when the save button is clicked.
163
+
164
+ Stores the dataset files to disk and updates the dataset's status to master data.
165
+ Displays success or error messages and closes the modal upon completion.
166
+
167
+ Args:
168
+ n_clicks: Number of times the submit button has been clicked
169
+ set_name: Name of the dataset to save
170
+ session_id: ID of the active session
171
+
172
+ Returns:
173
+ Tuple containing modal state and alert messages
174
+ """
175
+
176
+ sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
177
+
178
+ try:
179
+ data = sm.get_data(set_name)
180
+ data.set_to_master_data()
181
+
182
+ if sm.save_type == "json":
183
+ sm.store_data_as_json(set_name)
184
+ else:
185
+ raise ValueError(f"Unknown save type: {sm.save_type}")
186
+
187
+ return False, "Files saved successfully", True, "", False
188
+ except Exception as e:
189
+ sm.logger.error(f"Problem with saving: {str(e)}")
190
+ sm.logger.log_traceback(e)
191
+ return False, "", False, f"Problem with saving: {str(e)}", True
@@ -0,0 +1,123 @@
1
+ from dash import html, dcc, get_app
2
+ import dash_bootstrap_components as dbc
3
+
4
+ from algomancy_scenario import ScenarioManager
5
+
6
+ from .datamanagementdeletemodal import data_management_delete_modal
7
+ from .datamanagementderivemodal import data_management_derive_modal
8
+ from .datamanagementdownloadmodal import data_management_download_modal
9
+ from .datamanagementimportmodal import data_management_import_modal
10
+ from .datamanagementsavemodal import data_management_save_modal
11
+ from .datamanagementuploadmodal import data_management_upload_modal
12
+ from ..componentids import (
13
+ DATA_SELECTOR_DROPDOWN,
14
+ DATA_MAN_SUCCESS_ALERT,
15
+ DATA_MAN_ERROR_ALERT,
16
+ DM_DELETE_OPEN_BUTTON,
17
+ DM_DERIVE_OPEN_BTN,
18
+ DM_SAVE_OPEN_BUTTON,
19
+ DM_IMPORT_OPEN_BUTTON,
20
+ DM_UPLOAD_OPEN_BUTTON,
21
+ DM_DOWNLOAD_OPEN_BUTTON,
22
+ DM_LIST_UPDATER_STORE,
23
+ )
24
+
25
+
26
+ def top_bar(sm: ScenarioManager):
27
+ toolbar = create_data_management_toolbar(sm)
28
+ active_dataset_selector = create_datasource_selector(sm)
29
+
30
+ return html.Div(
31
+ [
32
+ dbc.Row(
33
+ [
34
+ dcc.Store(DM_LIST_UPDATER_STORE, data=""),
35
+ dbc.Col(active_dataset_selector, width=8),
36
+ dbc.Col(toolbar, width=4),
37
+ ]
38
+ ),
39
+ dbc.Alert(
40
+ id=DATA_MAN_SUCCESS_ALERT,
41
+ color="success",
42
+ is_open=False,
43
+ dismissable=True,
44
+ duration=4000,
45
+ class_name="mt-2",
46
+ ),
47
+ dbc.Alert(
48
+ id=DATA_MAN_ERROR_ALERT,
49
+ color="danger",
50
+ is_open=False,
51
+ dismissable=True,
52
+ duration=4000,
53
+ class_name="mt-2",
54
+ ),
55
+ ]
56
+ )
57
+
58
+
59
+ def create_data_management_toolbar(sm: ScenarioManager):
60
+ themed_styling = get_app().server.styling_config.initiate_theme_colors()
61
+
62
+ return html.Div(
63
+ [
64
+ dbc.ButtonGroup(
65
+ [
66
+ dbc.Button(
67
+ "Derive", id=DM_DERIVE_OPEN_BTN, className="me-2 dm-derive-btn"
68
+ ),
69
+ dbc.Button(
70
+ "Delete",
71
+ id=DM_DELETE_OPEN_BUTTON,
72
+ className="me-2 dm-delete-btn",
73
+ ),
74
+ dbc.Button(
75
+ "Save",
76
+ id=DM_SAVE_OPEN_BUTTON,
77
+ disabled=not sm.has_persistent_state,
78
+ className="me-2 dm-save-btn",
79
+ ),
80
+ dbc.Button(
81
+ "Import",
82
+ id=DM_IMPORT_OPEN_BUTTON, # adjust callback
83
+ className="me-2 dm-import-btn",
84
+ ),
85
+ dbc.Button(
86
+ "Upload",
87
+ id=DM_UPLOAD_OPEN_BUTTON, # adjust callback
88
+ className="me-2 dm-upload-btn",
89
+ ),
90
+ dbc.Button(
91
+ "Download",
92
+ id=DM_DOWNLOAD_OPEN_BUTTON, # adjust callback
93
+ className="me-2 dm-download-btn",
94
+ ),
95
+ ],
96
+ className="d-flex justify-content-end",
97
+ ),
98
+ data_management_derive_modal(sm, themed_styling),
99
+ data_management_delete_modal(sm, themed_styling),
100
+ data_management_save_modal(sm, themed_styling),
101
+ data_management_import_modal(sm, themed_styling),
102
+ data_management_upload_modal(sm, themed_styling),
103
+ data_management_download_modal(sm, themed_styling),
104
+ ]
105
+ )
106
+
107
+
108
+ def create_datasource_selector(sm: ScenarioManager) -> html.Div:
109
+ dropdown = dcc.Dropdown(
110
+ id=DATA_SELECTOR_DROPDOWN,
111
+ options=[{"label": ds, "value": ds} for ds in sm.get_data_keys()],
112
+ placeholder="Select dataset",
113
+ )
114
+
115
+ return html.Div(
116
+ dbc.Row(
117
+ [
118
+ dbc.Col(html.P("Active dataset: "), width=2),
119
+ dbc.Col(dropdown, width=10),
120
+ ]
121
+ ),
122
+ className="mb-4",
123
+ )
@@ -0,0 +1,366 @@
1
+ import base64
2
+ from datetime import datetime
3
+
4
+ import dash_bootstrap_components as dbc
5
+ from dash import html, dcc, get_app, callback, Output, Input, State, no_update
6
+ from dash.exceptions import PreventUpdate
7
+
8
+ from algomancy_scenario import ScenarioManager
9
+ from ..componentids import (
10
+ DM_UPLOAD_MODAL_CLOSE_BTN,
11
+ DM_UPLOAD_MODAL_FILEVIEWER_CARD,
12
+ DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE,
13
+ DM_UPLOAD_OPEN_BUTTON,
14
+ DM_UPLOAD_SUBMIT_BUTTON,
15
+ DM_UPLOAD_UPLOADER,
16
+ DM_UPLOAD_MODAL,
17
+ ACTIVE_SESSION,
18
+ )
19
+ from ..componentids import (
20
+ DM_UPLOAD_SUCCESS_ALERT,
21
+ DM_LIST_UPDATER_STORE,
22
+ )
23
+ from ..cqmloader import cqm_loader
24
+ from ..defaultloader import default_loader
25
+ from algomancy_gui.managergetters import get_scenario_manager
26
+ from ..settingsmanager import SettingsManager
27
+
28
+
29
+ """
30
+ Modal component for loading data files into the application.
31
+
32
+ This module provides a modal dialog that allows users to upload CSV files,
33
+ view file mapping information, and create new datasets from the uploaded files.
34
+ """
35
+
36
+
37
+ def data_management_upload_modal(sm: ScenarioManager, themed_styling):
38
+ """
39
+ Creates a modal dialog component for loading data files.
40
+
41
+ The modal contains a file upload area, a collapsible section for displaying
42
+ file mapping information, an input field for naming the new dataset, and
43
+ an alert area for displaying messages.
44
+
45
+ Returns:
46
+ dbc.Modal: A Dash Bootstrap Components modal dialog
47
+ """
48
+ settings: SettingsManager = get_app().server.settings
49
+
50
+ if settings.use_cqm_loader:
51
+ spinner = cqm_loader(
52
+ "Importing data..."
53
+ ) # requires letter-c.svg, letter-q.svg and letter-m.svg
54
+ else:
55
+ spinner = default_loader("Importing data...")
56
+
57
+ return dbc.Modal(
58
+ [
59
+ dbc.ModalHeader(dbc.ModalTitle("Upload Cases"), close_button=False),
60
+ dbc.ModalBody(
61
+ dcc.Loading(
62
+ [
63
+ dbc.Label(
64
+ "The uploaded file will be uploaded as a new dataset."
65
+ "The name of the dataset will be the name of the uploaded file."
66
+ f"The file must be in {sm.save_type} format."
67
+ ),
68
+ dcc.Upload(
69
+ id=DM_UPLOAD_UPLOADER,
70
+ children=html.Div(
71
+ ["Drag and Drop or ", html.A("Select Files")]
72
+ ),
73
+ style={
74
+ "width": "100%",
75
+ "height": "60px",
76
+ "lineHeight": "60px",
77
+ "borderWidth": "1px",
78
+ "borderStyle": "dashed",
79
+ "borderRadius": "4px",
80
+ "textAlign": "center",
81
+ },
82
+ multiple=True,
83
+ ),
84
+ # dcc.Store(DM_UPLOAD_DATA_STORE, data=""),
85
+ dbc.Collapse(
86
+ children=[
87
+ dbc.Card(
88
+ dbc.CardBody(id=DM_UPLOAD_MODAL_FILEVIEWER_CARD),
89
+ className="uploaded-files-card",
90
+ ),
91
+ ],
92
+ id=DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE,
93
+ is_open=False,
94
+ class_name="mt-2 mb-2",
95
+ ),
96
+ dbc.Alert(
97
+ children="Upload successful! Close the modal to continue.",
98
+ id=DM_UPLOAD_SUCCESS_ALERT,
99
+ color="success",
100
+ is_open=False,
101
+ ),
102
+ dcc.Store("dm-upload-dummy-store", data=""),
103
+ ],
104
+ overlay_style={
105
+ "visibility": "visible",
106
+ "opacity": 0.5,
107
+ "backgroundColor": "white",
108
+ },
109
+ custom_spinner=spinner,
110
+ delay_hide=50,
111
+ delay_show=50,
112
+ )
113
+ ),
114
+ dbc.ModalFooter(
115
+ [
116
+ dbc.Button(
117
+ "Upload",
118
+ id=DM_UPLOAD_SUBMIT_BUTTON,
119
+ class_name="dm-upload-modal-confirm-btn",
120
+ ),
121
+ dbc.Button(
122
+ "Close",
123
+ id=DM_UPLOAD_MODAL_CLOSE_BTN,
124
+ class_name="dm-upload-modal-cancel-btn ms-auto",
125
+ ),
126
+ ]
127
+ ),
128
+ ],
129
+ id=DM_UPLOAD_MODAL,
130
+ is_open=False,
131
+ centered=True,
132
+ class_name="themed-modal",
133
+ style=themed_styling,
134
+ keyboard=False,
135
+ backdrop="static",
136
+ )
137
+
138
+
139
+ @callback(
140
+ Output(DM_UPLOAD_MODAL, "is_open", allow_duplicate=True),
141
+ Input(DM_UPLOAD_OPEN_BUTTON, "n_clicks"),
142
+ Input(DM_UPLOAD_MODAL_CLOSE_BTN, "n_clicks"),
143
+ State(DM_UPLOAD_MODAL, "is_open"),
144
+ prevent_initial_call=True,
145
+ )
146
+ def open_close_modal(n_open, n_close, is_open):
147
+ """
148
+ Callback for opening and closing the dialog modal
149
+ """
150
+ if n_open or n_close:
151
+ return not is_open
152
+ return is_open
153
+
154
+
155
+ @callback(
156
+ [
157
+ Output(DM_UPLOAD_MODAL_FILEVIEWER_CARD, "children", allow_duplicate=True),
158
+ Output(DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE, "is_open", allow_duplicate=True),
159
+ Output(DM_UPLOAD_UPLOADER, "disabled", allow_duplicate=True),
160
+ Output(DM_UPLOAD_UPLOADER, "filename", allow_duplicate=True),
161
+ Output(DM_UPLOAD_UPLOADER, "contents", allow_duplicate=True),
162
+ Output(DM_UPLOAD_SUCCESS_ALERT, "is_open", allow_duplicate=True),
163
+ ],
164
+ Input(DM_UPLOAD_MODAL, "is_open"),
165
+ prevent_initial_call=True,
166
+ )
167
+ def reset_on_close(is_open):
168
+ if not is_open:
169
+ return [], False, False, None, None, False
170
+ return no_update, no_update, no_update, no_update, no_update, no_update
171
+
172
+
173
+ def _render_uploaded_files(filenames, wrong_filenames) -> html.Div:
174
+ """
175
+ Helper function to create a rendering of uploaded files
176
+ """
177
+ file_name_width = 8
178
+ status_width = 12 - file_name_width
179
+
180
+ header = html.Div(
181
+ dbc.Row(
182
+ [
183
+ dbc.Col(
184
+ [
185
+ html.Strong("File name"),
186
+ ],
187
+ width=file_name_width,
188
+ ),
189
+ dbc.Col(
190
+ [
191
+ html.Strong("Status"),
192
+ ],
193
+ width=status_width,
194
+ ),
195
+ ]
196
+ ),
197
+ className="uploaded-files-header",
198
+ )
199
+
200
+ body = [
201
+ html.Div(
202
+ dbc.Row(
203
+ [
204
+ dbc.Col([f"{filename}"], width=file_name_width),
205
+ dbc.Col(
206
+ [
207
+ dbc.Spinner(
208
+ html.Div(
209
+ id={"type": "dm-upload-status", "index": filename}
210
+ )
211
+ )
212
+ ],
213
+ width=status_width,
214
+ ),
215
+ ]
216
+ ),
217
+ className="uploaded-file-good",
218
+ )
219
+ for filename in filenames
220
+ ] + [
221
+ html.Div(
222
+ dbc.Row(
223
+ [
224
+ dbc.Col(
225
+ [f"{filename}"],
226
+ width=file_name_width,
227
+ ),
228
+ dbc.Col(
229
+ [
230
+ dbc.Spinner(
231
+ html.Div(
232
+ id={"type": "dm-upload-status", "index": filename}
233
+ )
234
+ )
235
+ ],
236
+ width=status_width,
237
+ ),
238
+ ]
239
+ ),
240
+ className="uploaded-file-bad",
241
+ )
242
+ for filename in wrong_filenames
243
+ ]
244
+
245
+ table_div = html.Div([header, *body], className="uploaded-files-table")
246
+
247
+ return table_div
248
+
249
+
250
+ def check_files(filenames, session_id: str):
251
+ sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
252
+ allowed_type = sm.save_type
253
+
254
+ filenames_with_wrong_type = [
255
+ file_name
256
+ for file_name in filenames
257
+ if not file_name.lower().endswith(allowed_type.lower())
258
+ ]
259
+
260
+ filenames_with_allowed_type = [
261
+ file_name
262
+ for file_name in filenames
263
+ if file_name.lower().endswith(allowed_type.lower())
264
+ ]
265
+
266
+ filenames_already_present = [
267
+ file_name
268
+ for file_name in filenames_with_allowed_type
269
+ if file_name.split(".")[0] in sm.get_data_keys()
270
+ ]
271
+
272
+ filenames_not_present = [
273
+ file_name
274
+ for file_name in filenames_with_allowed_type
275
+ if file_name.split(".")[0] not in sm.get_data_keys()
276
+ ]
277
+
278
+ return filenames_not_present, filenames_already_present + filenames_with_wrong_type
279
+
280
+
281
+ @callback(
282
+ [
283
+ Output(DM_UPLOAD_MODAL_FILEVIEWER_CARD, "children", allow_duplicate=True),
284
+ Output(DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE, "is_open", allow_duplicate=True),
285
+ Output(DM_UPLOAD_UPLOADER, "disabled", allow_duplicate=True),
286
+ ],
287
+ Input(DM_UPLOAD_UPLOADER, "filename"),
288
+ State(ACTIVE_SESSION, "data"),
289
+ prevent_initial_call=True,
290
+ )
291
+ def update_file_viewer(filename, session_id: str):
292
+ """
293
+ Callback to respond to file upload events
294
+ """
295
+ if filename is None:
296
+ return [], False, False
297
+
298
+ # Allow for possible list/file array
299
+ if isinstance(filename, list):
300
+ filenames = filename
301
+ else:
302
+ filenames = [filename]
303
+
304
+ good_files, bad_files = check_files(filenames, session_id)
305
+
306
+ return html.Div([_render_uploaded_files(good_files, bad_files)]), True, True
307
+
308
+
309
+ @callback(
310
+ [
311
+ Output(DM_LIST_UPDATER_STORE, "data", allow_duplicate=True),
312
+ Output(DM_UPLOAD_SUBMIT_BUTTON, "disabled", allow_duplicate=True),
313
+ Output(DM_UPLOAD_SUCCESS_ALERT, "is_open", allow_duplicate=True),
314
+ Output("dm-upload-dummy-store", "data", allow_duplicate=True),
315
+ ],
316
+ Input(DM_UPLOAD_SUBMIT_BUTTON, "n_clicks"),
317
+ State(DM_UPLOAD_UPLOADER, "contents"),
318
+ State(DM_UPLOAD_UPLOADER, "filename"),
319
+ State(ACTIVE_SESSION, "data"),
320
+ prevent_initial_call=True,
321
+ )
322
+ def process_uploaded_files(n_clicks, contents, filenames, session_id: str):
323
+ """
324
+ Process uploaded files when the submit button is clicked.
325
+
326
+ Returns:
327
+ bool: Whether to close the modal
328
+ """
329
+ if n_clicks is None or not n_clicks or contents is None or filenames is None:
330
+ raise PreventUpdate
331
+
332
+ sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
333
+
334
+ # Make sure we're working with lists
335
+ if not isinstance(filenames, list):
336
+ filenames = [filenames]
337
+ contents = [contents]
338
+
339
+ # Filter good files
340
+ good_files, _ = check_files(filenames, session_id)
341
+ files_with_content = zip(filenames, contents)
342
+ good_files_with_content = [
343
+ (filename, content)
344
+ for filename, content in files_with_content
345
+ if filename in good_files
346
+ ]
347
+
348
+ for filename, content in good_files_with_content:
349
+ try:
350
+ # Process the file content
351
+ content_type, content_string = content.split(",", 1)
352
+ decoded = base64.b64decode(content_string)
353
+ json_string = decoded.decode("utf-8")
354
+
355
+ # Add data source to scenario manager
356
+ sm.add_datasource_from_json(json_string)
357
+
358
+ # Log
359
+ sm.logger.success(f"Successfully uploaded {filename} to data manager.")
360
+
361
+ except Exception as e:
362
+ sm.logger.error(f"Error processing uploaded file {filename}: {e}")
363
+ sm.logger.log_traceback(e)
364
+
365
+ # Close the modal
366
+ return datetime.now(), [True], True, ""