Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ param aiDeploymentsLocation string
param AZURE_LOCATION string = ''
var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION

var uniqueId = toLower(uniqueString(environmentName, subscription().id, solutionLocation))

var uniqueId = toLower(uniqueString(environmentName, subscription().id, solutionLocation, resourceGroup().name))
var solutionPrefix = 'ca${padLeft(take(uniqueId, 12), 12, '0')}'

// Load the abbrevations file required to name the azure resources.
Expand Down
2 changes: 1 addition & 1 deletion src/App/WebApp.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ COPY --from=frontend /home/node/app/static /usr/src/app/static/
WORKDIR /usr/src/app
EXPOSE 80

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4", "--log-level", "info", "--access-log"]
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80", "--workers", "1", "--log-level", "info", "--access-log"]
66 changes: 41 additions & 25 deletions src/App/backend/services/sqldb_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

from backend.common.config import config

import time

load_dotenv()

driver = config.ODBC_DRIVER
Expand All @@ -33,33 +35,47 @@ def dict_cursor(cursor):


def get_connection():
try:
credential = DefaultAzureCredential(managed_identity_client_id=mid_id)
max_retries = 5
retry_delay = 2

token_bytes = credential.get_token(
"https://database.windows.net/.default"
).token.encode("utf-16-LE")
token_struct = struct.pack(
f"<I{len(token_bytes)}s", len(token_bytes), token_bytes
)
SQL_COPT_SS_ACCESS_TOKEN = (
1256 # This connection option is defined by Microsoft in msodbcsql.h
)
for attempt in range(max_retries):
try:
credential = DefaultAzureCredential(managed_identity_client_id=mid_id)

# Set up the connection
connection_string = f"DRIVER={driver};SERVER={server};DATABASE={database};"
conn = pyodbc.connect(
connection_string, attrs_before={SQL_COPT_SS_ACCESS_TOKEN: token_struct}
)
return conn
except pyodbc.Error as e:
logging.error(f"Failed with Default Credential: {str(e)}")
conn = pyodbc.connect(
f"DRIVER={driver};SERVER={server};DATABASE={database};UID={username};PWD={password}",
timeout=5,
)
logging.info("Connected using Username & Password")
return conn
token_bytes = credential.get_token(
"https://database.windows.net/.default"
).token.encode("utf-16-LE")
token_struct = struct.pack(
f"<I{len(token_bytes)}s", len(token_bytes), token_bytes
)
SQL_COPT_SS_ACCESS_TOKEN = (
1256 # This connection option is defined by Microsoft in msodbcsql.h
)

# Set up the connection
connection_string = f"DRIVER={driver};SERVER={server};DATABASE={database};"
conn = pyodbc.connect(
connection_string, attrs_before={SQL_COPT_SS_ACCESS_TOKEN: token_struct}
)
return conn
except pyodbc.Error as e:
logging.error(f"Failed with Default Credential: {str(e)}")
try:
conn = pyodbc.connect(
f"DRIVER={driver};SERVER={server};DATABASE={database};UID={username};PWD={password}",
timeout=5,
)
logging.info("Connected using Username & Password")
return conn
except pyodbc.Error as e:
logging.error(f"Failed with Username & Password: {str(e)}")

if attempt < max_retries - 1:
logging.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff
else:
raise e
Comment on lines +70 to +78
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception being raised here (e) is from the inner try-catch block (Username & Password authentication failure), but if the Default Credential also failed, that error information is lost. Consider raising a more comprehensive error that includes both failure modes or the original Default Credential error.

Suggested change
except pyodbc.Error as e:
logging.error(f"Failed with Username & Password: {str(e)}")
if attempt < max_retries - 1:
logging.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff
else:
raise e
except pyodbc.Error as username_password_error:
logging.error(f"Failed with Username & Password: {str(username_password_error)}")
if attempt < max_retries - 1:
logging.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff
else:
raise RuntimeError(
f"Connection failed after multiple attempts. "
f"Default Credential error: {str(default_credential_error)}; "
f"Username & Password error: {str(username_password_error)}"
)

Copilot uses AI. Check for mistakes.


Comment on lines +63 to 80
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nested try-catch blocks create complex control flow that's difficult to follow. Consider extracting the password-based connection attempt into a separate function or restructuring the retry logic to handle both authentication methods in a cleaner way.

Suggested change
try:
conn = pyodbc.connect(
f"DRIVER={driver};SERVER={server};DATABASE={database};UID={username};PWD={password}",
timeout=5,
)
logging.info("Connected using Username & Password")
return conn
except pyodbc.Error as e:
logging.error(f"Failed with Username & Password: {str(e)}")
if attempt < max_retries - 1:
logging.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff
else:
raise e
try:
conn = connect_with_password()
logging.info("Connected using Username & Password")
return conn
except pyodbc.Error as e:
logging.error(f"Failed with Username & Password: {str(e)}")
if attempt < max_retries - 1:
logging.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff
else:
raise e

Copilot uses AI. Check for mistakes.
def get_client_name_from_db(client_id: str) -> str:
Expand Down
35 changes: 24 additions & 11 deletions src/App/frontend/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,33 @@ export const getpbi = async (): Promise<string> => {
return '';
}

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

export const getUsers = async (): Promise<User[]> => {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`Failed to fetch users: ${response.statusText}`);
const maxRetries = 1;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('/api/users', {
signal: AbortSignal.timeout(60000)
});
if (!response.ok) {
throw new Error(`Failed to fetch users: ${response.statusText}`);
}
const data: User[] = await response.json();
console.log('Fetched users:', data);
return data;
} catch (error) {
if (attempt < maxRetries &&
error instanceof Error) {
Comment on lines +61 to +62
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retry logic only retries on Error instances, but AbortSignal.timeout errors may not always be Error instances. Consider checking for specific error types (like AbortError for timeouts) or removing the instanceof Error check to retry on all errors.

Suggested change
if (attempt < maxRetries &&
error instanceof Error) {
if (attempt < maxRetries) {

Copilot uses AI. Check for mistakes.
console.warn(`Retrying fetch users... (retry ${attempt + 1}/${maxRetries})`);
await sleep(5000); // Simple 5 second delay
} else {
console.error('Error fetching users:', error);
return [];
}
}
const data: User[] = await response.json();
console.log('Fetched users:', data);
return data;
} catch (error) {
console.error('Error fetching users:', error);
return [];
// throw error;
}
return [];
};

// export const fetchChatHistoryInit = async (): Promise<Conversation[] | null> => {
Expand Down
Loading