BananaSauce commited on
Commit
295a9df
Β·
1 Parent(s): 69a44c9

added auto loader that doesnt work

Browse files
Files changed (6) hide show
  1. app.py +62 -16
  2. environment_loader.py +137 -0
  3. jira_integration.py +73 -99
  4. multiple.py +164 -126
  5. multiple_env_loader.py +120 -0
  6. pre.py +42 -19
app.py CHANGED
@@ -4,9 +4,12 @@ import matplotlib.pyplot as plt
4
  import numpy as np
5
  from second import double_main
6
  from multiple import multiple_main
 
 
7
  from weekly import generate_weekly_report
8
  from pre import preprocess_uploaded_file, add_app_description
9
- from multi_env_compare import multi_env_compare_main
 
10
 
11
 
12
 
@@ -143,33 +146,73 @@ def single_main(uploaded_file):
143
  def main():
144
  add_app_description()
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  # Initialize session state for mode if it doesn't exist
147
  if "mode" not in st.session_state:
148
  st.session_state["mode"] = "multi"
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  # Initialize session state for the selectbox widget
151
  if "selected_mode" not in st.session_state:
152
  st.session_state["selected_mode"] = "Multi"
153
-
154
- # Use the session state for the default value of the selectbox
155
- selected_mode = st.sidebar.selectbox(
156
- "Select Mode",
157
- ["Multi", "Compare", "Weekly", "Multi-Env Compare"],
158
- index=["Multi", "Compare", "Weekly", "Multi-Env Compare"].index(st.session_state["selected_mode"])
159
- )
160
 
161
- # Update the session state with the new selection
162
- st.session_state["selected_mode"] = selected_mode
163
- st.session_state["mode"] = selected_mode.lower()
164
-
165
- mode_display = f'## Current mode: {st.session_state["mode"].title()} mode'
166
- st.sidebar.markdown(mode_display)
167
-
168
  if st.session_state["mode"] == "multi":
169
  multiple_main()
170
  elif st.session_state["mode"] == "compare":
171
  st.sidebar.markdown("### Upload Files for Comparison")
172
- upload_option = st.sidebar.radio("Upload method", ["Single uploader", "Two separate uploaders"])
 
 
173
 
174
  if upload_option == "Single uploader":
175
  uploaded_files = st.sidebar.file_uploader("Upload CSV or XLSX files for comparison", type=["csv", "xlsx"], accept_multiple_files=True)
@@ -201,6 +244,9 @@ def main():
201
  generate_weekly_report(uploaded_files)
202
  elif st.session_state["mode"] == "multi-env compare":
203
  multi_env_compare_main()
 
 
 
204
 
205
  if __name__ == "__main__":
206
  main()
 
4
  import numpy as np
5
  from second import double_main
6
  from multiple import multiple_main
7
+ from multiple import display_story_points_stats
8
+ from jira_integration import render_jira_login, JIRA_SERVER
9
  from weekly import generate_weekly_report
10
  from pre import preprocess_uploaded_file, add_app_description
11
+ from multi_env_compare import multi_env_compare_main
12
+ import multiple_env_loader
13
 
14
 
15
 
 
146
  def main():
147
  add_app_description()
148
 
149
+ # --- Centralized Sidebar Initialization ---
150
+ # Initialize session state for Jira and sprint data if they don't exist
151
+ if 'jira_server' not in st.session_state:
152
+ st.session_state.jira_server = JIRA_SERVER
153
+ if 'is_authenticated' not in st.session_state:
154
+ st.session_state.is_authenticated = False # Start as not authenticated
155
+ if 'jira_client' not in st.session_state:
156
+ st.session_state.jira_client = None
157
+ if 'sprint_data_initialized' not in st.session_state:
158
+ st.session_state.sprint_data_initialized = False
159
+ if 'force_sprint_refresh' not in st.session_state:
160
+ st.session_state.force_sprint_refresh = False
161
+ if 'sprint_data_cache' not in st.session_state:
162
+ st.session_state.sprint_data_cache = None
163
+ if 'last_sprint_fetch' not in st.session_state:
164
+ st.session_state.last_sprint_fetch = None
165
+
166
  # Initialize session state for mode if it doesn't exist
167
  if "mode" not in st.session_state:
168
  st.session_state["mode"] = "multi"
169
 
170
+ # --- Sidebar Rendering ---
171
+ with st.sidebar:
172
+ # Mode Selection (kept in sidebar)
173
+ selected_mode = st.selectbox(
174
+ "Select Mode",
175
+ ["Multi", "Compare", "Weekly", "Multi-Env Compare", "Auto Environment Loader"],
176
+ index=["Multi", "Compare", "Weekly", "Multi-Env Compare", "Auto Environment Loader"].index(st.session_state.get("selected_mode", "Multi"))
177
+ )
178
+ # Update the session state with the new selection
179
+ st.session_state["selected_mode"] = selected_mode
180
+ st.session_state["mode"] = selected_mode.lower()
181
+ mode_display = f'## Current mode: {st.session_state["mode"].title()} mode'
182
+ st.markdown(mode_display)
183
+
184
+ st.markdown("---") # Separator
185
+
186
+ # Jira Login Expander (always shown)
187
+ with st.expander("Jira Integration (Optional)", expanded=True):
188
+ # Render login - function handles checking if already authenticated
189
+ # It updates st.session_state.is_authenticated and st.session_state.jira_client
190
+ st.session_state.is_authenticated = render_jira_login()
191
+
192
+ # Sprint Progress Expander (shown only if authenticated)
193
+ if st.session_state.is_authenticated and st.session_state.jira_client:
194
+ st.markdown("---") # Separator inside the main expander
195
+ with st.expander("Sprint Progress", expanded=True):
196
+ # Refresh button
197
+ if st.button("πŸ”„ Refresh Sprint Data", key="refresh_sprint_sidebar_app"):
198
+ st.session_state.force_sprint_refresh = True
199
+ # Always call display (it handles caching), passing manual refresh flag
200
+ display_story_points_stats(force_refresh=st.session_state.force_sprint_refresh)
201
+ # Reset manual refresh flag after use
202
+ st.session_state.force_sprint_refresh = False
203
+
204
  # Initialize session state for the selectbox widget
205
  if "selected_mode" not in st.session_state:
206
  st.session_state["selected_mode"] = "Multi"
 
 
 
 
 
 
 
207
 
208
+ # --- Main Page Content based on Mode ---
 
 
 
 
 
 
209
  if st.session_state["mode"] == "multi":
210
  multiple_main()
211
  elif st.session_state["mode"] == "compare":
212
  st.sidebar.markdown("### Upload Files for Comparison")
213
+ # Move file uploaders to main page area if needed, or keep in sidebar below Jira?
214
+ # For now, keeping in sidebar as it was.
215
+ upload_option = st.sidebar.radio("Upload method", ["Single uploader", "Two separate uploaders"], key="compare_upload_method")
216
 
217
  if upload_option == "Single uploader":
218
  uploaded_files = st.sidebar.file_uploader("Upload CSV or XLSX files for comparison", type=["csv", "xlsx"], accept_multiple_files=True)
 
244
  generate_weekly_report(uploaded_files)
245
  elif st.session_state["mode"] == "multi-env compare":
246
  multi_env_compare_main()
247
+ elif st.session_state["mode"] == "auto environment loader":
248
+ # Launch the auto environment loader workflow
249
+ multiple_env_loader.main()
250
 
251
  if __name__ == "__main__":
252
  main()
environment_loader.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # environment_loader.py
2
+ '''Utility module to load .xlsx files from environment-specific folders and optional Huggingface repositories.'''
3
+
4
+ import os
5
+ from pathlib import Path
6
+ import pandas as pd
7
+ from datetime import datetime, timedelta
8
+
9
+ # Optional Huggingface support
10
+ try:
11
+ from huggingface_hub import hf_hub_download
12
+ except ImportError:
13
+ hf_hub_download = None
14
+
15
+
16
+ def get_latest_file_in_directory(directory_path: str, pattern: str = '*.xlsx') -> Path:
17
+ '''Return the Path to the latest modified file matching pattern in directory_path.'''
18
+ dir_path = Path(directory_path)
19
+ files = list(dir_path.glob(pattern))
20
+ if not files:
21
+ return None
22
+ return max(files, key=lambda f: f.stat().st_mtime)
23
+
24
+
25
+ def get_file_by_date(directory_path: str, target_date: datetime.date, pattern: str = '*.xlsx') -> Path:
26
+ '''Return Path to the file whose name contains target_date or whose modification date matches target_date.'''
27
+ date_str = target_date.strftime('%Y-%m-%d')
28
+ dir_path = Path(directory_path)
29
+ # First try matching date string in filename
30
+ candidates = [f for f in dir_path.glob(pattern) if date_str in f.name]
31
+ if candidates:
32
+ return max(candidates, key=lambda f: f.stat().st_mtime)
33
+ # Fallback to checking file modification date
34
+ files = [f for f in dir_path.glob(pattern) if datetime.fromtimestamp(f.stat().st_mtime).date() == target_date]
35
+ if files:
36
+ return max(files, key=lambda f: f.stat().st_mtime)
37
+ return None
38
+
39
+
40
+ def load_latest_xlsx_for_env(env_code: str,
41
+ base_path: str = 'Q:/Selenium_Reports',
42
+ use_date: bool = False,
43
+ target_date: datetime.date = None) -> pd.DataFrame:
44
+ '''Load the latest or date-specific .xlsx file for the given environment code.'''
45
+ folder = Path(base_path) / env_code / 'XLSX'
46
+ if not folder.exists():
47
+ raise FileNotFoundError(f"Environment folder not found: {folder}")
48
+ if use_date:
49
+ if target_date is None:
50
+ raise ValueError('target_date must be provided when use_date is True')
51
+ file_path = get_file_by_date(folder, target_date)
52
+ else:
53
+ file_path = get_latest_file_in_directory(folder)
54
+ if file_path is None:
55
+ raise FileNotFoundError(f'No .xlsx files found for environment {env_code} in {folder}')
56
+ return pd.read_excel(file_path)
57
+
58
+
59
+ def load_environments(env_codes: list,
60
+ base_path: str = 'Q:/Selenium_Reports',
61
+ by_date: bool = False,
62
+ days_ago: int = 1) -> dict:
63
+ '''Load DataFrame for each environment code; by_date chooses file from days_ago days before.'''
64
+ data = {}
65
+ target_date = datetime.now().date() - timedelta(days=days_ago)
66
+ for env in env_codes:
67
+ df = load_latest_xlsx_for_env(
68
+ env_code=env,
69
+ base_path=base_path,
70
+ use_date=by_date,
71
+ target_date=target_date
72
+ )
73
+ data[env] = df
74
+ return data
75
+
76
+
77
+ def load_from_huggingface(repo_id: str,
78
+ filenames: list,
79
+ revision: str = 'main') -> dict:
80
+ '''Download files from a Huggingface repo and load as DataFrames.'''
81
+ if hf_hub_download is None:
82
+ raise ImportError('huggingface_hub is not installed. Please pip install huggingface_hub')
83
+ data = {}
84
+ for fname in filenames:
85
+ local_path = hf_hub_download(repo_id=repo_id, filename=fname, revision=revision)
86
+ data[fname] = pd.read_excel(local_path)
87
+ return data
88
+
89
+
90
+ def get_latest_xlsx_path_for_env(env_code: str,
91
+ base_path: str = 'Q:/Selenium_Reports',
92
+ use_date: bool = False,
93
+ target_date: datetime.date = None) -> Path:
94
+ '''Return the Path to the desired .xlsx file for the given environment code without loading.'''
95
+ folder = Path(base_path) / env_code / 'XLSX'
96
+ if not folder.exists():
97
+ raise FileNotFoundError(f"Environment folder not found: {folder}")
98
+ if use_date:
99
+ if target_date is None:
100
+ raise ValueError('target_date must be provided when use_date is True')
101
+ file_path = get_file_by_date(folder, target_date)
102
+ else:
103
+ file_path = get_latest_file_in_directory(folder)
104
+ if file_path is None:
105
+ raise FileNotFoundError(f'No .xlsx files found for environment {env_code} in {folder}')
106
+ return file_path
107
+
108
+
109
+ def get_environments_paths(env_codes: list,
110
+ base_path: str = 'Q:/Selenium_Reports',
111
+ by_date: bool = False,
112
+ days_ago: int = 1) -> dict:
113
+ '''Return file Paths for each environment code; by_date chooses file from days_ago days before.'''
114
+ data = {}
115
+ target_date = datetime.now().date() - timedelta(days=days_ago)
116
+ for env in env_codes:
117
+ path = get_latest_xlsx_path_for_env(
118
+ env_code=env,
119
+ base_path=base_path,
120
+ use_date=by_date,
121
+ target_date=target_date
122
+ )
123
+ data[env] = path
124
+ return data
125
+
126
+
127
+ def get_huggingface_paths(repo_id: str,
128
+ filenames: list,
129
+ revision: str = 'main') -> dict:
130
+ '''Download files from a Huggingface repo and return local Paths without loading.'''
131
+ if hf_hub_download is None:
132
+ raise ImportError('huggingface_hub is not installed. Please pip install huggingface_hub')
133
+ data = {}
134
+ for fname in filenames:
135
+ local_path = hf_hub_download(repo_id=repo_id, filename=fname, revision=revision)
136
+ data[fname] = Path(local_path)
137
+ return data
jira_integration.py CHANGED
@@ -10,7 +10,6 @@ from datetime import datetime, timedelta
10
  import pandas as pd
11
  import requests
12
  import json
13
- from groq import Groq
14
  from difflib import SequenceMatcher
15
  import time
16
 
@@ -48,13 +47,10 @@ load_dotenv()
48
 
49
  # Get API keys and configuration with default values for development
50
  JIRA_SERVER = os.getenv("JIRA_SERVER")
51
- GROQ_API_KEY = os.getenv("GROQ_API_KEY")
52
 
53
  # Validate required environment variables
54
  if not JIRA_SERVER:
55
  st.error("JIRA_SERVER not found in environment variables. Please check your .env file.")
56
- if not GROQ_API_KEY:
57
- st.error("GROQ_API_KEY not found in environment variables. Please check your .env file.")
58
 
59
  def init_jira_session():
60
  """Initialize Jira session state variables"""
@@ -1484,102 +1480,70 @@ def display_story_points_stats(force_refresh=False):
1484
  """Display story points statistics from current sprint"""
1485
  if not st.session_state.jira_client:
1486
  return
1487
-
1488
- # Initialize session state for sprint data if not exists
1489
  if 'sprint_data' not in st.session_state:
1490
  st.session_state.sprint_data = None
1491
-
1492
- # Initialize refresh timestamp if not exists
1493
  if 'last_sprint_refresh' not in st.session_state:
1494
  st.session_state.last_sprint_refresh = None
1495
-
1496
- try:
1497
- # Only fetch data if forced refresh, no data exists, or refresh timestamp is old
1498
- current_time = datetime.now()
1499
- refresh_needed = (
1500
- force_refresh or
1501
- st.session_state.sprint_data is None or
1502
- (st.session_state.last_sprint_refresh and
1503
- (current_time - st.session_state.last_sprint_refresh).total_seconds() > 300) # 5 minutes cache
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1504
  )
1505
-
1506
- if refresh_needed:
1507
- with st.spinner("Fetching sprint data..."):
1508
- # Get regression board
1509
- board = get_regression_board("RS")
1510
- if not board:
1511
- return
1512
-
1513
- # Get current sprint
1514
- sprint = get_current_sprint(board['id'])
1515
- if not sprint:
1516
- return
1517
-
1518
- # Get sprint issues
1519
- issues = get_sprint_issues(board['id'], sprint.id, board['estimation_field'])
1520
- if not issues:
1521
- return
1522
-
1523
- # Calculate points
1524
- issues_data, total_points, completed_points, in_progress_points = calculate_points(issues, board['estimation_field'])
1525
-
1526
- # Store in session state
1527
- st.session_state.sprint_data = {
1528
- 'sprint_name': sprint.name,
1529
- 'total_points': total_points,
1530
- 'completed_points': completed_points,
1531
- 'in_progress_points': in_progress_points,
1532
- 'timestamp': current_time
1533
- }
1534
- st.session_state.last_sprint_refresh = current_time
1535
-
1536
- # Display data from session state
1537
- if st.session_state.sprint_data:
1538
- sprint_data = st.session_state.sprint_data
1539
-
1540
- # Create compact metrics display using custom HTML/CSS
1541
- st.markdown(f"""
1542
- <div style='background-color: #1E1E1E; padding: 10px; border-radius: 5px; margin-bottom: 10px;'>
1543
- <div style='font-size: 0.8em; color: #E0E0E0; margin-bottom: 8px;'>Current Sprint: {sprint_data['sprint_name']}</div>
1544
- <div style='display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; font-size: 0.9em;'>
1545
- <div style='text-align: center;'>
1546
- <div style='color: #E0E0E0;'>Total</div>
1547
- <div style='font-size: 1.2em; font-weight: bold;'>{sprint_data['total_points']:.1f}</div>
1548
- </div>
1549
- <div style='text-align: center;'>
1550
- <div style='color: #E0E0E0;'>Done</div>
1551
- <div style='font-size: 1.2em; font-weight: bold;'>{sprint_data['completed_points']:.1f}</div>
1552
- </div>
1553
- <div style='text-align: center;'>
1554
- <div style='color: #E0E0E0;'>In Progress</div>
1555
- <div style='font-size: 1.2em; font-weight: bold;'>{sprint_data['in_progress_points']:.1f}</div>
1556
- </div>
1557
- <div style='text-align: center;'>
1558
- <div style='color: #E0E0E0;'>Complete</div>
1559
- <div style='font-size: 1.2em; font-weight: bold;'>{(sprint_data['completed_points'] / sprint_data['total_points'] * 100) if sprint_data['total_points'] > 0 else 0:.1f}%</div>
1560
- </div>
1561
- </div>
1562
- </div>
1563
- """, unsafe_allow_html=True)
1564
-
1565
- # Show progress bar
1566
- progress = sprint_data['completed_points'] / sprint_data['total_points'] if sprint_data['total_points'] > 0 else 0
1567
- st.progress(progress)
1568
-
1569
- # Add refresh button with key based on timestamp to prevent rerendering
1570
- refresh_key = f"refresh_stats_{datetime.now().strftime('%Y%m%d%H%M%S')}"
1571
- if st.button("πŸ”„ Refresh", key=refresh_key, use_container_width=True):
1572
- # Use a session state flag to trigger refresh on next rerun
1573
- st.session_state.force_sprint_refresh = True
1574
- st.rerun()
1575
-
1576
- except Exception as e:
1577
- st.error(f"Error updating story points: {str(e)}")
1578
-
1579
- # Check if we need to force refresh (from button click)
1580
- if 'force_sprint_refresh' in st.session_state and st.session_state.force_sprint_refresh:
1581
- st.session_state.force_sprint_refresh = False
1582
- return display_story_points_stats(force_refresh=True)
1583
 
1584
  def main():
1585
  st.title("Jira Integration Test")
@@ -1588,10 +1552,20 @@ def main():
1588
  if st.button("Load Test Data"):
1589
  st.session_state.filtered_scenarios_df = create_test_data()
1590
  st.success("Test data loaded!")
1591
-
1592
- is_authenticated = render_jira_login()
1593
-
1594
- if is_authenticated and st.session_state.projects:
 
 
 
 
 
 
 
 
 
 
1595
  # Fixed project and board selection
1596
  project_key = "RS"
1597
  board_type = "scrum"
 
10
  import pandas as pd
11
  import requests
12
  import json
 
13
  from difflib import SequenceMatcher
14
  import time
15
 
 
47
 
48
  # Get API keys and configuration with default values for development
49
  JIRA_SERVER = os.getenv("JIRA_SERVER")
 
50
 
51
  # Validate required environment variables
52
  if not JIRA_SERVER:
53
  st.error("JIRA_SERVER not found in environment variables. Please check your .env file.")
 
 
54
 
55
  def init_jira_session():
56
  """Initialize Jira session state variables"""
 
1480
  """Display story points statistics from current sprint"""
1481
  if not st.session_state.jira_client:
1482
  return
1483
+
1484
+ # Initialize sprint data cache
1485
  if 'sprint_data' not in st.session_state:
1486
  st.session_state.sprint_data = None
 
 
1487
  if 'last_sprint_refresh' not in st.session_state:
1488
  st.session_state.last_sprint_refresh = None
1489
+
1490
+ current_time = datetime.now()
1491
+ cache_expiry = 300 # 5 minutes
1492
+ # Determine if a data refresh is needed
1493
+ refresh_needed = (
1494
+ force_refresh
1495
+ or st.session_state.sprint_data is None
1496
+ or (st.session_state.last_sprint_refresh
1497
+ and (current_time - st.session_state.last_sprint_refresh).total_seconds() > cache_expiry)
1498
+ )
1499
+
1500
+ if refresh_needed:
1501
+ # Only show spinner when fetching new data
1502
+ with st.spinner("Fetching sprint data..."):
1503
+ board = get_regression_board("RS")
1504
+ if not board:
1505
+ return
1506
+ sprint = get_current_sprint(board['id'])
1507
+ if not sprint:
1508
+ return
1509
+ issues = get_sprint_issues(board['id'], sprint.id, board['estimation_field'])
1510
+ if not issues:
1511
+ return
1512
+ # Calculate story points
1513
+ _, total_points, completed_points, in_progress_points = calculate_points(
1514
+ issues, board['estimation_field']
1515
+ )
1516
+ # Cache results
1517
+ st.session_state.sprint_data = {
1518
+ 'sprint_name': sprint.name,
1519
+ 'total_points': total_points,
1520
+ 'completed_points': completed_points,
1521
+ 'in_progress_points': in_progress_points
1522
+ }
1523
+ st.session_state.last_sprint_refresh = current_time
1524
+
1525
+ # Display cached sprint data
1526
+ if st.session_state.sprint_data:
1527
+ sprint_data = st.session_state.sprint_data
1528
+ cols = st.columns(4)
1529
+ with cols[0]:
1530
+ st.metric("Total", f"{sprint_data['total_points']:.1f}")
1531
+ with cols[1]:
1532
+ st.metric("Done", f"{sprint_data['completed_points']:.1f}")
1533
+ with cols[2]:
1534
+ st.metric("In Progress", f"{sprint_data['in_progress_points']:.1f}")
1535
+ with cols[3]:
1536
+ completion_rate = (
1537
+ sprint_data['completed_points'] / sprint_data['total_points'] * 100
1538
+ if sprint_data['total_points'] > 0 else 0
1539
+ )
1540
+ st.metric("Complete", f"{completion_rate:.1f}%")
1541
+ # Show progress bar
1542
+ progress = (
1543
+ sprint_data['completed_points'] / sprint_data['total_points']
1544
+ if sprint_data['total_points'] > 0 else 0
1545
  )
1546
+ st.progress(progress)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1547
 
1548
  def main():
1549
  st.title("Jira Integration Test")
 
1552
  if st.button("Load Test Data"):
1553
  st.session_state.filtered_scenarios_df = create_test_data()
1554
  st.success("Test data loaded!")
1555
+
1556
+ # Sidebar: Jira login control
1557
+ with st.sidebar:
1558
+ if 'is_authenticated' not in st.session_state:
1559
+ st.session_state.is_authenticated = False
1560
+ if st.session_state.is_authenticated and 'jira_client' in st.session_state and st.session_state.jira_client:
1561
+ st.success("Connected to Jira")
1562
+ else:
1563
+ if st.button("Connect to Jira"):
1564
+ is_authenticated = render_jira_login()
1565
+ st.session_state.is_authenticated = is_authenticated
1566
+ st.experimental_rerun()
1567
+
1568
+ if st.session_state.get('is_authenticated', False) and st.session_state.get('projects'):
1569
  # Fixed project and board selection
1570
  project_key = "RS"
1571
  board_type = "scrum"
multiple.py CHANGED
@@ -25,6 +25,29 @@ import os
25
  from dotenv import load_dotenv
26
  import json
27
  import logging
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  load_dotenv()
29
  JIRA_SERVER = os.getenv("JIRA_SERVER")
30
  # Initialize session state variables
@@ -237,21 +260,18 @@ def perform_analysis(uploaded_dataframes):
237
  grouped_filtered_scenarios.index = range(1, len(grouped_filtered_scenarios) + 1)
238
  st.dataframe(grouped_filtered_scenarios)
239
 
240
- # Show task creation button if:
241
- # 1. User is authenticated
242
- # 2. Status is Failed
243
- # 3. Exactly one functional area is selected (not "All")
244
- if ('jira_client' in st.session_state and
245
  st.session_state.jira_client and
246
  selected_status == 'Failed' and
247
  len(selected_functional_areas) == 1 and
248
- "All" not in selected_functional_areas):
249
-
250
- # If we have a recently created task, show the success message first
 
251
  if st.session_state.show_success and st.session_state.last_task_key:
252
  st.success("βœ… Task created successfully!")
253
-
254
- # Display task link in a more prominent way
255
  st.markdown(
256
  f"""
257
  <div style='padding: 10px; border-radius: 5px; border: 1px solid #90EE90; margin: 10px 0;'>
@@ -266,46 +286,42 @@ def perform_analysis(uploaded_dataframes):
266
  """,
267
  unsafe_allow_html=True
268
  )
269
-
270
- # Add a button to create another task
271
- col1, col2, col3 = st.columns([1, 2, 1])
272
- with col2:
273
- if st.button("Create Another Task", key="create_another", use_container_width=True):
274
- # Clear all task-related state
275
- st.session_state.task_content = None
276
- st.session_state.last_task_key = None
277
- st.session_state.last_task_url = None
278
- st.session_state.show_success = False
279
- st.rerun()
280
  else:
281
- environment = filtered_scenarios['Environment'].iloc[0]
282
- # Create columns for compact layout
283
- col1, col2, col3 = st.columns([1, 2, 1])
284
- with col2:
285
- if st.button("πŸ“ Log Jira Task", use_container_width=True):
286
- st.write("Debug: Button clicked") # Debug line
287
- # Use the properly structured DataFrame for task creation
288
- task_df = grouped_filtered_scenarios.copy()
289
- expected_columns = [
290
- 'Environment',
291
- 'Functional area',
292
- 'Scenario Name',
293
- 'Error Message',
294
- 'Failed Step',
295
- 'Time spent(m:s)',
296
- 'Start datetime'
297
- ]
298
- missing_columns = [col for col in expected_columns if col not in task_df.columns]
299
- if missing_columns:
300
- st.error(f"Missing required columns: {', '.join(missing_columns)}")
301
- st.error("Please ensure your data includes all required columns")
302
- return
303
-
304
- # Generate task content
305
- summary, description = generate_task_content(task_df)
306
- if summary and description:
307
- # Call the task creation function
308
- handle_task_button_click(summary, description, environment, task_df)
 
309
 
310
  # Check if selected_status is 'Failed' and show bar graph
311
  if selected_status != 'Passed':
@@ -329,8 +345,19 @@ def perform_analysis(uploaded_dataframes):
329
  # Display individual numbers on y-axis
330
  for bar in bars:
331
  height = bar.get_height()
332
- plt.text(bar.get_x() + bar.get_width() / 2, height, str(int(height)),
333
- ha='center', va='bottom') # Reduce font size of individual numbers
 
 
 
 
 
 
 
 
 
 
 
334
 
335
  plt.tight_layout() # Add this line to adjust layout
336
  st.pyplot(plt)
@@ -339,62 +366,103 @@ def perform_analysis(uploaded_dataframes):
339
  pass
340
 
341
  def display_story_points_stats(force_refresh=False):
342
- """Display story points statistics from current sprint"""
343
  if not st.session_state.jira_client:
344
  return
345
-
346
- try:
347
- with st.spinner("Fetching sprint data..."):
348
- # Get regression board
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  board = get_regression_board("RS")
350
  if not board:
351
  return
352
-
353
- # Get current sprint
354
  sprint = get_current_sprint(board['id'])
355
  if not sprint:
356
  return
357
-
358
- # Get sprint issues
359
  issues = get_sprint_issues(board['id'], sprint.id, board['estimation_field'])
360
  if not issues:
361
  return
362
-
363
- # Calculate points
364
- issues_data, total_points, completed_points, in_progress_points = calculate_points(issues, board['estimation_field'])
365
-
366
- # Update session state
367
- st.session_state.total_story_points = total_points
368
- st.session_state.completed_points = completed_points
369
-
370
- # Create compact metrics display
371
- metrics_container = st.container()
372
- with metrics_container:
373
- # Show sprint info
374
- st.info(f"Current Sprint: {sprint.name}")
375
-
376
- # Show metrics in a compact format
377
- cols = st.columns(4)
378
- with cols[0]:
379
- st.metric("Total", f"{total_points:.1f}")
380
- with cols[1]:
381
- st.metric("Done", f"{completed_points:.1f}")
382
- with cols[2]:
383
- st.metric("In Progress", f"{in_progress_points:.1f}")
384
- with cols[3]:
385
- completion_rate = (completed_points / total_points * 100) if total_points > 0 else 0
386
- st.metric("Complete", f"{completion_rate:.1f}%")
387
-
388
- # Show progress bar
389
- progress = completed_points / total_points if total_points > 0 else 0
390
- st.progress(progress)
391
-
392
- # Add refresh button
393
- if st.button("πŸ”„ Refresh", key="refresh_stats", use_container_width=True):
394
- st.session_state.last_refresh = datetime.now()
395
- return
396
- except Exception as e:
397
- st.error(f"Error updating story points: {str(e)}")
 
 
 
 
 
 
 
 
 
398
 
399
  def show_task_creation_section(filtered_df, environment):
400
  """Display the task creation section with detailed functional area mapping information."""
@@ -475,39 +543,9 @@ def multiple_main():
475
  if 'filtered_scenarios_df' not in st.session_state:
476
  st.session_state.filtered_scenarios_df = None
477
 
478
- if 'jira_server' not in st.session_state:
479
- st.session_state.jira_server = JIRA_SERVER
480
- # Initialize session state for sprint data if not exists
481
  if 'sprint_data_initialized' not in st.session_state:
482
  st.session_state.sprint_data_initialized = False
483
 
484
- # Add Jira login to sidebar (only once)
485
- with st.sidebar:
486
- st.subheader("Jira Integration (Optional)")
487
-
488
- # Only render login if not already authenticated
489
- if 'is_authenticated' not in st.session_state:
490
- st.session_state.is_authenticated = render_jira_login()
491
- else:
492
- # Just display the status without re-rendering the login
493
- if st.session_state.is_authenticated:
494
- st.success("Connected to Jira")
495
- else:
496
- # Allow re-login if not authenticated
497
- st.session_state.is_authenticated = render_jira_login()
498
-
499
- # Only show story points in sidebar if authenticated
500
- if st.session_state.is_authenticated and st.session_state.jira_client:
501
- st.markdown("---")
502
- st.subheader("Sprint Progress")
503
-
504
- # Only fetch sprint data once or when refresh is clicked
505
- if not st.session_state.sprint_data_initialized:
506
- display_story_points_stats(force_refresh=True)
507
- st.session_state.sprint_data_initialized = True
508
- else:
509
- display_story_points_stats(force_refresh=False)
510
-
511
  st.title("Multiple File Analysis")
512
 
513
  # Initialize session state for uploaded data
 
25
  from dotenv import load_dotenv
26
  import json
27
  import logging
28
+
29
+ # Inject CSS to shrink metric font sizes and padding to prevent ellipsis overflow
30
+ if __name__ == "__main__":
31
+ st.markdown("""
32
+ <style>
33
+ [data-testid="metric-container"] {
34
+ padding: 0.25rem 0.5rem !important;
35
+ min-width: 80px !important;
36
+ overflow: visible !important;
37
+ }
38
+ [data-testid="metric-container"] div {
39
+ white-space: nowrap !important;
40
+ text-overflow: clip !important;
41
+ }
42
+ [data-testid="metric-value"] {
43
+ font-size: 0.8rem !important;
44
+ }
45
+ [data-testid="metric-label"] {
46
+ font-size: 0.6rem !important;
47
+ }
48
+ </style>
49
+ """, unsafe_allow_html=True)
50
+
51
  load_dotenv()
52
  JIRA_SERVER = os.getenv("JIRA_SERVER")
53
  # Initialize session state variables
 
260
  grouped_filtered_scenarios.index = range(1, len(grouped_filtered_scenarios) + 1)
261
  st.dataframe(grouped_filtered_scenarios)
262
 
263
+ # Task creation section: always show button placeholder with tooltip, enabling only when conditions are met
264
+ can_create_task = (
265
+ 'jira_client' in st.session_state and
 
 
266
  st.session_state.jira_client and
267
  selected_status == 'Failed' and
268
  len(selected_functional_areas) == 1 and
269
+ "All" not in selected_functional_areas
270
+ )
271
+ col1, col2, col3 = st.columns([1, 2, 1])
272
+ with col2:
273
  if st.session_state.show_success and st.session_state.last_task_key:
274
  st.success("βœ… Task created successfully!")
 
 
275
  st.markdown(
276
  f"""
277
  <div style='padding: 10px; border-radius: 5px; border: 1px solid #90EE90; margin: 10px 0;'>
 
286
  """,
287
  unsafe_allow_html=True
288
  )
289
+ if st.button("Create Another Task", key="create_another", use_container_width=True):
290
+ st.session_state.task_content = None
291
+ st.session_state.last_task_key = None
292
+ st.session_state.last_task_url = None
293
+ st.session_state.show_success = False
294
+ st.rerun()
 
 
 
 
 
295
  else:
296
+ help_text = (
297
+ "Requires: Jira login, 'Failed' status selected, "
298
+ "and exactly one functional area (not 'All')."
299
+ )
300
+ if st.button(
301
+ "πŸ“ Log Jira Task",
302
+ disabled=not can_create_task,
303
+ use_container_width=True,
304
+ help=help_text
305
+ ) and can_create_task:
306
+ environment = filtered_scenarios['Environment'].iloc[0]
307
+ task_df = grouped_filtered_scenarios.copy()
308
+ expected_columns = [
309
+ 'Environment',
310
+ 'Functional area',
311
+ 'Scenario Name',
312
+ 'Error Message',
313
+ 'Failed Step',
314
+ 'Time spent(m:s)',
315
+ 'Start datetime'
316
+ ]
317
+ missing_columns = [col for col in expected_columns if col not in task_df.columns]
318
+ if missing_columns:
319
+ st.error(f"Missing required columns: {', '.join(missing_columns)}")
320
+ st.error("Please ensure your data includes all required columns")
321
+ return
322
+ summary, description = generate_task_content(task_df)
323
+ if summary and description:
324
+ handle_task_button_click(summary, description, environment, task_df)
325
 
326
  # Check if selected_status is 'Failed' and show bar graph
327
  if selected_status != 'Passed':
 
345
  # Display individual numbers on y-axis
346
  for bar in bars:
347
  height = bar.get_height()
348
+ # Annotate bar height, defaulting to 0 if conversion fails
349
+ try:
350
+ # Ensure numeric conversion in case of string 'NaN'
351
+ h_int = int(float(height))
352
+ except Exception:
353
+ h_int = 0
354
+ plt.text(
355
+ bar.get_x() + bar.get_width() / 2,
356
+ height,
357
+ str(h_int),
358
+ ha='center',
359
+ va='bottom'
360
+ ) # Reduce font size of individual numbers
361
 
362
  plt.tight_layout() # Add this line to adjust layout
363
  st.pyplot(plt)
 
366
  pass
367
 
368
  def display_story_points_stats(force_refresh=False):
369
+ """Display story points statistics from current sprint with caching"""
370
  if not st.session_state.jira_client:
371
  return
372
+
373
+ # Initialize cache
374
+ if 'sprint_data_cache' not in st.session_state:
375
+ st.session_state.sprint_data_cache = None
376
+ if 'last_sprint_fetch' not in st.session_state:
377
+ st.session_state.last_sprint_fetch = None
378
+
379
+ now = datetime.now()
380
+ cache_expiry = 300 # 5 minutes
381
+ refresh_needed = (
382
+ force_refresh
383
+ or st.session_state.sprint_data_cache is None
384
+ or (st.session_state.last_sprint_fetch
385
+ and (now - st.session_state.last_sprint_fetch).total_seconds() > cache_expiry)
386
+ )
387
+
388
+ if refresh_needed:
389
+ if force_refresh:
390
+ with st.spinner("Fetching sprint data..."):
391
+ board = get_regression_board("RS")
392
+ if not board:
393
+ return
394
+ sprint = get_current_sprint(board['id'])
395
+ if not sprint:
396
+ return
397
+ issues = get_sprint_issues(board['id'], sprint.id, board['estimation_field'])
398
+ if not issues:
399
+ return
400
+ _, total_points, completed_points, in_progress_points = calculate_points(
401
+ issues, board['estimation_field']
402
+ )
403
+ st.session_state.sprint_data_cache = {
404
+ 'sprint_name': sprint.name,
405
+ 'total_points': total_points,
406
+ 'completed_points': completed_points,
407
+ 'in_progress_points': in_progress_points
408
+ }
409
+ st.session_state.last_sprint_fetch = now
410
+ else:
411
+ # Fetch data silently without spinner
412
  board = get_regression_board("RS")
413
  if not board:
414
  return
 
 
415
  sprint = get_current_sprint(board['id'])
416
  if not sprint:
417
  return
 
 
418
  issues = get_sprint_issues(board['id'], sprint.id, board['estimation_field'])
419
  if not issues:
420
  return
421
+ _, total_points, completed_points, in_progress_points = calculate_points(
422
+ issues, board['estimation_field']
423
+ )
424
+ st.session_state.sprint_data_cache = {
425
+ 'sprint_name': sprint.name,
426
+ 'total_points': total_points,
427
+ 'completed_points': completed_points,
428
+ 'in_progress_points': in_progress_points
429
+ }
430
+ st.session_state.last_sprint_fetch = now
431
+
432
+ # Display cached sprint data
433
+ if st.session_state.sprint_data_cache:
434
+ sprint_data = st.session_state.sprint_data_cache
435
+
436
+ # Use markdown with custom HTML for a compact, non-truncating display
437
+ metrics_html = f"""
438
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; text-align: center; font-size: 0.8rem;">
439
+ <div>
440
+ <div style="color: #888;">Total</div>
441
+ <div style="font-size: 1rem; font-weight: bold;">{sprint_data['total_points']:.1f}</div>
442
+ </div>
443
+ <div>
444
+ <div style="color: #888;">Done</div>
445
+ <div style="font-size: 1rem; font-weight: bold;">{sprint_data['completed_points']:.1f}</div>
446
+ </div>
447
+ <div>
448
+ <div style="color: #888;">In Progress</div>
449
+ <div style="font-size: 1rem; font-weight: bold;">{sprint_data['in_progress_points']:.1f}</div>
450
+ </div>
451
+ <div>
452
+ <div style="color: #888;">Complete</div>
453
+ <div style="font-size: 1rem; font-weight: bold;">{(
454
+ sprint_data['completed_points'] / sprint_data['total_points'] * 100
455
+ if sprint_data['total_points'] > 0 else 0
456
+ ):.1f}%</div>
457
+ </div>
458
+ </div>
459
+ """
460
+ st.markdown(metrics_html, unsafe_allow_html=True)
461
+
462
+ st.progress(
463
+ sprint_data['completed_points'] / sprint_data['total_points']
464
+ if sprint_data['total_points'] > 0 else 0
465
+ )
466
 
467
  def show_task_creation_section(filtered_df, environment):
468
  """Display the task creation section with detailed functional area mapping information."""
 
543
  if 'filtered_scenarios_df' not in st.session_state:
544
  st.session_state.filtered_scenarios_df = None
545
 
 
 
 
546
  if 'sprint_data_initialized' not in st.session_state:
547
  st.session_state.sprint_data_initialized = False
548
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
  st.title("Multiple File Analysis")
550
 
551
  # Initialize session state for uploaded data
multiple_env_loader.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use default Streamlit page configuration
2
+ import streamlit as st
3
+
4
+ # Configure the Streamlit app to use a wide layout for maximum content width
5
+ st.set_page_config(layout="wide")
6
+
7
+ def main():
8
+ from pathlib import Path
9
+ from datetime import datetime, timedelta
10
+ from pre import preprocess_uploaded_file
11
+ from environment_loader import get_environments_paths
12
+ from multiple import perform_analysis
13
+
14
+ # Sidebar: Environment selection and loading options
15
+ st.sidebar.header("Auto Environment Loader")
16
+ base_path = st.sidebar.text_input("Base folder path", "Q:/Selenium_Reports")
17
+
18
+ # Dynamically discover available environment folders
19
+ env_root = Path(base_path)
20
+ try:
21
+ env_codes = [p.name for p in env_root.iterdir() if p.is_dir()]
22
+ except Exception:
23
+ env_codes = []
24
+ st.sidebar.error(f"Could not list environments under {base_path}")
25
+
26
+ selected_envs = st.sidebar.multiselect("Select environments to load", env_codes)
27
+ # For envs ending with 'FIN', let user pick one of the 3 most recent files
28
+ version_selection = {}
29
+ for env in selected_envs:
30
+ # Only FIN environments get manual version selection
31
+ if env.upper().endswith('FIN'):
32
+ folder = Path(base_path) / env / 'XLSX'
33
+ if not folder.exists():
34
+ st.sidebar.warning(f"No folder found for environment {env}: {folder}")
35
+ continue
36
+ # List the 3 most recent files
37
+ recent_files = sorted(folder.glob('*.xlsx'), key=lambda f: f.stat().st_mtime, reverse=True)[:3]
38
+ if not recent_files:
39
+ st.sidebar.warning(f"No .xlsx files found for environment {env}")
40
+ continue
41
+ labels = [f.name for f in recent_files]
42
+ choice = st.sidebar.selectbox(f"Select version for {env}", labels, index=0, key=f"select_{env}")
43
+ version_selection[env] = folder / choice
44
+
45
+ # Initialize session state for incremental loading
46
+ if 'loaded_envs' not in st.session_state:
47
+ st.session_state.loaded_envs = []
48
+ if 'uploaded_env_data' not in st.session_state:
49
+ st.session_state.uploaded_env_data = {}
50
+
51
+ if st.sidebar.button("Load Latest Files", use_container_width=True):
52
+ if not selected_envs:
53
+ st.sidebar.warning("Please select at least one environment to load.")
54
+ else:
55
+ # Determine which environments are new
56
+ new_envs = [env for env in selected_envs if env not in st.session_state.loaded_envs]
57
+ if not new_envs:
58
+ st.sidebar.info("βœ… All selected environments are already loaded.")
59
+ return
60
+ # Build paths: manual picks for FIN envs, auto for others
61
+ paths = {}
62
+ missing = []
63
+ fin_envs = [env for env in new_envs if env.upper().endswith('FIN')]
64
+ non_fin = [env for env in new_envs if not env.upper().endswith('FIN')]
65
+ # FIN envs must have manual selection
66
+ for env in fin_envs:
67
+ path = version_selection.get(env)
68
+ if not path:
69
+ missing.append(env)
70
+ else:
71
+ paths[env] = path
72
+ if missing:
73
+ st.sidebar.error(f"Please select a file version for: {', '.join(missing)}")
74
+ return
75
+ # Auto-load latest for non-FIN envs
76
+ if non_fin:
77
+ auto_paths = get_environments_paths(non_fin, base_path=base_path)
78
+ paths.update(auto_paths)
79
+ try:
80
+ dfs = []
81
+ loaded = []
82
+ failed = []
83
+ # Preprocess each new file, tracking success/failure
84
+ for env, path in paths.items():
85
+ try:
86
+ df = preprocess_uploaded_file(path)
87
+ dfs.append(df)
88
+ loaded.append((env, path))
89
+ except Exception as e:
90
+ failed.append((env, path, e))
91
+
92
+ # Show ticks for successful and failed loads
93
+ if loaded:
94
+ st.sidebar.markdown("**βœ… Successfully loaded:**")
95
+ for env, path in loaded:
96
+ st.sidebar.markdown(f"- βœ… {env}: `{path}`")
97
+ if failed:
98
+ st.sidebar.markdown("**❌ Failed to load:**")
99
+ for env, path, e in failed:
100
+ st.sidebar.markdown(f"- ❌ {env}: `{path}` ({e})")
101
+
102
+ # Update session state with new loaded data
103
+ for (env, _), df in zip(loaded, dfs):
104
+ st.session_state.uploaded_env_data[env] = df
105
+ st.session_state.loaded_envs.append(env)
106
+ # Build combined list of DataFrames for analysis
107
+ st.session_state.uploaded_data = list(st.session_state.uploaded_env_data.values())
108
+ except Exception as e:
109
+ st.sidebar.error(str(e))
110
+
111
+ # Main: run analysis if data is loaded
112
+ st.title("Multiple File Analysis (Auto Env Loader)")
113
+ if 'uploaded_data' in st.session_state and st.session_state.uploaded_data:
114
+ perform_analysis(st.session_state.uploaded_data)
115
+ else:
116
+ st.info("Use the sidebar to select environments and load their latest files for analysis.")
117
+
118
+
119
+ if __name__ == "__main__":
120
+ main()
pre.py CHANGED
@@ -43,18 +43,28 @@ def preprocess_xlsx(uploaded_file):
43
  'Failed Scenario': 'string'
44
  }
45
 
46
- # Read both the first sheet for error messages and "Time Taken" sheet
47
- excel_file = pd.ExcelFile(uploaded_file, engine='openpyxl')
48
-
49
- # Read detailed step data from first sheet (contains error messages)
50
- error_df = pd.read_excel(excel_file, sheet_name=0)
51
-
52
- # Read time taken data from the "Time Taken" sheet
53
- df = pd.read_excel(
54
- excel_file,
55
- sheet_name='Time Taken',
56
- dtype=dtype_dict
57
- )
 
 
 
 
 
 
 
 
 
 
58
 
59
  # Print column names and sample values for debugging
60
  # st.write("Excel columns:", df.columns.tolist())
@@ -66,9 +76,13 @@ def preprocess_xlsx(uploaded_file):
66
  # empty_features = df['Feature Name'].isna().sum()
67
  # st.write(f"Empty Feature Names: {empty_features}")
68
 
69
- # Convert Failed Scenario column to boolean after reading
70
- # Handle different possible values (TRUE/FALSE, True/False, etc.)
71
  df['Failed Scenario'] = df['Failed Scenario'].astype(str).str.upper()
 
 
 
 
72
  df['Status'] = df['Failed Scenario'].map(
73
  lambda x: 'FAILED' if x in ['TRUE', 'YES', 'Y', '1'] else 'PASSED'
74
  )
@@ -132,8 +146,9 @@ def preprocess_xlsx(uploaded_file):
132
  # Add environment column
133
  result_df['Environment'] = environment
134
 
135
- # Calculate formatted time spent
136
- result_df['Time spent(m:s)'] = pd.to_datetime(result_df['Time spent'], unit='s').dt.strftime('%M:%S')
 
137
 
138
 
139
  result_df['Start datetime'] = pd.to_datetime(file_date)
@@ -154,8 +169,14 @@ def preprocess_xlsx(uploaded_file):
154
  result_df = result_df.merge(start_times, on='Scenario Name', how='left')
155
  result_df.rename(columns={'Time Stamp': 'Scenario Start Time'}, inplace=True)
156
  scenario_start_times = result_df['Scenario Start Time']
157
- # Combine the date from the filename with the time stamp
158
- result_df['Start datetime'] = pd.to_datetime(scenario_start_times.dt.strftime('%H:%M:%S') + ' ' + file_date.strftime('%Y-%m-%d'))
 
 
 
 
 
 
159
 
160
  # Print counts for debugging
161
  # st.write(f"Processed data - Failed: {len(result_df[result_df['Status'] == 'FAILED'])}, Passed: {len(result_df[result_df['Status'] == 'PASSED'])}")
@@ -194,7 +215,9 @@ def preprocess_uploaded_file(uploaded_file):
194
  data['Start datetime'] = pd.to_datetime(data['Start datetime'], dayfirst=True, errors='coerce')
195
  data['End datetime'] = pd.to_datetime(data['End datetime'], dayfirst=True, errors='coerce')
196
  data['Time spent'] = (data['End datetime'] - data['Start datetime']).dt.total_seconds()
197
- data['Time spent(m:s)'] = pd.to_datetime(data['Time spent'], unit='s').dt.strftime('%M:%S')
 
 
198
 
199
  # Extract environment name from filename
200
  filename = uploaded_file.name
 
43
  'Failed Scenario': 'string'
44
  }
45
 
46
+ # Attempt fast streaming read (read_only) for performance
47
+ fast_excel = pd.ExcelFile(uploaded_file, engine='openpyxl')
48
+ # Read first sheet (error messages)
49
+ error_df = pd.read_excel(fast_excel, sheet_name=0)
50
+ # Read 'Time Taken' sheet in fast mode
51
+ df = pd.read_excel(fast_excel, sheet_name='Time Taken', dtype=object)
52
+
53
+ # If the sheet appears truncated (e.g., only header row) or missing expected columns, retry in full mode
54
+ if df.shape[0] <= 1 or 'Total Time Taken (ms)' not in df.columns:
55
+ st.warning("Fast Excel read produced incomplete data; retrying in full mode.")
56
+ slow_excel = pd.ExcelFile(
57
+ uploaded_file,
58
+ engine='openpyxl',
59
+ engine_kwargs={
60
+ 'read_only': False,
61
+ 'data_only': True,
62
+ 'keep_links': False
63
+ }
64
+ )
65
+ # Reload both sheets in full mode
66
+ error_df = pd.read_excel(slow_excel, sheet_name=0)
67
+ df = pd.read_excel(slow_excel, sheet_name='Time Taken', dtype=object)
68
 
69
  # Print column names and sample values for debugging
70
  # st.write("Excel columns:", df.columns.tolist())
 
76
  # empty_features = df['Feature Name'].isna().sum()
77
  # st.write(f"Empty Feature Names: {empty_features}")
78
 
79
+ # Convert specific columns after reading as object
80
+ df['Total Time Taken (ms)'] = pd.to_numeric(df['Total Time Taken (ms)'], errors='coerce').fillna(0).astype('float64')
81
  df['Failed Scenario'] = df['Failed Scenario'].astype(str).str.upper()
82
+ # Ensure Feature Name and Scenario Name are strings, handling potential NaNs read as objects
83
+ df['Feature Name'] = df['Feature Name'].astype(str).fillna('Unknown')
84
+ df['Scenario Name'] = df['Scenario Name'].astype(str)
85
+
86
  df['Status'] = df['Failed Scenario'].map(
87
  lambda x: 'FAILED' if x in ['TRUE', 'YES', 'Y', '1'] else 'PASSED'
88
  )
 
146
  # Add environment column
147
  result_df['Environment'] = environment
148
 
149
+ # Calculate formatted time spent (coerce non-numeric values)
150
+ _secs = pd.to_numeric(result_df['Time spent'], errors='coerce')
151
+ result_df['Time spent(m:s)'] = pd.to_datetime(_secs, unit='s', errors='coerce').dt.strftime('%M:%S')
152
 
153
 
154
  result_df['Start datetime'] = pd.to_datetime(file_date)
 
169
  result_df = result_df.merge(start_times, on='Scenario Name', how='left')
170
  result_df.rename(columns={'Time Stamp': 'Scenario Start Time'}, inplace=True)
171
  scenario_start_times = result_df['Scenario Start Time']
172
+ # Combine time and date strings, then parse with explicit format to prevent mismatches
173
+ combined = scenario_start_times.dt.strftime('%H:%M:%S') + ' ' + file_date.strftime('%Y-%m-%d')
174
+ # Let pandas infer the datetime format and coerce invalid parses
175
+ result_df['Start datetime'] = pd.to_datetime(
176
+ combined,
177
+ infer_datetime_format=True,
178
+ errors='coerce'
179
+ )
180
 
181
  # Print counts for debugging
182
  # st.write(f"Processed data - Failed: {len(result_df[result_df['Status'] == 'FAILED'])}, Passed: {len(result_df[result_df['Status'] == 'PASSED'])}")
 
215
  data['Start datetime'] = pd.to_datetime(data['Start datetime'], dayfirst=True, errors='coerce')
216
  data['End datetime'] = pd.to_datetime(data['End datetime'], dayfirst=True, errors='coerce')
217
  data['Time spent'] = (data['End datetime'] - data['Start datetime']).dt.total_seconds()
218
+ ## Format time spent for CSV branch (coerce non-numeric values)
219
+ _secs = pd.to_numeric(data['Time spent'], errors='coerce')
220
+ data['Time spent(m:s)'] = pd.to_datetime(_secs, unit='s', errors='coerce').dt.strftime('%M:%S')
221
 
222
  # Extract environment name from filename
223
  filename = uploaded_file.name