Skip to content

Commit 660f7a6

Browse files
sdc50rdgsllw8
andauthored
Fix submit stage file browser (#74)
* Initial fix for environment variables file browser * fix submit stage file browser * --Multifile write updated to skip existing files * style changes to the file browser and file selector * --modified so working directory will be a name/timestamp directory within the user-defined directory * --finished debugging current iteration of user execution directory specification * update tooltip text --------- Co-authored-by: rdgsllw8 <[email protected]>
1 parent d398e28 commit 660f7a6

File tree

5 files changed

+159
-79
lines changed

5 files changed

+159
-79
lines changed

uit/gui_tools/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
)
1717
from .file_browser import (
1818
get_js_loading_code,
19+
create_file_browser,
1920
FileBrowser,
2021
FileManager,
2122
FileManagerHPC,

uit/gui_tools/file_browser.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,12 @@ class FileBrowser(Viewer):
183183

184184
path = param.ClassSelector(class_=Path, precedence=-1)
185185
path_text = param.String(label="", precedence=0.3)
186-
home = param.Action(lambda self: self.go_home(), label="🏠", precedence=0.1)
187-
up = param.Action(lambda self: self.move_up(), label="⬆️", precedence=0.2)
186+
home = param.Action(lambda self: self.go_home(), label="🏠", doc="Home", precedence=0.1)
187+
up = param.Action(lambda self: self.move_up(), label="⬆️", doc="Move Up", precedence=0.2)
188188
# refresh_controll triggers rather than calling validate directly to allow an async override of the validate method
189-
refresh_control = param.Action(lambda self: self.param.trigger("refresh_control"), label="🔄", precedence=0.25)
189+
refresh_control = param.Action(
190+
lambda self: self.param.trigger("refresh_control"), label="🔄", doc="Refresh", precedence=0.25
191+
)
190192
callback = param.Action(lambda x: None, precedence=-1)
191193
file_listing = param.ListSelector(default=[], label="Single click to select a file or directory:", precedence=0.5)
192194
patterns = param.List(precedence=-1, default=["*"])
@@ -591,7 +593,7 @@ async def exists(self):
591593

592594
class HpcFileBrowser(FileBrowser):
593595
path = param.ClassSelector(class_=HpcPath)
594-
workdir = param.Action(lambda self: self.go_to_workdir(), label="⚙️", precedence=0.15)
596+
workdir = param.Action(lambda self: self.go_to_workdir(), label="⚙️", doc="Workdir", precedence=0.15)
595597
uit_client = param.ClassSelector(class_=Client)
596598

597599
def __init__(self, uit_client, **params):
@@ -683,10 +685,17 @@ async def make_options(self):
683685
self.do_callback()
684686

685687

688+
def create_file_browser(uit_client, **kwargs):
689+
if isinstance(uit_client, AsyncClient):
690+
return AsyncHpcFileBrowser(uit_client, **kwargs)
691+
if isinstance(uit_client, Client):
692+
return HpcFileBrowser(uit_client, **kwargs)
693+
694+
686695
class FileSelector(Viewer):
687696
file_path = param.String(default="")
688697
show_browser = param.Boolean(default=False)
689-
browse_toggle = param.Action(lambda self: self.toggle(), label="Browse")
698+
browse_toggle = param.Action(lambda self: self.toggle(), label="📂", doc="Open file browser.")
690699
file_browser = param.ClassSelector(class_=FileBrowser)
691700
title = param.String(default="File Path")
692701
help_text = param.String()
@@ -698,14 +707,22 @@ def __init__(self, disabled=False, **params):
698707
self.file_browser = self.file_browser or FileBrowser(delayed_init=True)
699708
self.update_file(True)
700709
self.disabled = disabled
701-
# self.param.file_path.label = self.title
710+
self.param.file_path.label = self.title
711+
self.param.file_path.doc = self.help_text
702712
self._layout = pn.Column(
703713
self.input_row,
704-
pn.pane.HTML(f'<span style="font-style: italic;">{self.help_text}</span>'),
705714
self.file_browser_container,
706715
sizing_mode="stretch_width",
707716
)
708717

718+
@param.depends("title", watch=True)
719+
def update_title(self):
720+
self.param.file_path.label = self.title
721+
722+
@param.depends("help_text", watch=True)
723+
def update_help_text(self):
724+
self.param.file_path.doc = self.help_text
725+
709726
@param.depends("disabled", watch=True)
710727
def update_disabled(self):
711728
for p in ["file_path", "browse_toggle"]:
@@ -735,16 +752,16 @@ def initialize_file_browser(self):
735752
@param.depends("show_browser", watch=True)
736753
def show_hide_browser(self):
737754
self.file_browser_container.visible = self.show_browser
738-
self.param.browse_toggle.label = "Browse"
755+
self.param.browse_toggle.label = "📂"
756+
self.param.browse_toggle.doc = "Open file browser"
739757
if self.show_browser:
740758
self.initialize_file_browser()
741-
self.param.browse_toggle.label = "Hide"
759+
self.param.browse_toggle.label = "❌"
760+
self.param.browse_toggle.doc = "Close file browser"
742761

743762
def input_row(self):
744763
file_path = pn.widgets.TextInput.from_param(self.param.file_path, sizing_mode="stretch_width")
745-
browse_toggle = pn.widgets.Button.from_param(
746-
self.param.browse_toggle, button_type="primary", width=100, align="end"
747-
)
764+
browse_toggle = pn.widgets.Button.from_param(self.param.browse_toggle, width=40, align="end")
748765

749766
browse_toggle.js_on_click(
750767
args={"btn": browse_toggle},

uit/gui_tools/submit.py

Lines changed: 110 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,65 @@
77
import param
88
import panel as pn
99

10-
from .file_browser import HpcFileBrowser, get_js_loading_code
10+
from .file_browser import HpcFileBrowser, create_file_browser, get_js_loading_code, FileSelector
1111
from .utils import HpcBase, HpcConfigurable
1212
from ..uit import QUEUES
1313
from ..pbs_script import NODE_TYPES, factors, PbsScript
1414
from ..job import PbsJob
1515

16+
1617
logger = logging.getLogger(__name__)
1718

1819

1920
class PbsScriptInputs(HpcBase):
20-
hpc_subproject = param.Selector(default=None, label="HPC Subproject", precedence=3)
21-
subproject_usage = param.DataFrame(precedence=3.1)
22-
workdir = param.String(default="", precedence=4)
23-
node_type = param.Selector(default="", objects=[], label="Node Type", precedence=5)
24-
nodes = param.Integer(default=1, bounds=(1, 1000), precedence=5.1)
25-
processes_per_node = param.Selector(default=1, objects=[], label="Processes per Node", precedence=5.2)
26-
wall_time = param.String(default="01:00:00", label="Wall Time", precedence=6)
21+
hpc_subproject = param.Selector(
22+
default=None,
23+
label="HPC Subproject",
24+
precedence=3,
25+
doc="The resource allocation code that will be used when submitting this job.",
26+
)
27+
subproject_usage = param.DataFrame(precedence=3.1, doc="Usage details about your available subproject allocations.")
28+
node_type = param.Selector(
29+
default="", objects=[], label="Node Type", precedence=5, doc="Type of node on which this job will be run."
30+
)
31+
nodes = param.Integer(
32+
default=1,
33+
bounds=(1, 1000),
34+
precedence=5.1,
35+
doc=(
36+
"Number of nodes to request for the job.\n\n"
37+
"**Note:** for array jobs, the number of nodes requested are available to each sub job."
38+
),
39+
)
40+
processes_per_node = param.Selector(
41+
default=1,
42+
objects=[],
43+
label="Processes per Node",
44+
precedence=5.2,
45+
doc="Number of processes per node to request for the job.",
46+
)
47+
wall_time = param.String(
48+
default="01:00:00",
49+
label="Wall Time (HH:MM:SS)",
50+
precedence=6,
51+
doc=(
52+
"Maximum allowable time for the job to run.\n\n"
53+
"**Note:** for array jobs, the entire amount of wall time requested is available to each sub job."
54+
),
55+
)
2756
wall_time_alert = pn.pane.Alert(visible=False)
2857
node_alert = pn.pane.Alert(visible=False)
29-
queue = param.Selector(default=QUEUES[0], objects=QUEUES, precedence=7)
58+
queue = param.Selector(
59+
default=QUEUES[0], objects=QUEUES, precedence=7, doc="Scheduling queue to which the job will be submitted."
60+
)
3061
max_wall_time = param.String(default="Not Found", label="Max Wall Time", precedence=7.1)
3162
max_nodes = param.String(default="Not Found", label="Max Processes", precedence=7.2)
3263
submit_script_filename = param.String(default="run.pbs", precedence=8)
33-
notification_email = param.String(label="Notification E-mail(s)", precedence=9)
64+
notification_email = param.String(
65+
label="Notification E-mail(s)",
66+
precedence=9,
67+
doc="E-mail address to receive notification(s) when the job starts and/or ends.",
68+
)
3469
notify_start = param.Boolean(default=True, label="when job begins", precedence=9.1)
3570
notify_end = param.Boolean(default=True, label="when job ends", precedence=9.2)
3671

@@ -39,6 +74,22 @@ class PbsScriptInputs(HpcBase):
3974
wall_time_maxes = None
4075
node_maxes = None
4176

77+
def __init__(self, **params):
78+
super().__init__(**params)
79+
self.workdir = FileSelector(
80+
title="Base Directory",
81+
show_browser=False,
82+
help_text=(
83+
"Base directory that the job's working directory path will be created in.\n\n"
84+
"**Note:** by default the job's working directory is: "
85+
f"`<BASE_DIRECTORY>/{PbsJob.DEFAULT_JOB_LABEL}/<JOB_NAME>.<TIMESTAMP>/`"
86+
),
87+
)
88+
89+
@param.depends("uit_client", watch=True)
90+
def set_file_browser(self):
91+
self.workdir.file_browser = create_file_browser(self.uit_client, patterns=[])
92+
4293
@staticmethod
4394
def get_default(value, objects):
4495
return value if value in objects else objects[0]
@@ -54,7 +105,7 @@ async def update_hpc_connection_dependent_defaults(self):
54105
subprojects = self.subproject_usage["Subproject"].to_list()
55106
self.param.hpc_subproject.objects = subprojects
56107
self.hpc_subproject = self.get_default(self.hpc_subproject, subprojects)
57-
self.workdir = self.uit_client.WORKDIR.as_posix()
108+
self.workdir.file_path = self.uit_client.WORKDIR.as_posix()
58109
self.param.node_type.objects = list(NODE_TYPES[self.uit_client.system].keys())
59110
self.node_type = self.get_default(self.node_type, self.param.node_type.objects)
60111
self.param.queue.objects = await self.await_if_async(self.uit_client.get_queues())
@@ -169,7 +220,7 @@ def pbs_options_view(self):
169220
),
170221
pn.Column(
171222
self.param.hpc_subproject,
172-
self.param.workdir,
223+
self.workdir,
173224
self.param.node_type,
174225
pn.widgets.Spinner.from_param(self.param.nodes),
175226
self.param.processes_per_node,
@@ -198,39 +249,46 @@ class PbsScriptAdvancedInputs(HpcConfigurable):
198249
env_browsers = param.List()
199250
env_delete_buttons = param.List()
200251
file_browser = param.ClassSelector(class_=HpcFileBrowser)
201-
file_browser_col = param.ClassSelector(class_=pn.Column, default=pn.Column(None, sizing_mode="stretch_width"))
252+
file_browser_wb = param.ClassSelector(class_=pn.layout.WidgetBox)
202253
apply_file_browser = param.Action(label="Apply")
203254
close_file_browser = param.Action(lambda self: self.show_file_browser(False), label="Close")
204255
append_path = param.Boolean(label="Append to Path")
205256

257+
def __init__(self, **params):
258+
super().__init__(**params)
259+
self.environment_variables_card = pn.Card(
260+
title="Environment Variables",
261+
sizing_mode="stretch_width",
262+
margin=(10, 0),
263+
)
264+
self.update_environment_variables_col()
265+
self.file_browser_wb = pn.WidgetBox(
266+
self.file_browser,
267+
pn.Row(
268+
pn.widgets.Checkbox.from_param(self.param.append_path, width=100),
269+
pn.widgets.Button.from_param(
270+
self.param.apply_file_browser,
271+
button_type="success",
272+
width=100,
273+
),
274+
pn.widgets.Button.from_param(
275+
self.param.close_file_browser,
276+
button_type="primary",
277+
width=100,
278+
),
279+
align="end",
280+
),
281+
sizing_mode="stretch_width",
282+
)
283+
206284
@param.depends("uit_client", watch=True)
207285
def configure_file_browser(self):
208-
self.file_browser = None # HpcFileBrowser(self.uit_client) #TODO
286+
self.file_browser = create_file_browser(self.uit_client)
287+
if self.file_browser_wb:
288+
self.file_browser_wb[0] = self.file_browser
209289

210290
def show_file_browser(self, show):
211-
self.file_browser_col[0] = (
212-
pn.WidgetBox(
213-
self.file_browser.panel,
214-
pn.Row(
215-
pn.widgets.Checkbox.from_param(self.param.append_path, width=100),
216-
pn.widgets.Button.from_param(
217-
self.param.apply_file_browser,
218-
button_type="success",
219-
width=100,
220-
),
221-
pn.widgets.Button.from_param(
222-
self.param.close_file_browser,
223-
button_type="primary",
224-
width=100,
225-
),
226-
align="end",
227-
),
228-
sizing_mode="stretch_width",
229-
)
230-
if show
231-
else None
232-
)
233-
291+
self.environment_variables_card[-1] = self.file_browser_wb if show else None
234292
if not show:
235293
for btn in self.env_browsers:
236294
btn.loading = False
@@ -281,8 +339,8 @@ def update_file_path(self, _, index):
281339
else:
282340
self.env_values[index].value = self.file_browser.value[0]
283341

284-
@param.depends("environment_variables")
285-
def environment_variables_view(self):
342+
@param.depends("environment_variables", watch=True)
343+
def update_environment_variables_col(self):
286344
self.environment_variables.pop("", None) # Clear blank key if there is one
287345
self.env_names = list()
288346
self.env_values = list()
@@ -311,25 +369,20 @@ def environment_variables_view(self):
311369
self.env_names[0].name = "Name"
312370
self.env_values[0].name = "Value"
313371

314-
return pn.Card(
315-
*[
316-
pn.Row(k, v, b, d, sizing_mode="stretch_width")
317-
for k, v, b, d in zip_longest(
318-
self.env_names,
319-
self.env_values,
320-
self.env_browsers,
321-
self.env_delete_buttons,
322-
)
323-
],
324-
self.file_browser_col,
325-
title="Environment Variables",
326-
sizing_mode="stretch_width",
327-
margin=(10, 0),
328-
)
372+
self.environment_variables_card[:] = [
373+
pn.Row(k, v, b, d, sizing_mode="stretch_width")
374+
for k, v, b, d in zip_longest(
375+
self.env_names,
376+
self.env_values,
377+
self.env_browsers,
378+
self.env_delete_buttons,
379+
)
380+
]
381+
self.environment_variables_card.append(None)
329382

330383
def advanced_options_view(self):
331384
return pn.Column(
332-
self.environment_variables_view,
385+
self.environment_variables_card,
333386
pn.Card(
334387
"<h3>Modules to Load</h3>",
335388
pn.widgets.CrossSelector.from_param(self.param.modules_to_load, width=700),
@@ -463,11 +516,13 @@ def pbs_script(self):
463516

464517
@property
465518
def job(self):
519+
466520
if self._job is None:
467521
self._job = PbsJob(
468522
script=self.pbs_script,
469523
client=self.uit_client,
470524
workspace=self.user_workspace,
525+
base_dir=self.workdir.file_path,
471526
)
472527
return self._job
473528

uit/gui_tools/utils.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,13 @@ def load_config_file(self, reset=False):
102102

103103

104104
class HpcWorkspaces(HpcConfigurable):
105-
working_dir = param.ClassSelector(class_=PurePosixPath)
105+
base_dir = param.ClassSelector(class_=PurePosixPath)
106+
remote_workspace_suffix = param.ClassSelector(class_=PurePosixPath)
106107
_user_workspace = param.ClassSelector(class_=Path)
107108

108109
@property
109-
def remote_workspace_suffix(self):
110-
try:
111-
return self.working_dir.relative_to(self.uit_client.WORKDIR)
112-
except ValueError:
113-
return self.working_dir.relative_to("/p")
110+
def working_dir(self):
111+
return self.base_dir / self.remote_workspace_suffix
114112

115113
@property
116114
def workspace(self):
@@ -188,7 +186,8 @@ def update_selected_sub_job(self):
188186

189187
def update_working_dir(self):
190188
if self.selected_job is not None:
191-
self.working_dir = self.selected_job.working_dir
189+
self.base_dir = self.selected_job.base_dir
190+
self.remote_workspace_suffix = self.selected_job.remote_workspace_suffix
192191

193192
@param.depends("selected_sub_job", watch=True)
194193
def update_active_job(self):

0 commit comments

Comments
 (0)