bfabric-web-apps 0.1.0__py3-none-any.whl → 0.1.2__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.
@@ -1,6 +1,97 @@
1
- from objects import (
2
- BfabricInterface,
3
- Logger
4
- )
1
+ import os
5
2
 
6
- from layouts.layouts import *
3
+ # Export objects and classes
4
+ from bfabric_web_apps.objects import BfabricInterface, Logger
5
+
6
+ # Export components
7
+ from .utils import components
8
+
9
+ # Export layouts
10
+ from .layouts.layouts import get_static_layout
11
+
12
+ # Export app initialization utilities
13
+ from .utils.app_init import create_app
14
+ from .utils.get_logger import get_logger
15
+ from .utils.get_power_user_wrapper import get_power_user_wrapper
16
+ from .utils.create_app_in_bfabric import create_app_in_bfabric
17
+
18
+ # Export callbacks
19
+ from .utils.callbacks import process_url_and_token, submit_bug_report
20
+
21
+ from .utils import defaults
22
+
23
+ HOST = os.getenv("HOST", defaults.HOST)
24
+ PORT = int(os.getenv("PORT", defaults.PORT)) # Convert to int since env variables are strings
25
+ DEV = os.getenv("DEV", str(defaults.DEV)).lower() in ["true", "1", "yes"] # Convert to bool
26
+ CONFIG_FILE_PATH = os.getenv("CONFIG_FILE_PATH", defaults.CONFIG_FILE_PATH)
27
+
28
+ DEVELOPER_EMAIL_ADDRESS = os.getenv("DEVELOPER_EMAIL_ADDRESS", defaults.DEVELOPER_EMAIL_ADDRESS)
29
+ BUG_REPORT_EMAIL_ADDRESS = os.getenv("BUG_REPORT_EMAIL_ADDRESS", defaults.BUG_REPORT_EMAIL_ADDRESS)
30
+
31
+
32
+ # Define __all__ for controlled imports
33
+ __all__ = [
34
+ "BfabricInterface",
35
+ "Logger",
36
+ "components",
37
+ "get_static_layout",
38
+ "create_app",
39
+ "process_url_and_token",
40
+ "submit_bug_report",
41
+ 'get_logger',
42
+ 'get_power_user_wrapper',
43
+ 'HOST',
44
+ 'PORT',
45
+ 'DEV',
46
+ 'CONFIG_FILE_PATH',
47
+ 'DEVELOPER_EMAIL_ADDRESS',
48
+ 'BUG_REPORT_EMAIL_ADDRESS',
49
+ 'create_app_in_bfabric'
50
+ ]
51
+
52
+
53
+
54
+ '''
55
+ import os
56
+ from .utils import defaults
57
+
58
+ # Private variable for CONFIG_FILE_PATH
59
+ _CONFIG_FILE_PATH = os.getenv("CONFIG_FILE_PATH", defaults.CONFIG_FILE_PATH)
60
+
61
+ def set_config_file_path(path):
62
+ """
63
+ Setter for the CONFIG_FILE_PATH variable.
64
+ """
65
+ global _CONFIG_FILE_PATH
66
+ if not isinstance(path, str):
67
+ raise ValueError("CONFIG_FILE_PATH must be a string.")
68
+ _CONFIG_FILE_PATH = path
69
+
70
+ def get_config_file_path():
71
+ """
72
+ Getter for the CONFIG_FILE_PATH variable.
73
+ """
74
+ return _CONFIG_FILE_PATH
75
+
76
+ # Expose CONFIG_FILE_PATH as a read-only property
77
+ class Config:
78
+ @property
79
+ def CONFIG_FILE_PATH(self):
80
+ return get_config_file_path()
81
+
82
+ config = Config()
83
+
84
+ '''
85
+
86
+
87
+
88
+ '''
89
+ from bfabric import config
90
+
91
+ config.CONFIG_FILE_PATH
92
+ '''
93
+
94
+ '''
95
+ from bfabric import set_config_file_path
96
+ set_config_file_path("new/path/to/config.json")
97
+ '''
@@ -1,13 +1,259 @@
1
+ from dash import html, dcc
2
+ import dash_bootstrap_components as dbc
3
+ import bfabric_web_apps
1
4
 
5
+ def get_static_layout(base_title=None, main_content=None, documentation_content=None):
6
+ """
7
+ Returns a layout with static tabs for Main, Documentation, and Report a Bug.
8
+ The main content is customizable, while the other tabs are generic.
2
9
 
3
- def get_empty_layout():
10
+ Args:
11
+ base_title (str): The main title to be displayed in the banner.
12
+ main_content (html.Div): Content to be displayed in the "Main" tab.
13
+ documentation_content (html.Div): Content for the "Documentation" tab.
4
14
 
5
- pass
15
+ Returns:
16
+ html.Div: The complete static layout of the web app.
17
+ """
18
+ return html.Div(
19
+ children=[
20
+ dcc.Location(id='url', refresh=False),
21
+ dcc.Store(id='token', storage_type='session'),
22
+ dcc.Store(id='entity', storage_type='session'),
23
+ dcc.Store(id='app_data', storage_type='session'),
24
+ dcc.Store(id='token_data', storage_type='session'),
25
+ dcc.Store(id='dynamic-link-store', storage_type='session'), # Store for dynamic job link
6
26
 
7
- def get_layout_with_sidebar():
8
-
9
- pass
27
+ dbc.Container(
28
+ children=[
29
+ # Banner Section
30
+ dbc.Row(
31
+ dbc.Col(
32
+ html.Div(
33
+ className="banner",
34
+ children=[
35
+ # Title
36
+ html.Div(
37
+ children=[
38
+ html.P(
39
+ base_title,
40
+ style={
41
+ 'color': '#ffffff',
42
+ 'margin-top': '15px',
43
+ 'height': '80px',
44
+ 'width': '100%',
45
+ 'font-size': '40px',
46
+ 'margin-left': '20px'
47
+ }
48
+ )
49
+ ],
50
+ style={"background-color": "#000000", "border-radius": "10px"}
51
+ ),
52
+ ],
53
+ style={"position": "relative", "padding": "10px"}
54
+ ),
55
+ ),
56
+ ),
10
57
 
11
- def get_layout_without_sidebar():
12
-
13
- pass
58
+ # Page Title Section + View Logs Button (Aligned Right)
59
+ dbc.Row(
60
+ dbc.Col(
61
+ html.Div(
62
+ children=[
63
+ # Page Title (Aligned Left)
64
+ html.P(
65
+ id="page-title",
66
+ children=[str(" ")],
67
+ style={"font-size": "40px", "margin-left": "20px", "margin-top": "10px"}
68
+ ),
69
+
70
+ # View Logs Button (Aligned Right)
71
+ html.Div(
72
+ children=[
73
+ html.A(
74
+ dbc.Button(
75
+ "View Logs",
76
+ id="dynamic-link-button",
77
+ color="secondary", # Greyish color
78
+ style={
79
+ "font-size": "18px",
80
+ "padding": "10px 20px",
81
+ "border-radius": "8px"
82
+ }
83
+ ),
84
+ id="dynamic-link",
85
+ href="#", # Will be dynamically set in the callback
86
+ target="_blank"
87
+ )
88
+ ],
89
+ style={
90
+ "position": "absolute",
91
+ "right": "20px",
92
+ "top": "10px", # Aligns with title
93
+ }
94
+ ),
95
+ ],
96
+ style={
97
+ "position": "relative", # Ensures absolute positioning works
98
+ "margin-top": "0px",
99
+ "min-height": "80px",
100
+ "height": "6vh",
101
+ "border-bottom": "2px solid #d4d7d9",
102
+ "display": "flex",
103
+ "align-items": "center",
104
+ "justify-content": "space-between", # Title left, button right
105
+ "padding-right": "20px" # Space between button & right edge
106
+ }
107
+ ),
108
+ ),
109
+ ),
110
+
111
+ # Bug Report Alerts (Restored)
112
+ dbc.Row(
113
+ dbc.Col(
114
+ [
115
+ dbc.Alert(
116
+ "Your bug report has been submitted. Thanks for helping us improve!",
117
+ id="alert-fade-bug-success",
118
+ dismissable=True,
119
+ is_open=False,
120
+ color="info",
121
+ style={
122
+ "max-width": "50vw",
123
+ "margin-left": "10px",
124
+ "margin-top": "10px",
125
+ }
126
+ ),
127
+ dbc.Alert(
128
+ "Failed to submit bug report! Please email the developers directly at the email below!",
129
+ id="alert-fade-bug-fail",
130
+ dismissable=True,
131
+ is_open=False,
132
+ color="danger",
133
+ style={
134
+ "max-width": "50vw",
135
+ "margin-left": "10px",
136
+ "margin-top": "10px",
137
+ }
138
+ ),
139
+ ]
140
+ )
141
+ ),
142
+
143
+ # Tabs Section
144
+ 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
+ ],
150
+ id="tabs",
151
+ active_tab="main",
152
+ ),
153
+ ],
154
+ fluid=True,
155
+ style={"width": "100vw"}
156
+ )
157
+ ],
158
+ style={"width": "100vw", "overflow-x": "hidden", "overflow-y": "scroll"}
159
+ )
160
+
161
+
162
+ def get_documentation_tab(documentation_content):
163
+ """
164
+ Returns the content for the Documentation tab with the upgraded layout.
165
+ """
166
+ return dbc.Row(
167
+ id="page-content-docs",
168
+ children=[
169
+ dbc.Col(
170
+ html.Div(
171
+ id="sidebar_docs",
172
+ children=[],
173
+ style={
174
+ "border-right": "2px solid #d4d7d9",
175
+ "height": "100%",
176
+ "padding": "20px",
177
+ "font-size": "20px",
178
+ },
179
+ ),
180
+ width=3,
181
+ ),
182
+ dbc.Col(
183
+ html.Div(
184
+ id="page-content-docs-children",
185
+ children= documentation_content,
186
+ style={"margin-top":"2vh", "margin-left":"2vw", "font-size":"20px", "padding-right":"40px", "overflow-y": "scroll", "max-height": "60vh"},
187
+ ),
188
+ width=9,
189
+ ),
190
+ ],
191
+ style={"margin-top": "0px", "min-height": "40vh"},
192
+ )
193
+
194
+
195
+ def get_report_bug_tab():
196
+ """
197
+ Returns the content for the Report a Bug tab with the upgraded layout.
198
+ """
199
+ return dbc.Row(
200
+ id="page-content-bug-report",
201
+ children=[
202
+ dbc.Col(
203
+ html.Div(
204
+ id="sidebar_bug_report",
205
+ children=[], # Optional: Add sidebar content here if needed
206
+ style={
207
+ "border-right": "2px solid #d4d7d9",
208
+ "height": "100%",
209
+ "padding": "20px",
210
+ "font-size": "20px",
211
+ },
212
+ ),
213
+ width=3,
214
+ ),
215
+ dbc.Col(
216
+ html.Div(
217
+ id="page-content-bug-report-children",
218
+ children=[
219
+ html.H2("Report a Bug"),
220
+ html.P(
221
+ [
222
+ "Please use the form below to report a bug. If you have any questions, please email the developer at ",
223
+ html.A(
224
+ bfabric_web_apps.DEVELOPER_EMAIL_ADDRESS,
225
+ href=f"mailto:{bfabric_web_apps.DEVELOPER_EMAIL_ADDRESS}",
226
+ ),
227
+ ]
228
+ ),
229
+ html.Br(),
230
+ html.H4("Session Details: "),
231
+ html.Br(),
232
+ html.P(id="session-details", children="No Active Session"),
233
+ html.Br(),
234
+ html.H4("Bug Description"),
235
+ dbc.Textarea(
236
+ id="bug-description",
237
+ placeholder="Please describe the bug you encountered here.",
238
+ style={"width": "100%"},
239
+ ),
240
+ html.Br(),
241
+ dbc.Button(
242
+ "Submit Bug Report",
243
+ id="submit-bug-report",
244
+ n_clicks=0,
245
+ style={"margin-bottom": "60px"},
246
+ ),
247
+ ],
248
+ style={
249
+ "margin-top": "2vh",
250
+ "margin-left": "2vw",
251
+ "font-size": "20px",
252
+ "padding-right": "40px",
253
+ },
254
+ ),
255
+ width=9,
256
+ ),
257
+ ],
258
+ style={"margin-top": "0px", "min-height": "40vh"},
259
+ )
@@ -1,16 +1,285 @@
1
- from bfabric import Bfabric
1
+ from bfabric import Bfabric
2
+ import requests
3
+ import json
4
+ import datetime
5
+ import bfabric
6
+ from bfabric import BfabricAuth
7
+ from bfabric import BfabricClientConfig
8
+ from bfabric_web_apps.utils.get_logger import get_logger
9
+ import os
10
+ import bfabric_web_apps
2
11
 
3
- class BfabricInterface( Bfabric ):
4
12
 
5
- def __init__():
6
- pass
7
13
 
8
- def token_to_data(self, token):
9
- pass
14
+ VALIDATION_URL = "https://fgcz-bfabric.uzh.ch/bfabric/rest/token/validate?token="
15
+ HOST = "fgcz-bfabric.uzh.ch"
10
16
 
11
- def token_response_to_bfabric(self, token_response):
12
- pass
13
17
 
14
- def entity_data(self, entity):
18
+ class BfabricInterface( Bfabric ):
19
+ """
20
+ A class to interface with the Bfabric API, providing methods to validate tokens,
21
+ retrieve data, and send bug reports.
22
+ """
23
+
24
+ def __init__(self):
25
+ """
26
+ Initializes an instance of BfabricInterface.
27
+ """
15
28
  pass
16
29
 
30
+ def token_to_data(self, token):
31
+ """
32
+ Validates the given token and retrieves its associated data.
33
+
34
+ Args:
35
+ token (str): The token to validate.
36
+
37
+ Returns:
38
+ str: A JSON string containing token data if valid.
39
+ str: "EXPIRED" if the token is expired.
40
+ None: If the token is invalid or validation fails.
41
+ """
42
+
43
+ if not token:
44
+ return None
45
+
46
+ validation_url = VALIDATION_URL + token
47
+ res = requests.get(validation_url, headers={"Host": HOST})
48
+
49
+ if res.status_code != 200:
50
+ res = requests.get(validation_url)
51
+
52
+ if res.status_code != 200:
53
+ return None
54
+ try:
55
+ master_data = json.loads(res.text)
56
+ except:
57
+ return None
58
+
59
+ if True:
60
+
61
+ userinfo = json.loads(res.text)
62
+ expiry_time = userinfo['expiryDateTime']
63
+ current_time = datetime.datetime.now()
64
+ five_minutes_later = current_time + datetime.timedelta(minutes=5)
65
+
66
+ # Comparing the parsed expiry time with the five minutes later time
67
+
68
+ if not five_minutes_later <= datetime.datetime.strptime(expiry_time, "%Y-%m-%d %H:%M:%S"):
69
+ return "EXPIRED"
70
+
71
+ environment_dict = {"Production":"https://fgcz-bfabric.uzh.ch/bfabric","Test":"https://fgcz-bfabric-test.uzh.ch/bfabric"}
72
+
73
+ token_data = dict(
74
+ environment = userinfo['environment'],
75
+ user_data = userinfo['user'],
76
+ token_expires = expiry_time,
77
+ entity_id_data = userinfo['entityId'],
78
+ entityClass_data = userinfo['entityClassName'],
79
+ webbase_data = environment_dict.get(userinfo['environment'], None),
80
+ application_params_data = {},
81
+ application_data = str(userinfo['applicationId']),
82
+ userWsPassword = userinfo['userWsPassword'],
83
+ jobId = userinfo['jobId']
84
+ )
85
+
86
+ return json.dumps(token_data)
87
+
88
+
89
+
90
+ def token_response_to_bfabric(self, token_response):
91
+
92
+ """
93
+ Converts token response data into a Bfabric object for further interactions.
94
+
95
+ Args:
96
+ token_response (dict): The token response data.
97
+
98
+ Returns:
99
+ Bfabric: An authenticated Bfabric instance.
100
+ """
101
+
102
+ bfabric_auth = BfabricAuth(login=token_response.get('user_data'), password=token_response.get('userWsPassword'))
103
+ bfabric_client_config = BfabricClientConfig(base_url=token_response.get('webbase_data'))
104
+
105
+ bfabric_wrapper = bfabric.Bfabric(config=bfabric_client_config, auth=bfabric_auth)
106
+
107
+ return bfabric_wrapper
108
+
109
+
110
+
111
+
112
+ def entity_data(self, token_data: dict) -> str:
113
+ """
114
+ Retrieves entity data associated with the provided token.
115
+
116
+ Args:
117
+ token_data (dict): The token data.
118
+
119
+ Returns:
120
+ str: A JSON string containing entity data.
121
+ {}: If the retrieval fails or token_data is invalid.
122
+ """
123
+
124
+ entity_class_map = {
125
+ "Run": "run",
126
+ "Sample": "sample",
127
+ "Project": "container",
128
+ "Order": "container",
129
+ "Container": "container",
130
+ "Plate": "plate"
131
+ }
132
+
133
+ if not token_data:
134
+ return json.dumps({})
135
+
136
+ wrapper = self.token_response_to_bfabric(token_data)
137
+ entity_class = token_data.get('entityClass_data', None)
138
+ endpoint = entity_class_map.get(entity_class, None)
139
+ entity_id = token_data.get('entity_id_data', None)
140
+ jobId = token_data.get('jobId', None)
141
+ username = token_data.get("user_data", "None")
142
+ environment = token_data.get("environment", "None")
143
+
144
+ if wrapper and entity_class and endpoint and entity_id and jobId:
145
+ L = get_logger(token_data)
146
+
147
+ # Log the read operation directly using Logger L
148
+ entity_data_dict = L.logthis(
149
+ api_call=wrapper.read,
150
+ endpoint=endpoint,
151
+ obj={"id": entity_id},
152
+ max_results=None,
153
+ params=None,
154
+ flush_logs=True
155
+ )[0]
156
+
157
+
158
+ if entity_data_dict:
159
+ json_data = json.dumps({
160
+ "name": entity_data_dict.get("name", ""),
161
+ "createdby": entity_data_dict.get("createdby"),
162
+ "created": entity_data_dict.get("created"),
163
+ "modified": entity_data_dict.get("modified"),
164
+ })
165
+ return json_data
166
+ else:
167
+ L.log_operation(
168
+ operation="entity_data",
169
+ message="Entity data retrieval failed or returned None.",
170
+ params=None,
171
+ flush_logs=True
172
+ )
173
+ print("entity_data_dict is empty or None")
174
+ return json.dumps({})
175
+
176
+ else:
177
+ print("Invalid input or entity information")
178
+ return json.dumps({})
179
+
180
+
181
+ def app_data(self, token_data: dict) -> str:
182
+ """
183
+ Retrieves application data (App Name and Description) associated with the provided token.
184
+
185
+ Args:
186
+ token_data (dict): The token data.
187
+
188
+ Returns:
189
+ str: A JSON string containing application data.
190
+ {}: If retrieval fails or token_data is invalid.
191
+ """
192
+
193
+ if not token_data:
194
+ return json.dumps({}) # Return empty JSON if no token data
195
+
196
+ # Extract App ID from token
197
+ app_data_raw = token_data.get("application_data", None)
198
+
199
+ try:
200
+ app_id = int(app_data_raw)
201
+ except:
202
+ print("Invalid application_data format in token_data")
203
+ return json.dumps({}) # Return empty JSON if app_id is invalid
204
+
205
+ # Define API endpoint
206
+ endpoint = "application"
207
+
208
+ # Initialize Logger
209
+ L = get_logger(token_data)
210
+
211
+ # Get API wrapper
212
+ wrapper = self.token_response_to_bfabric(token_data) # Same as entity_data
213
+ if not wrapper:
214
+ print("Failed to get Bfabric API wrapper")
215
+ return json.dumps({})
216
+
217
+ # Make API Call
218
+ app_data_dict = L.logthis(
219
+ api_call=wrapper.read,
220
+ endpoint=endpoint,
221
+ obj={"id": app_id}, # Query using the App ID
222
+ max_results=None,
223
+ params=None,
224
+ flush_logs=True
225
+ )
226
+
227
+ # If API call fails, return empty JSON
228
+ if not app_data_dict or len(app_data_dict) == 0:
229
+ L.log_operation(
230
+ operation="app_data",
231
+ message=f"Failed to retrieve application data for App ID {app_id}",
232
+ params=None,
233
+ flush_logs=True
234
+ )
235
+ return json.dumps({})
236
+
237
+ # Extract Name and Description
238
+ app_info = app_data_dict[0] # First (and only) result
239
+ json_data = json.dumps({
240
+ "name": app_info.get("name", "Unknown"),
241
+ "description": app_info.get("description", "No description available")
242
+ })
243
+
244
+ return json_data
245
+
246
+
247
+ def send_bug_report(self, token_data = None, entity_data = None, description = None):
248
+ """
249
+ Sends a bug report via email.
250
+
251
+ Args:
252
+ token_data (dict): Token data to include in the report.
253
+ entity_data (dict): Entity data to include in the report.
254
+ description (str): A description of the bug.
255
+
256
+ Returns:
257
+ bool: True if the report is sent successfully, False otherwise.
258
+ """
259
+
260
+ mail_string = f"""
261
+ BUG REPORT FROM QC-UPLOADER
262
+ \n\n
263
+ token_data: {token_data} \n\n
264
+ entity_data: {entity_data} \n\n
265
+ description: {description} \n\n
266
+ sent_at: {datetime.datetime.now()} \n\n
267
+ """
268
+
269
+ mail = f"""
270
+ echo "{mail_string}" | mail -s "Bug Report" {bfabric_web_apps.BUG_REPORT_EMAIL_ADDRESS}
271
+ """
272
+
273
+ print("MAIL STRING:")
274
+ print(mail_string)
275
+
276
+ print("MAIL:")
277
+ print(mail)
278
+
279
+ os.system(mail)
280
+
281
+ return True
282
+
283
+
284
+
285
+
@@ -1,47 +1,80 @@
1
1
  import os
2
2
  import pickle
3
+ from typing import List
3
4
  from bfabric import Bfabric
4
5
  from datetime import datetime as dt
5
6
  import base64
6
-
7
- try:
8
- from PARAMS import CONFIG_FILE_PATH
9
- except ImportError:
10
- CONFIG_FILE_PATH = "~/.bfabricpy.yml"
7
+ import bfabric_web_apps
11
8
 
12
9
 
13
10
  class Logger:
14
11
  """
15
12
  A Logger class to manage and batch API call logs locally and flush them to the backend when needed.
16
13
  """
17
- def __init__(self, jobid: int, username: str):
14
+ def __init__(self, jobid: int, username: str, environment: str):
15
+ """
16
+ Initializes the Logger with a job ID, username, and environment.
17
+
18
+ Args:
19
+ jobid (int): The ID of the current job.
20
+ username (str): The name of the user performing the operations.
21
+ environment (str): The environment (e.g., Production, Test).
22
+ """
18
23
  self.jobid = jobid
19
24
  self.username = username
20
- self.power_user_wrapper = self._get_power_user_wrapper()
25
+ self.config_file_path = bfabric_web_apps.CONFIG_FILE_PATH
26
+ self.power_user_wrapper = self._get_power_user_wrapper(environment)
21
27
  self.logs = []
22
28
 
23
- def _get_power_user_wrapper(self) -> Bfabric:
29
+ def _get_power_user_wrapper(self, environment) -> Bfabric:
24
30
  """
25
31
  Initializes a B-Fabric wrapper using the power user's credentials.
32
+
33
+ Args:
34
+ environment (str): The environment in which to initialize the wrapper.
35
+
36
+ Returns:
37
+ Bfabric: An authenticated Bfabric instance.
26
38
  """
27
39
  power_user_wrapper = Bfabric.from_config(
28
- config_path=os.path.expanduser(CONFIG_FILE_PATH)
40
+ config_path = os.path.expanduser(self.config_file_path),
41
+ config_env = environment.upper()
29
42
  )
30
43
  return power_user_wrapper
31
44
 
32
45
  def to_pickle(self):
46
+ """
47
+ Serializes the Logger object and encodes it as a base64 string.
48
+
49
+ Returns:
50
+ dict: A dictionary containing the base64-encoded pickle string.
51
+ """
33
52
  # Pickle the object and then encode it as a base64 string
34
53
  return {"data": base64.b64encode(pickle.dumps(self)).decode('utf-8')}
35
54
 
36
55
  @classmethod
37
56
  def from_pickle(cls, pickle_object):
57
+ """
58
+ Deserializes a Logger object from a base64-encoded pickle string.
59
+
60
+ Args:
61
+ pickle_object (dict): A dictionary containing the base64-encoded pickle string.
62
+
63
+ Returns:
64
+ Logger: The deserialized Logger object.
65
+ """
38
66
  # Decode the base64 string back to bytes and then unpickle
39
67
  return pickle.loads(base64.b64decode(pickle_object.get("data").encode('utf-8')))
40
68
 
41
69
  def log_operation(self, operation: str, message: str, params = None, flush_logs: bool = True):
42
70
  """
43
- Log an operation either locally (if flush_logs=False) or flush to the backend.
44
- Creates well-structured, readable log entries.
71
+ Logs an operation locally or flushes it to the backend.
72
+
73
+ Args:
74
+ operation (str): The name of the operation being logged.
75
+ message (str): A detailed message about the operation.
76
+ params (dict, optional): Additional parameters to log. Defaults to None.
77
+ flush_logs (bool, optional): Whether to immediately flush the logs to the backend. Defaults to True.
45
78
  """
46
79
  # Define the timestamp format
47
80
  timestamp = dt.now().strftime('%Y-%m-%d %H:%M:%S')
@@ -83,7 +116,17 @@ class Logger:
83
116
 
84
117
  def logthis(self, api_call: callable, *args, params=None , flush_logs: bool = True, **kwargs) -> any:
85
118
  """
86
- Generic logging function to wrap any API call using a Logger instance.
119
+ Wraps an API call with logging functionality.
120
+
121
+ Args:
122
+ api_call (callable): The API call to be logged and executed.
123
+ *args: Positional arguments to pass to the API call.
124
+ params (dict, optional): Additional parameters to log. Defaults to None.
125
+ flush_logs (bool, optional): Whether to flush logs immediately. Defaults to True.
126
+ **kwargs: Keyword arguments to pass to the API call.
127
+
128
+ Returns:
129
+ any: The result of the API call.
87
130
  """
88
131
  # Construct a message describing the API call
89
132
  call_args = ', '.join([repr(arg) for arg in args])
@@ -0,0 +1,11 @@
1
+ from dash import Dash
2
+ import dash_bootstrap_components as dbc
3
+
4
+ def create_app():
5
+ """Initialize and return a Dash app instance with suppressed callback exceptions."""
6
+ return Dash(
7
+ __name__,
8
+ suppress_callback_exceptions=True, # Allow dynamic callbacks
9
+ external_stylesheets=[dbc.themes.BOOTSTRAP],
10
+ meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1.0"}],
11
+ )
@@ -0,0 +1,170 @@
1
+ from dash import Input, Output, State, html, dcc
2
+ from bfabric_web_apps.objects.BfabricInterface import BfabricInterface
3
+ import json
4
+ import dash_bootstrap_components as dbc
5
+ from datetime import datetime as dt
6
+ from bfabric_web_apps.utils.get_logger import get_logger
7
+
8
+ def process_url_and_token(url_params):
9
+ """
10
+ Processes URL parameters to extract the token, validates it, and retrieves the corresponding data.
11
+ Additionally, it constructs a dynamic job link based on the environment and job ID.
12
+
13
+ Args:
14
+ url_params (str): The URL parameters containing the token.
15
+
16
+ Returns:
17
+ tuple: A tuple containing:
18
+ - token (str): Authentication token.
19
+ - token_data (dict): Token metadata.
20
+ - entity_data (dict): Retrieved entity information.
21
+ - page_title (str): Title for the page header.
22
+ - session_details (list): HTML-formatted session details.
23
+ - job_link (str): Dynamically generated link to the job page.
24
+ """
25
+ base_title = " "
26
+
27
+ if not url_params:
28
+ return None, None, None, None, base_title, None, None
29
+
30
+ token = "".join(url_params.split('token=')[1:])
31
+ bfabric_interface = BfabricInterface()
32
+ tdata_raw = bfabric_interface.token_to_data(token)
33
+
34
+ if tdata_raw:
35
+ if tdata_raw == "EXPIRED":
36
+ return None, None, None, None, base_title, None, None
37
+ else:
38
+ tdata = json.loads(tdata_raw)
39
+ else:
40
+ return None, None, None, None, base_title, None, None
41
+
42
+ if tdata:
43
+ entity_data_json = bfabric_interface.entity_data(tdata)
44
+ app_data_json = bfabric_interface.app_data(tdata)
45
+ entity_data = json.loads(entity_data_json)
46
+ app_data = json.loads(app_data_json)
47
+ page_title = (
48
+ f"{tdata.get('entityClass_data', 'Unknown')} - {entity_data.get('name', 'Unknown')} "
49
+ f"({tdata.get('environment', 'Unknown')} System)"
50
+ ) if tdata else "Bfabric App Interface"
51
+
52
+ environment = tdata.get("environment", "").strip().lower() # 'test' or 'prod'
53
+ job_id = tdata.get("jobId", None) # Extract job ID
54
+
55
+ job_link = None
56
+ if job_id:
57
+ if "test" in environment:
58
+ job_link = f"https://fgcz-bfabric-test.uzh.ch/bfabric/job/show.html?id={job_id}&tab=details"
59
+ else:
60
+ job_link = f"https://fgcz-bfabric.uzh.ch/bfabric/job/show.html?id={job_id}&tab=details"
61
+
62
+ session_details = [
63
+ html.P([
64
+ html.B("Entity Name: "), entity_data.get('name', 'Unknown'),
65
+ html.Br(),
66
+ html.B("Entity Class: "), tdata.get('entityClass_data', 'Unknown'),
67
+ html.Br(),
68
+ html.B("Environment: "), tdata.get('environment', 'Unknown'),
69
+ html.Br(),
70
+ html.B("Entity ID: "), tdata.get('entity_id_data', 'Unknown'),
71
+ html.Br(),
72
+ html.B("Job ID: "), job_id if job_id else "Unknown",
73
+ html.Br(),
74
+ html.B("User Name: "), tdata.get('user_data', 'Unknown'),
75
+ html.Br(),
76
+ html.B("Session Expires: "), tdata.get('token_expires', 'Unknown'),
77
+ html.Br(),
78
+ html.B("App Name: "), app_data.get("name", "Unknown"),
79
+ html.Br(),
80
+ html.B("App Description: "), app_data.get("description", "No description available"),
81
+ html.Br(),
82
+ html.B("Current Time: "), str(dt.now().strftime("%Y-%m-%d %H:%M:%S"))
83
+ ])
84
+ ]
85
+
86
+ return token, tdata, entity_data, app_data, page_title, session_details, job_link
87
+ else:
88
+ return None, None, None, None, base_title, None, None
89
+
90
+
91
+
92
+ def submit_bug_report(n_clicks, bug_description, token, entity_data):
93
+ """
94
+ Submits a bug report based on user input, token, and entity data.
95
+
96
+ Args:
97
+ n_clicks (int): The number of times the submit button has been clicked.
98
+ bug_description (str): The description of the bug provided by the user.
99
+ token (str): The authentication token.
100
+ entity_data (dict): The data related to the current entity.
101
+
102
+ Returns:
103
+ tuple: A tuple containing two boolean values indicating success and failure status of the submission.
104
+ (is_open_success, is_open_failure)
105
+ """
106
+ bfabric_interface = BfabricInterface()
107
+ print("submit bug report", token)
108
+
109
+ # Parse token data if token is provided, otherwise set it to an empty dictionary
110
+ if token:
111
+ token_data = json.loads(bfabric_interface.token_to_data(token))
112
+ else:
113
+ token_data = {}
114
+
115
+ print(token_data)
116
+
117
+ # Extract logging-related information from token_data, with defaults for missing values
118
+ jobId = token_data.get('jobId', None)
119
+ username = token_data.get("user_data", "None")
120
+ environment = token_data.get("environment", "None")
121
+
122
+ # Initialize the logger only if token_data is available
123
+ L = None
124
+ if token_data:
125
+ L = get_logger(token_data)
126
+
127
+ if n_clicks:
128
+ # Log the operation only if the logger is initialized
129
+ if L:
130
+ L.log_operation(
131
+ "bug report",
132
+ "Initiating bug report submission process.",
133
+ params=None,
134
+ flush_logs=False,
135
+ )
136
+ try:
137
+ sending_result = bfabric_interface.send_bug_report(
138
+ token_data, entity_data, bug_description
139
+ )
140
+
141
+ if sending_result:
142
+ if L:
143
+ L.log_operation(
144
+ "bug report",
145
+ f"Bug report successfully submitted. | DESCRIPTION: {bug_description}",
146
+ params=None,
147
+ flush_logs=True,
148
+ )
149
+ return True, False
150
+ else:
151
+ if L:
152
+ L.log_operation(
153
+ "bug report",
154
+ "Failed to submit bug report!",
155
+ params=None,
156
+ flush_logs=True,
157
+ )
158
+ return False, True
159
+ except Exception as e:
160
+ if L:
161
+ L.log_operation(
162
+ "bug report",
163
+ f"Failed to submit bug report! Error: {str(e)}",
164
+ params=None,
165
+ flush_logs=True,
166
+ )
167
+ return False, True
168
+
169
+ return False, False
170
+
@@ -0,0 +1,22 @@
1
+ from dash import html
2
+
3
+ DEVELOPER_EMAIL = "gwhite@fgcz.ethz.ch"
4
+
5
+ expired = [
6
+ html.P("Your session has expired. Please log into bfabric to continue:"),
7
+ html.A('Login to Bfabric', href='https://fgcz-bfabric.uzh.ch/bfabric/')
8
+ ]
9
+
10
+ no_entity = [
11
+ html.P("There was an error fetching the data for your entity. Please try accessing the applicaiton again from bfabric:"),
12
+ html.A('Login to Bfabric', href='https://fgcz-bfabric.uzh.ch/bfabric/')
13
+ ]
14
+
15
+ dev = [html.P("This page is under development. Please check back later."),html.Br(),html.A("email the developer for more details",href="mailto:"+DEVELOPER_EMAIL)]
16
+
17
+ auth = [html.Div(id="auth-div")]
18
+
19
+ no_auth = [
20
+ html.P("You are not currently logged into an active session. Please log into bfabric to continue:"),
21
+ html.A('Login to Bfabric', href='https://fgcz-bfabric.uzh.ch/bfabric/')
22
+ ]
@@ -0,0 +1,86 @@
1
+ from bfabric import Bfabric
2
+
3
+ def get_user_input():
4
+ """Prompt user for necessary inputs."""
5
+
6
+ systems = {"TEST": "TEST", "PROD": "PRODUCTION"}
7
+
8
+ technologies = {
9
+ "TEST": {
10
+ "1": "Genomics / Transcriptomics",
11
+ "2": "Proteomics",
12
+ "4": "Metabolomics / Biophysics",
13
+ "6": "General",
14
+ "10": "New Tech"
15
+ },
16
+ "PRODUCTION": {
17
+ "1": "Genomics / Transcriptomics",
18
+ "2": "Proteomics",
19
+ "4": "Metabolomics / Biophysics",
20
+ "6": "General",
21
+ "10": "Bioinformatics"
22
+ }
23
+ }
24
+
25
+ # Get system input
26
+ system = input("In which system do you want to create the app? (Type 'TEST' for the test system or 'PROD' for the production system): ").strip().upper()
27
+
28
+ while system not in systems:
29
+ print("Invalid input! Please enter 'TEST' or 'PROD'.")
30
+ system = input("Enter system (TEST/PROD): ").strip().upper()
31
+
32
+ selected_system = systems[system] # Map input to full system name
33
+
34
+ # Display technology options based on selected system
35
+ print("\nAvailable Technologies:")
36
+ for key, value in technologies[selected_system].items():
37
+ print(f"{key}: {value}")
38
+
39
+ # Get technology ID from user
40
+ technologyid = input("\nEnter the number corresponding to your chosen application type: ").strip()
41
+
42
+ while technologyid not in technologies[selected_system]:
43
+ print("Invalid technology ID! Please select a valid number from the list.")
44
+ technologyid = input("Enter a valid technology ID: ").strip()
45
+
46
+ # Get remaining inputs
47
+ name = input("Enter app name: ")
48
+ weburl = input("Enter web URL: ")
49
+ description = input("Enter description: ")
50
+
51
+ return {
52
+ "system": selected_system,
53
+ "name": name,
54
+ "weburl": weburl,
55
+ "type": "WebApp",
56
+ "technologyid": technologyid,
57
+ "supervisorid": "2446",
58
+ "enabled": True,
59
+ "valid": True,
60
+ "hidden": False,
61
+ "description": description
62
+ }
63
+
64
+ def create_app_in_bfabric():
65
+ """Create an app in B-Fabric using user inputs."""
66
+ # Get user input for parameters
67
+ user_input = get_user_input()
68
+
69
+ # Determine configuration environment based on user input
70
+ config_env = user_input.pop("system")
71
+
72
+ # Initialize Bfabric instance
73
+ bfabric = Bfabric.from_config(config_env=config_env)
74
+
75
+ # Set endpoint for app creation
76
+ endpoint = "application"
77
+
78
+ # Make API call to save the app
79
+ try:
80
+ result = bfabric.save(endpoint=endpoint, obj=user_input)
81
+ print("App created successfully:", result)
82
+ except Exception as e:
83
+ print("Failed to create app:", str(e))
84
+
85
+ if __name__ == "__main__":
86
+ create_app_in_bfabric()
@@ -0,0 +1,11 @@
1
+ # defaults.py
2
+ CONFIG_FILE_PATH = "~/.bfabricpy.yml"
3
+
4
+ # Default values for application settings
5
+ HOST = "0.0.0.0"
6
+ PORT = 8050
7
+ DEV = False
8
+
9
+ # Developer and bug report email addresses
10
+ DEVELOPER_EMAIL_ADDRESS = "griffin@gwcustom.com"
11
+ BUG_REPORT_EMAIL_ADDRESS = "gwtools@fgcz.system"
@@ -0,0 +1,14 @@
1
+ from bfabric_web_apps.objects.Logger import Logger
2
+
3
+ def get_logger(token_data):
4
+
5
+ """ Extract logging-related information from token_data, with defaults for missing values """
6
+ jobId = token_data.get('jobId', None)
7
+ username = token_data.get("user_data", "None")
8
+ environment = token_data.get("environment", "None")
9
+
10
+ return Logger(
11
+ jobid=jobId,
12
+ username=username,
13
+ environment=environment,
14
+ )
@@ -0,0 +1,27 @@
1
+ import os
2
+ from bfabric import Bfabric
3
+ import bfabric_web_apps
4
+
5
+ def get_power_user_wrapper(token_data):
6
+ """
7
+ Initializes and returns a Bfabric power user instance configured for a specific environment.
8
+
9
+ This function retrieves the environment information from the provided `token_data`
10
+ and uses it to initialize a Bfabric instance. The configuration file path is
11
+ determined by the `CONFIG_FILE_PATH` from the application's configuration.
12
+
13
+ Args:
14
+ token_data (dict): A dictionary containing token information
15
+ The key "environment" is used to determine the environment
16
+ (default is "None" if not specified).
17
+
18
+ Returns:
19
+ Bfabric: A Bfabric instance initialized with the configuration
20
+ corresponding to the specified environment.
21
+ """
22
+ environment = token_data.get("environment", "None")
23
+
24
+ return Bfabric.from_config(
25
+ config_path = os.path.expanduser(bfabric_web_apps.CONFIG_FILE_PATH),
26
+ config_env = environment.upper()
27
+ )
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 GWC GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bfabric-web-apps
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: A package containing handy boilerplate utilities for developing bfabric web-applications
5
- Author: Mark Zuber, Griffin White, GWC GmbH
5
+ Author: Marc Zuber, Griffin White, GWC GmbH
6
6
  Requires-Python: >=3.8,<4.0
7
7
  Classifier: Programming Language :: Python :: 3
8
- Classifier: Programming Language :: Python :: 3.10
9
8
  Classifier: Programming Language :: Python :: 3.8
10
9
  Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
@@ -0,0 +1,15 @@
1
+ bfabric_web_apps/__init__.py,sha256=zZvM0CQPkiu0y9xOZos-d2hK-h6erc8K_nov7vD1bdE,2362
2
+ bfabric_web_apps/layouts/layouts.py,sha256=XoSLcQPcgMBhQz2VfxkUzNL23FLBXFRFvbCL2mNLfnk,11636
3
+ bfabric_web_apps/objects/BfabricInterface.py,sha256=nRdU_cYLW2_xp2x1cvP9b30gQ5blavbOz66B-Hi6UZQ,8980
4
+ bfabric_web_apps/objects/Logger.py,sha256=62LC94xhm7YG5LUw3yH46NqvJQsAX7wnc9D4zbY16rA,5224
5
+ bfabric_web_apps/utils/app_init.py,sha256=RCdpCXp19cF74bouYJLPe-KSETZ0Vwqtd02Ta2VXEF8,428
6
+ bfabric_web_apps/utils/callbacks.py,sha256=PiP1ZJ-QxdrOAZ-Mt-MN-g9wJLSOoLkWkXwPq_TLqDI,6472
7
+ bfabric_web_apps/utils/components.py,sha256=V7ECGmF2XYy5O9ciDJVH1nofJYP2a_ELQF3z3X_ADbo,844
8
+ bfabric_web_apps/utils/create_app_in_bfabric.py,sha256=eVk3cQDXxW-yo9b9n_zzGO6kLg_SLxYbIDECyvEPJXU,2752
9
+ bfabric_web_apps/utils/defaults.py,sha256=B82j3JEbysLEU9JDZgoDBTX7WGvW3Hn5YMZaWAcjZew,278
10
+ bfabric_web_apps/utils/get_logger.py,sha256=0Y3SrXW93--eglS0_ZOc34NOriAt6buFPik5n0ltzRA,434
11
+ bfabric_web_apps/utils/get_power_user_wrapper.py,sha256=T33z64XjmJ0KSlmfEmrEP8eYpbpINCVD6Xld_V7PR2g,1027
12
+ bfabric_web_apps-0.1.2.dist-info/LICENSE,sha256=k0O_i2k13i9e35aO-j7FerJafAqzzu8x0kkBs0OWF3c,1065
13
+ bfabric_web_apps-0.1.2.dist-info/METADATA,sha256=tvvoNH4-tMyPbWJdoe_pVgC_cDzQpDzdGtqd49vt9QI,480
14
+ bfabric_web_apps-0.1.2.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
15
+ bfabric_web_apps-0.1.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry 1.0.7
2
+ Generator: poetry-core 1.7.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
File without changes
@@ -1,8 +0,0 @@
1
- bfabric_web_apps/__init__.py,sha256=6IFMcIq8biYvtMafM-gELPRBq19k5kHRlEYmRTsXlYI,87
2
- bfabric_web_apps/layouts/layouts.py,sha256=6b5_wk2fk1ZLyWhhhKfz2EnsSdZtlPx8aR-pMfPC1t0,135
3
- bfabric_web_apps/objects/BfabricInterface.py,sha256=IU1D1Q6ZdAR0oo64Fpcru9LWVj-3Ydmlti6Vy_-VSj4,275
4
- bfabric_web_apps/objects/Logger.py,sha256=Sikkqfbkp7NGqEkFZ4cHKtjGX1Bu9Y8sf1PWDLOOjco,3467
5
- bfabric_web_apps/objects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- bfabric_web_apps-0.1.0.dist-info/WHEEL,sha256=y3eDiaFVSNTPbgzfNn0nYn5tEn1cX6WrdetDlQM4xWw,83
7
- bfabric_web_apps-0.1.0.dist-info/METADATA,sha256=9GcQM9qWxhYBDHgmq0r0Gu9mAmvvn0Lxy0YJrfFCiPc,429
8
- bfabric_web_apps-0.1.0.dist-info/RECORD,,