bfabric-web-apps 0.1.3__py3-none-any.whl → 0.1.5__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.
@@ -16,85 +16,42 @@ from .utils.get_power_user_wrapper import get_power_user_wrapper
16
16
  from .utils.create_app_in_bfabric import create_app_in_bfabric
17
17
 
18
18
  # Export callbacks
19
- from .utils.callbacks import process_url_and_token, submit_bug_report
19
+ from .utils.callbacks import (
20
+ process_url_and_token,
21
+ submit_bug_report,
22
+ populate_workunit_details,
23
+ get_redis_queue_layout
24
+ )
20
25
 
21
- from .utils import defaults
26
+ from .utils.config import settings as config
22
27
 
23
- from bfabric_web_apps.utils.resource_utilities import create_workunit, create_resource
24
- HOST = os.getenv("HOST", defaults.HOST)
25
- PORT = int(os.getenv("PORT", defaults.PORT)) # Convert to int since env variables are strings
26
- DEV = os.getenv("DEV", str(defaults.DEV)).lower() in ["true", "1", "yes"] # Convert to bool
27
- CONFIG_FILE_PATH = os.getenv("CONFIG_FILE_PATH", defaults.CONFIG_FILE_PATH)
28
+ from. utils.run_main_pipeline import run_main_job
28
29
 
29
- DEVELOPER_EMAIL_ADDRESS = os.getenv("DEVELOPER_EMAIL_ADDRESS", defaults.DEVELOPER_EMAIL_ADDRESS)
30
- BUG_REPORT_EMAIL_ADDRESS = os.getenv("BUG_REPORT_EMAIL_ADDRESS", defaults.BUG_REPORT_EMAIL_ADDRESS)
30
+ from .utils.resource_utilities import (
31
+ create_workunit,
32
+ create_resource,
33
+ create_workunits,
34
+ create_resources
35
+ )
31
36
 
37
+ from .utils.redis_worker_init import run_worker, test_job
38
+ from .utils.redis_queue import q
32
39
 
33
- # Define __all__ for controlled imports
34
- __all__ = [
35
- "BfabricInterface",
36
- "Logger",
37
- "components",
38
- "get_static_layout",
39
- "create_app",
40
- "process_url_and_token",
41
- "submit_bug_report",
42
- 'get_logger',
43
- 'get_power_user_wrapper',
44
- 'HOST',
45
- 'PORT',
46
- 'DEV',
47
- 'CONFIG_FILE_PATH',
48
- 'DEVELOPER_EMAIL_ADDRESS',
49
- 'BUG_REPORT_EMAIL_ADDRESS',
50
- 'create_app_in_bfabric',
51
- 'create_workunit',
52
- 'create_resource'
53
- ]
40
+ REDIS_HOST = config.REDIS_HOST
41
+ REDIS_PORT = config.REDIS_PORT
54
42
 
43
+ HOST = config.HOST
44
+ PORT = config.PORT
45
+ DEV = config.DEV
46
+ DEBUG = config.DEBUG
55
47
 
48
+ CONFIG_FILE_PATH = config.CONFIG_FILE_PATH
56
49
 
57
- '''
58
- import os
59
- from .utils import defaults
60
-
61
- # Private variable for CONFIG_FILE_PATH
62
- _CONFIG_FILE_PATH = os.getenv("CONFIG_FILE_PATH", defaults.CONFIG_FILE_PATH)
63
-
64
- def set_config_file_path(path):
65
- """
66
- Setter for the CONFIG_FILE_PATH variable.
67
- """
68
- global _CONFIG_FILE_PATH
69
- if not isinstance(path, str):
70
- raise ValueError("CONFIG_FILE_PATH must be a string.")
71
- _CONFIG_FILE_PATH = path
72
-
73
- def get_config_file_path():
74
- """
75
- Getter for the CONFIG_FILE_PATH variable.
76
- """
77
- return _CONFIG_FILE_PATH
78
-
79
- # Expose CONFIG_FILE_PATH as a read-only property
80
- class Config:
81
- @property
82
- def CONFIG_FILE_PATH(self):
83
- return get_config_file_path()
84
-
85
- config = Config()
86
-
87
- '''
88
-
89
-
90
-
91
- '''
92
- from bfabric import config
93
-
94
- config.CONFIG_FILE_PATH
95
- '''
50
+ DEVELOPER_EMAIL_ADDRESS = config.DEVELOPER_EMAIL_ADDRESS
51
+ BUG_REPORT_EMAIL_ADDRESS = config.BUG_REPORT_EMAIL_ADDRESS
96
52
 
97
- '''
98
- from bfabric import set_config_file_path
99
- set_config_file_path("new/path/to/config.json")
100
- '''
53
+ GSTORE_REMOTE_PATH = config.GSTORE_REMOTE_PATH
54
+ SCRATCH_PATH = config.SCRATCH_PATH
55
+ TRX_LOGIN = config.TRX_LOGIN
56
+ TRX_SSH_KEY = config.TRX_SSH_KEY
57
+ URL = config.URL
@@ -2,7 +2,7 @@ from dash import html, dcc
2
2
  import dash_bootstrap_components as dbc
3
3
  import bfabric_web_apps
4
4
 
5
- def get_static_layout(base_title=None, main_content=None, documentation_content=None):
5
+ def get_static_layout(base_title=None, main_content=None, documentation_content=None, layout_config={}):
6
6
  """
7
7
  Returns a layout with static tabs for Main, Documentation, and Report a Bug.
8
8
  The main content is customizable, while the other tabs are generic.
@@ -11,10 +11,24 @@ def get_static_layout(base_title=None, main_content=None, documentation_content=
11
11
  base_title (str): The main title to be displayed in the banner.
12
12
  main_content (html.Div): Content to be displayed in the "Main" tab.
13
13
  documentation_content (html.Div): Content for the "Documentation" tab.
14
+ layout_config (dict): Configuration for the layout, determining which tabs are shown.
14
15
 
15
16
  Returns:
16
17
  html.Div: The complete static layout of the web app.
17
18
  """
19
+
20
+ tab_list = [
21
+ dbc.Tab(main_content, label="Main", tab_id="main"),
22
+ dbc.Tab(dcc.Loading(get_documentation_tab(documentation_content)), label="Documentation", tab_id="documentation"),
23
+ ]
24
+
25
+ if layout_config.get("workunits", False):
26
+ tab_list.append(dbc.Tab(dcc.Loading(get_workunits_tab()), label="Workunits", tab_id="workunits"))
27
+ if layout_config.get("queue", False):
28
+ tab_list.append(dbc.Tab(get_queue_tab(), label="Queue", tab_id="queue"))
29
+ if layout_config.get("bug", False):
30
+ tab_list.append(dbc.Tab(dcc.Loading(get_report_bug_tab()), label="Report a Bug", tab_id="report-bug"))
31
+
18
32
  return html.Div(
19
33
  children=[
20
34
  dcc.Location(id='url', refresh=False),
@@ -142,11 +156,7 @@ def get_static_layout(base_title=None, main_content=None, documentation_content=
142
156
 
143
157
  # Tabs Section
144
158
  dbc.Tabs(
145
- [
146
- dbc.Tab(main_content, label="Main", tab_id="main"),
147
- dbc.Tab(get_documentation_tab(documentation_content), label="Documentation", tab_id="documentation"),
148
- dbc.Tab(get_report_bug_tab(), label="Report a Bug", tab_id="report-bug"),
149
- ],
159
+ tab_list,
150
160
  id="tabs",
151
161
  active_tab="main",
152
162
  ),
@@ -250,10 +260,92 @@ def get_report_bug_tab():
250
260
  "margin-left": "2vw",
251
261
  "font-size": "20px",
252
262
  "padding-right": "40px",
263
+ "overflow-y": "scroll",
264
+ "max-height": "65vh",
253
265
  },
254
266
  ),
255
267
  width=9,
256
268
  ),
257
269
  ],
258
270
  style={"margin-top": "0px", "min-height": "40vh"},
271
+ )
272
+
273
+
274
+ def get_workunits_tab():
275
+ """
276
+ Returns the content for the Workunits tab with the upgraded layout.
277
+ """
278
+ return dbc.Row(
279
+ id="page-content-workunits",
280
+ children=[
281
+ dbc.Col(
282
+ html.Div(
283
+ id="sidebar_workunits",
284
+ children=[], # Optional: Add sidebar content here if needed
285
+ style={
286
+ "border-right": "2px solid #d4d7d9",
287
+ "height": "100%",
288
+ "padding": "20px",
289
+ "font-size": "20px",
290
+ },
291
+ ),
292
+ width=3,
293
+ ),
294
+ dbc.Col(
295
+ html.Div(
296
+ id="page-content-workunits-children",
297
+ children=[
298
+ html.H2("Workunits"),
299
+ html.Div(id="refresh-workunits", children=[]),
300
+ html.Div(
301
+ id="workunits-content"
302
+ )
303
+ ],
304
+ style={
305
+ "margin-top": "2vh",
306
+ "margin-left": "2vw",
307
+ "font-size": "20px",
308
+ "padding-right": "40px",
309
+ "overflow-y": "scroll",
310
+ "max-height": "65vh",
311
+ },
312
+ ),
313
+ width=9,
314
+ ),
315
+ ],
316
+ style={"margin-top": "0px", "min-height": "40vh"},
317
+ )
318
+
319
+
320
+ def get_queue_tab():
321
+
322
+ return dbc.Row(
323
+ id="page-content-queue",
324
+ children=[
325
+ dbc.Col(
326
+ html.Div(
327
+ id="page-content-queue-children",
328
+ children=[],
329
+ style={
330
+ "margin-top": "2vh",
331
+ "margin-left": "2vw",
332
+ "font-size": "20px",
333
+ "padding-right": "40px",
334
+ "overflow-y": "scroll",
335
+ "max-height": "65vh",
336
+ },
337
+ ),
338
+ ),
339
+ dbc.Col(
340
+ children = [
341
+ dcc.Interval(
342
+ id="queue-interval",
343
+ interval=5 * 1000, # in milliseconds
344
+ n_intervals=0,
345
+ ),
346
+ ],
347
+ style={"display": "none"}
348
+ )
349
+ ],
350
+ style={"margin-top": "0px", "min-height": "40vh"},
259
351
  )
@@ -9,12 +9,9 @@ from bfabric_web_apps.utils.get_logger import get_logger
9
9
  import os
10
10
  import bfabric_web_apps
11
11
 
12
-
13
-
14
12
  VALIDATION_URL = "https://fgcz-bfabric.uzh.ch/bfabric/rest/token/validate?token="
15
13
  HOST = "fgcz-bfabric.uzh.ch"
16
14
 
17
-
18
15
  class BfabricInterface( Bfabric ):
19
16
  _instance = None # Singleton instance
20
17
  _wrapper = None # Shared wrapper instance
@@ -50,8 +47,7 @@ class BfabricInterface( Bfabric ):
50
47
  if self._wrapper is None:
51
48
  raise RuntimeError("Bfabric wrapper is not initialized. Token validation must run first.")
52
49
  return self._wrapper
53
-
54
-
50
+
55
51
 
56
52
  def token_to_data(self, token):
57
53
  """
@@ -96,8 +92,6 @@ class BfabricInterface( Bfabric ):
96
92
 
97
93
  environment_dict = {"Production":"https://fgcz-bfabric.uzh.ch/bfabric","Test":"https://fgcz-bfabric-test.uzh.ch/bfabric"}
98
94
 
99
- print('userinfo', userinfo)
100
-
101
95
  token_data = dict(
102
96
  environment = userinfo['environment'],
103
97
  user_data = userinfo['user'],
@@ -267,7 +261,7 @@ class BfabricInterface( Bfabric ):
267
261
 
268
262
  # Extract App ID, Name, and Description
269
263
  app_info = app_data_dict[0] # First (and only) result
270
- print('app_info', app_info)
264
+
271
265
  json_data = json.dumps({
272
266
  "id": app_info.get("id", "Unknown"),
273
267
  "name": app_info.get("name", "Unknown"),
@@ -312,10 +306,9 @@ class BfabricInterface( Bfabric ):
312
306
  os.system(mail)
313
307
 
314
308
  return True
309
+
315
310
 
316
-
317
-
318
-
311
+
319
312
  # Create a globally accessible instance
320
313
  bfabric_interface = BfabricInterface()
321
314
 
@@ -1,9 +1,13 @@
1
1
  from dash import Input, Output, State, html, dcc
2
- from bfabric_web_apps.objects.BfabricInterface import BfabricInterface
2
+ from bfabric_web_apps.objects.BfabricInterface import bfabric_interface
3
3
  import json
4
4
  import dash_bootstrap_components as dbc
5
5
  from datetime import datetime as dt
6
6
  from bfabric_web_apps.utils.get_logger import get_logger
7
+ from rq import Queue
8
+ from .redis_connection import redis_conn
9
+ from rq.registry import StartedJobRegistry, FailedJobRegistry, FinishedJobRegistry
10
+
7
11
 
8
12
  def process_url_and_token(url_params):
9
13
  """
@@ -28,7 +32,6 @@ def process_url_and_token(url_params):
28
32
  return None, None, None, None, base_title, None, None
29
33
 
30
34
  token = "".join(url_params.split('token=')[1:])
31
- bfabric_interface = BfabricInterface()
32
35
  tdata_raw = bfabric_interface.token_to_data(token)
33
36
 
34
37
  if tdata_raw:
@@ -88,7 +91,6 @@ def process_url_and_token(url_params):
88
91
  return None, None, None, None, base_title, None, None
89
92
 
90
93
 
91
-
92
94
  def submit_bug_report(n_clicks, bug_description, token, entity_data):
93
95
  """
94
96
  Submits a bug report based on user input, token, and entity data.
@@ -103,7 +105,7 @@ def submit_bug_report(n_clicks, bug_description, token, entity_data):
103
105
  tuple: A tuple containing two boolean values indicating success and failure status of the submission.
104
106
  (is_open_success, is_open_failure)
105
107
  """
106
- bfabric_interface = BfabricInterface()
108
+
107
109
  print("submit bug report", token)
108
110
 
109
111
  # Parse token data if token is provided, otherwise set it to an empty dictionary
@@ -168,3 +170,169 @@ def submit_bug_report(n_clicks, bug_description, token, entity_data):
168
170
 
169
171
  return False, False
170
172
 
173
+
174
+ def populate_workunit_details(token_data):
175
+
176
+ """
177
+ Function to populate workunit data for the current app instance.
178
+
179
+ Args:
180
+ token_data (dict): Token metadata.
181
+
182
+ Returns:
183
+ html.Div: A div containing the populated workunit data.
184
+ """
185
+
186
+ environment_urls = {
187
+ "Test": "https://fgcz-bfabric-test.uzh.ch/bfabric/workunit/show.html?id=",
188
+ "Production": "https://fgcz-bfabric.uzh.ch/bfabric/workunit/show.html?id="
189
+ }
190
+
191
+ if token_data:
192
+
193
+ jobId = token_data.get('jobId', None)
194
+ print("jobId", jobId)
195
+
196
+ job = bfabric_interface.get_wrapper().read("job", {"id": jobId})[0]
197
+ workunits = job.get("workunit", [])
198
+
199
+ if workunits:
200
+ wus = bfabric_interface.get_wrapper().read(
201
+ "workunit",
202
+ {"id": [wu["id"] for wu in workunits]}
203
+ )
204
+ else:
205
+ return html.Div(
206
+ [
207
+ html.P("No workunits found for the current job.")
208
+ ]
209
+ )
210
+
211
+ wu_cards = []
212
+
213
+ for wu in wus:
214
+ print(wu)
215
+ wu_card = html.A(
216
+ dbc.Card([
217
+ dbc.CardHeader(html.B(f"Workunit {wu['id']}")),
218
+ dbc.CardBody([
219
+ html.P(f"Name: {wu.get('name', 'n/a')}"),
220
+ html.P(f"Description: {wu.get('description', 'n/a')}"),
221
+ html.P(f"Num Resources: {len(wu.get('resource', []))}"),
222
+ html.P(f"Created: {wu.get('created', 'n/a')}"),
223
+ html.P(f"Status: {wu.get('status', 'n/a')}")
224
+ ])
225
+ ], style={"width": "400px", "margin":"10px"}),
226
+ href=environment_urls[token_data.get("environment", "Test")] + str(wu["id"]),
227
+ target="_blank",
228
+ style={"text-decoration": "none"}
229
+ )
230
+
231
+ wu_cards.append(wu_card)
232
+
233
+ return dbc.Container(wu_cards, style={"display": "flex", "flex-wrap": "wrap"})
234
+ else:
235
+ return html.Div()
236
+
237
+ def get_redis_queue_layout():
238
+ # Get all queues dynamically
239
+ queues = Queue.all(connection=redis_conn)
240
+
241
+ queue_cards = []
242
+
243
+ print("QUEUES", queues)
244
+
245
+ for queue in queues:
246
+ queue_name = queue.name
247
+
248
+ # Get queue stats
249
+ started_registry = StartedJobRegistry(queue_name, connection=redis_conn)
250
+ failed_registry = FailedJobRegistry(queue_name, connection=redis_conn)
251
+ finished_registry = FinishedJobRegistry(queue_name, connection=redis_conn)
252
+
253
+ stats = {
254
+ "Jobs in queue": queue.count,
255
+ "Running": started_registry.count,
256
+ "Failed": failed_registry.count,
257
+ "Completed": finished_registry.count,
258
+ }
259
+
260
+ print("STAT", stats)
261
+
262
+ stats_row = dbc.Row([
263
+ dbc.Col([
264
+ html.P([html.B("Jobs in queue: "), f"{queue.count}"]),
265
+ html.P([html.B("Running: "), f"{started_registry.count}"]),
266
+ ],width=6),
267
+ dbc.Col([
268
+ html.P([html.B("Failed: "), f"{failed_registry.count}"]),
269
+ html.P([html.B("Completed: "), f"{finished_registry.count}"]),
270
+ ], width=6)
271
+ ])
272
+
273
+ # Fetch job details
274
+ job_cards = []
275
+ for job_id in started_registry.get_job_ids():
276
+ job = queue.fetch_job(job_id)
277
+ if job:
278
+ job_cards.append(
279
+ dbc.Card(
280
+ dbc.CardBody([
281
+ html.H6(f"Job ID: {job.id}", className="card-title"),
282
+ html.P(f"Function: {job.func_name}", className="card-text"),
283
+ html.P(f"Status: Running", className="text-success"),
284
+ ]),
285
+ style={"maxWidth": "36vw", "backgroundColor": "#d4edda"}, className="mb-2"
286
+ )
287
+ )
288
+
289
+ for job_id in failed_registry.get_job_ids():
290
+ job = queue.fetch_job(job_id)
291
+ if job:
292
+ job_cards.append(
293
+ dbc.Card(
294
+ dbc.CardBody([
295
+ html.H6(f"Job ID: {job.id}", className="card-title"),
296
+ html.P(f"Function: {job.func_name}", className="card-text"),
297
+ html.P(f"Status: Failed", className="text-danger"),
298
+ ]),
299
+ style={"maxWidth": "36vw", "backgroundColor": "#f8d7da"}, className="mb-2"
300
+ )
301
+ )
302
+
303
+ for job_id in finished_registry.get_job_ids():
304
+ job = queue.fetch_job(job_id)
305
+ if job:
306
+ finished_time = job.ended_at.strftime("%Y-%m-%d %H:%M:%S") if job.ended_at else "Unknown"
307
+ job_cards.append(
308
+ dbc.Card(
309
+ dbc.CardBody([
310
+ html.H6(f"Job ID: {job.id}", className="card-title"),
311
+ html.P(f"Function: {job.func_name}", className="card-text"),
312
+ html.P(f"Status: Completed", className="text-primary"),
313
+ html.P(f"Finished at: {finished_time}", className="text-muted"),
314
+ ]),
315
+ style={"maxWidth": "36vw", "backgroundColor": "#d1ecf1"}, className="mb-2"
316
+ )
317
+ )
318
+
319
+ # Create queue card
320
+ queue_card = dbc.Col([
321
+ dbc.Card(
322
+ [
323
+ dbc.CardHeader(html.H5(f"Queue: {queue_name}")),
324
+ dbc.CardBody([
325
+ stats_row, # Fixed: Convert dictionary to list
326
+ html.Hr(),
327
+ *job_cards # Add job sub-cards
328
+ ], style={"maxHeight": "58vh", "overflow-y": "scroll"})
329
+ ],
330
+ style={"maxWidth": "36vw", "backgroundColor": "#f8f9fa", "max-height":"60vh"}, className="mb-4"
331
+ )
332
+ ])
333
+
334
+ queue_cards.append(queue_card)
335
+
336
+ container_children = dbc.Row(queue_cards)
337
+
338
+ return dbc.Container(container_children, className="mt-4")
@@ -0,0 +1,38 @@
1
+ from pydantic_settings import BaseSettings
2
+ from pydantic import EmailStr
3
+
4
+ class Settings(BaseSettings):
5
+
6
+ REDIS_HOST: str = "localhost"
7
+ REDIS_PORT: int = 6379
8
+
9
+ CONFIG_FILE_PATH: str = "~/.bfabricpy.yml"
10
+
11
+ HOST: str = "127.0.0.1"
12
+ PORT: int = 8050
13
+
14
+ DEV: bool = False
15
+ DEBUG: bool = False
16
+
17
+ DEVELOPER_EMAIL_ADDRESS: EmailStr = "griffin@gwcustom.com"
18
+ BUG_REPORT_EMAIL_ADDRESS: EmailStr = "gwtools@fgcz.system"
19
+
20
+ #Run main pipeline config (only FGCZ specific)
21
+ GSTORE_REMOTE_PATH: str = "/path/to/remote/gstore"
22
+ SCRATCH_PATH: str = "/scratch/folder"
23
+ TRX_LOGIN: str = "trxcopy@fgcz-server.uzh.ch"
24
+ TRX_SSH_KEY: str = "/home/user/.ssh/your_ssh_key"
25
+ URL: str = "https:/fgcz/dummy/url"
26
+
27
+ class Config:
28
+
29
+ env_file = ".env"
30
+
31
+ # Disable reading from environment variables
32
+ @classmethod
33
+ def customise_sources(cls, init_settings, env_settings, file_secret_settings):
34
+ return file_secret_settings, init_settings
35
+
36
+ # Instantiate settings
37
+ settings = Settings()
38
+
@@ -0,0 +1,6 @@
1
+
2
+ from .config import settings as config
3
+
4
+ from redis import Redis
5
+
6
+ redis_conn = Redis(host=config.REDIS_HOST, port=config.REDIS_PORT)
@@ -0,0 +1,6 @@
1
+ from rq import Queue
2
+ from .redis_connection import redis_conn as conn
3
+
4
+
5
+ def q(queue_name):
6
+ return Queue(name=queue_name, connection=conn, default_timeout=10000000)
@@ -0,0 +1,28 @@
1
+ import redis
2
+ from rq import Worker, Queue, Connection
3
+ import time
4
+
5
+ def test_job():
6
+
7
+ """
8
+ A test job that prints a message to the console.
9
+ """
10
+ print("Hello, this is a test job!")
11
+ time.sleep(10)
12
+ print("Test job finished!")
13
+ return
14
+
15
+
16
+ def run_worker(host, port, queue_names):
17
+ """
18
+ Provides internal interface for running workers on a specified host and port.
19
+
20
+ Args:
21
+ host (str): The host to run
22
+ port (int): The port to run
23
+ queue_names (list): A list of queue names to listen to
24
+ """
25
+ conn = redis.Redis(host=host, port=port)
26
+ with Connection(conn):
27
+ worker = Worker(map(Queue, queue_names))
28
+ worker.work()