Skip to content

Dev #475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 25 commits into from
Closed

Dev #475

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d5776d2
Downgrade msal[broker] to 1.24.0b1 and azure-mgmt-resource to 23.1.0b2
Prasanjeet-Microsoft Jan 10, 2025
56f4f45
Merge pull request #282 from microsoft/downgrade-msal-azure-mgmt-reso…
Prajwal-Microsoft Jan 10, 2025
02f68ca
Downgrade azure-ai-ml package to version 1.17.1 to resolve package ve…
Prasanjeet-Microsoft Jan 13, 2025
c39f609
Upgrade azure-ai-ml package to compatible version
Prasanjeet-Microsoft Jan 13, 2025
99b5270
Merge pull request #284 from microsoft/upgrade-azure-ai-ml-package
Prajwal-Microsoft Jan 13, 2025
94b4f4c
test: Research assistant backend unit test (#286)
Pradheep-Microsoft Jan 13, 2025
b785eb6
fix: Research Assistant Deployment Issue - Prompt Flow version update…
Pradheep-Microsoft Jan 20, 2025
07bac06
Implement Consistent Versioning and Tagging for Accelerator Images (#…
Pavan-Microsoft Jan 22, 2025
0d82faa
Dev image version check
Pavan-Microsoft Jan 27, 2025
ae75bd3
Merge pull request #291 from microsoft/PSL-PK-ImageVersion
Pavan-Microsoft Jan 27, 2025
fb30c09
Revert "feat: Dev Image Version Check Test"
Pavan-Microsoft Jan 27, 2025
a582ee1
Merge pull request #292 from microsoft/revert-291-PSL-PK-ImageVersion
Pavan-Microsoft Jan 27, 2025
2adf389
update history method changes (#295)
pradeepjha-microsoft Jan 30, 2025
951fa25
fix: Fixed Asset and retirement update query (#332)
AjitPadhi-Microsoft Feb 6, 2025
6b85ef2
fixed the UI issue in Chat history list section (#335)
Mohan-Microsoft Feb 14, 2025
c412c08
Merge branch 'main' into dev
Roopan-Microsoft Feb 26, 2025
24752c7
Added code changes to handle null object error in history generate me…
Abdul-Microsoft Feb 26, 2025
ba9eb2a
build: validated and updated dependency for BYOC-ResearchAssistant Ac…
VishalS-Microsoft Feb 28, 2025
b81be42
Upgraded Package For AI_Hub Script (#419)
UtkarshMishra-Microsoft Mar 26, 2025
0d1f835
Merge branch 'main' into dev
Roopan-Microsoft Apr 1, 2025
03d13f1
package and ai-script (#426)
UtkarshMishra-Microsoft Apr 1, 2025
5c27106
resolved bug:16535 (#468)
VishalS-Microsoft Apr 4, 2025
14c770f
feat: SFI SQL Policy Changes (#469)
Abdul-Microsoft Apr 9, 2025
9cbfca2
perf: Semantic Kernel and Prompt improvement (#415)
UtkarshMishra-Microsoft Apr 9, 2025
23cbe2c
Merge branch 'main' into dev
Roopan-Microsoft Apr 10, 2025
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
9 changes: 7 additions & 2 deletions ClientAdvisor/App/WebApp.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ RUN apk add --no-cache --virtual .build-deps \
libffi-dev \
openssl-dev \
curl \
unixodbc-dev \
&& apk add --no-cache \
libpq
libpq \
&& curl -O https://download.microsoft.com/download/7/6/d/76de322a-d860-4894-9945-f0cc5d6a45f8/msodbcsql18_18.4.1.1-1_amd64.apk \
&& apk add --allow-untrusted msodbcsql18_18.4.1.1-1_amd64.apk \
&& rm msodbcsql18_18.4.1.1-1_amd64.apk

COPY ./ClientAdvisor/App/requirements.txt /usr/src/app/
RUN pip install --upgrade pip setuptools wheel \

RUN pip install --upgrade pip setuptools wheel \
&& pip install --no-cache-dir -r /usr/src/app/requirements.txt \
&& rm -rf /root/.cache

Expand Down
7 changes: 5 additions & 2 deletions ClientAdvisor/App/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
format_stream_response, generateFilterString,
parse_multi_columns)
from db import get_connection
from db import dict_cursor

bp = Blueprint("routes", __name__, static_folder="static", template_folder="static")

Expand Down Expand Up @@ -1583,7 +1584,8 @@ def get_users():
ORDER BY NextMeeting ASC;
"""
cursor.execute(sql_stmt)
rows = cursor.fetchall()
# Since pyodbc returns query results as a list of tuples, using `dict_cursor` function to convert these tuples into a list of dictionaries
rows = dict_cursor(cursor)

if len(rows) <= 6:
# update ClientMeetings,Assets,Retirement tables sample data to current date
Expand Down Expand Up @@ -1618,7 +1620,8 @@ def get_users():
FROM DaysDifference
"""
cursor.execute(combined_stmt)
date_diff_rows = cursor.fetchall()
# Since pyodbc returns query results as a list of tuples, using `dict_cursor` function to convert these tuples into a list of dictionaries
date_diff_rows = dict_cursor(cursor)

client_days = (
date_diff_rows[0]["ClientMeetingDaysDifference"]
Expand Down
50 changes: 45 additions & 5 deletions ClientAdvisor/App/db.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,60 @@
# db.py
import os

import pymssql
from dotenv import load_dotenv
from azure.identity import DefaultAzureCredential
import pyodbc
import struct
import logging


load_dotenv()

driver = "{ODBC Driver 18 for SQL Server}"
server = os.environ.get("SQLDB_SERVER")
database = os.environ.get("SQLDB_DATABASE")
username = os.environ.get("SQLDB_USERNAME")
password = os.environ.get("SQLDB_PASSWORD")
mid_id = os.environ.get("SQLDB_USER_MID")


def dict_cursor(cursor):
"""
Converts rows fetched by the cursor into a list of dictionaries.

Args:
cursor: A database cursor object.

Returns:
A list of dictionaries representing rows.
"""
columns = [column[0] for column in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]


def get_connection():
try:
credential = DefaultAzureCredential(managed_identity_client_id=mid_id)

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
)

conn = pymssql.connect(
server=server, user=username, password=password, database=database, as_dict=True
)
return conn
# 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
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ describe('ChatHistoryListItemCell', () => {
test('renders the chat history item', () => {
renderWithContext(<ChatHistoryListItemCell item={conversation} onSelect={mockOnSelect} />, mockAppState)

const titleElement = screen.getByText(/Test Chat/i)
expect(titleElement).toBeInTheDocument()
const titleElement = screen.getAllByText(/Test Chat/i)
expect(titleElement.length).toBeGreaterThan(1)
})

test('truncates long title', () => {
Expand All @@ -50,7 +50,7 @@ describe('ChatHistoryListItemCell', () => {

renderWithContext(<ChatHistoryListItemCell item={longTitleConversation} onSelect={mockOnSelect} />, mockAppState)

const truncatedTitle = screen.getByText(/A very long title that shoul .../i)
const truncatedTitle = screen.getByText(/A very long title that s .../i)
expect(truncatedTitle).toBeInTheDocument()
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import {
DialogType,
IconButton,
ITextField,
ITooltipHostStyles,
List,
PrimaryButton,
Separator,
Spinner,
SpinnerSize,
Stack,
Text,
TextField
TextField,
TooltipHost,
DirectionalHint,
} from '@fluentui/react'
import { useBoolean } from '@fluentui/react-hooks'

Expand Down Expand Up @@ -107,7 +110,7 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
appStateContext?.dispatch({ type: 'UPDATE_CURRENT_CHAT', payload: item })
}

const truncatedTitle = item?.title?.length > 28 ? `${item.title.substring(0, 28)} ...` : item.title
const truncatedTitle = item?.title?.length > 24 ? `${item.title.substring(0, 24)} ...` : item.title

const handleSaveEdit = async (e: any) => {
e.preventDefault()
Expand Down Expand Up @@ -236,8 +239,15 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
</>
) : (
<>
<Stack horizontal verticalAlign={'center'} style={{ width: '100%' }}>
<div className={styles.chatTitle}>{truncatedTitle}</div>
<Stack horizontal verticalAlign={'center'} style={{ width: '100%' }}>
<Stack.Item grow>
<TooltipHost
content={item.title}
directionalHint={DirectionalHint.bottomCenter}
>
<div className={styles.chatTitle}>{truncatedTitle}</div>
</TooltipHost>
</Stack.Item>
{(isSelected || isHovered) && (
<Stack horizontal horizontalAlign="end">
<IconButton
Expand Down
6 changes: 4 additions & 2 deletions ClientAdvisor/App/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ isort==6.0.0

# Testing tools
pytest>=8.2,<9 # Compatible version for pytest-asyncio
pytest-asyncio==0.25.3
pytest-cov==6.0.0
pytest-asyncio==0.24.0
pytest-cov==5.0.0

pyodbc==5.2.0
76 changes: 43 additions & 33 deletions ClientAdvisor/App/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,50 +200,60 @@ async def test_ensure_cosmos_generic_exception(mock_init_cosmosdb_client, client


@pytest.mark.asyncio
async def test_get_users_success(client):
@patch("app.get_connection")
@patch("app.dict_cursor")
async def test_get_users_success(mock_dict_cursor, mock_get_connection, client):
# Mock database connection and cursor
mock_conn = MagicMock()
mock_cursor = MagicMock()
mock_get_connection.return_value = mock_conn
mock_conn.cursor.return_value = mock_cursor
mock_cursor.fetchall.return_value = [
{
"ClientId": 1,
"ndays": 10,
"ClientMeetingDaysDifference": 1,
"AssetMonthsDifference": 1,
"StatusMonthsDifference": 1,
"DaysDifference": 1,
"Client": "Client A",
"Email": "clienta@example.com",
"AssetValue": "1,000,000",
"ClientSummary": "Summary A",
"LastMeetingDateFormatted": "Monday January 1, 2023",
"LastMeetingStartTime": "10:00 AM",
"LastMeetingEndTime": "10:30 AM",
"NextMeetingFormatted": "Monday January 8, 2023",
"NextMeetingStartTime": "11:00 AM",
"NextMeetingEndTime": "11:30 AM",
}
]

with patch("app.get_connection", return_value=mock_conn):
response = await client.get("/api/users")
assert response.status_code == 200
res_text = await response.get_data(as_text=True)
assert json.loads(res_text) == [
# Mock query results
mock_dict_cursor.side_effect = [
[ # First call (client data)
{
"ClientId": 1,
"ClientName": "Client A",
"ClientEmail": "clienta@example.com",
"Client": "Client A",
"Email": "clienta@example.com",
"AssetValue": "1,000,000",
"NextMeeting": "Monday January 8, 2023",
"NextMeetingTime": "11:00 AM",
"NextMeetingEndTime": "11:30 AM",
"LastMeeting": "Monday January 1, 2023",
"ClientSummary": "Summary A",
"LastMeetingDateFormatted": "Monday January 1, 2023",
"LastMeetingStartTime": "10:00 AM",
"LastMeetingEndTime": "10:30 AM",
"ClientSummary": "Summary A",
"NextMeetingFormatted": "Monday January 8, 2023",
"NextMeetingStartTime": "11:00 AM",
"NextMeetingEndTime": "11:30 AM",
}
],
[ # Second call (date difference query)
{
"ClientMeetingDaysDifference": 5,
"AssetMonthsDifference": 1,
"StatusMonthsDifference": 1
}
]
]

# Call the function
response = await client.get("/api/users")
assert response.status_code == 200
res_text = await response.get_data(as_text=True)
assert json.loads(res_text) == [
{
"ClientId": 1,
"ClientName": "Client A",
"ClientEmail": "clienta@example.com",
"AssetValue": "1,000,000",
"NextMeeting": "Monday January 8, 2023",
"NextMeetingTime": "11:00 AM",
"NextMeetingEndTime": "11:30 AM",
"LastMeeting": "Monday January 1, 2023",
"LastMeetingStartTime": "10:00 AM",
"LastMeetingEndTime": "10:30 AM",
"ClientSummary": "Summary A",
}
]


@pytest.mark.asyncio
Expand Down
78 changes: 70 additions & 8 deletions ClientAdvisor/App/tests/test_db.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,92 @@
import struct
from unittest.mock import MagicMock, patch

import db
import pyodbc

# Mock configuration
db.server = "mock_server"
db.username = "mock_user"
db.password = "mock_password"
db.database = "mock_database"
db.driver = "mock_driver"
db.mid_id = "mock_mid_id" # Managed identity client ID if needed


@patch("db.pymssql.connect")
def test_get_connection(mock_connect):
@patch("db.pyodbc.connect") # Mock pyodbc.connect
@patch("db.DefaultAzureCredential") # Mock DefaultAzureCredential
def test_get_connection(mock_credential_class, mock_connect):
# Mock the DefaultAzureCredential and get_token method
mock_credential = MagicMock()
mock_credential_class.return_value = mock_credential
mock_token = MagicMock()
mock_token.token = "mock_token"
mock_credential.get_token.return_value = mock_token
# Create a mock connection object
mock_conn = MagicMock()
mock_connect.return_value = mock_conn

# Call the function
conn = db.get_connection()

# Assert that pymssql.connect was called with the correct parameters
# Assert that DefaultAzureCredential and get_token were called correctly
mock_credential_class.assert_called_once_with(managed_identity_client_id=db.mid_id)
mock_credential.get_token.assert_called_once_with("https://database.windows.net/.default")

# Assert that pyodbc.connect was called with the correct parameters, including the token
expected_attrs_before = {
1256: struct.pack(f"<I{len(mock_token.token.encode('utf-16-LE'))}s", len(mock_token.token.encode("utf-16-LE")), mock_token.token.encode("utf-16-LE"))
}
mock_connect.assert_called_once_with(
server="mock_server",
user="mock_user",
password="mock_password",
database="mock_database",
as_dict=True,
f"DRIVER={db.driver};SERVER={db.server};DATABASE={db.database};",
attrs_before=expected_attrs_before
)

# Assert that the connection returned is the mock connection
assert conn == mock_conn


@patch("db.pyodbc.connect") # Mock pyodbc.connect
@patch("db.DefaultAzureCredential") # Mock DefaultAzureCredential
def test_get_connection_token_failure(mock_credential_class, mock_connect):
# Mock the DefaultAzureCredential and get_token method
mock_credential = MagicMock()
mock_credential_class.return_value = mock_credential
mock_token = MagicMock()
mock_token.token = "mock_token"
mock_credential.get_token.return_value = mock_token

# Create a mock connection object
mock_conn = MagicMock()
mock_connect.return_value = mock_conn

# Simulate a failure in pyodbc.connect by raising pyodbc.Error on the first call
mock_connect.side_effect = [pyodbc.Error("pyodbc connection error"), mock_conn]

# Call the function and ensure fallback is used after the pyodbc error
conn = db.get_connection()

# Assert that pyodbc.connect was called with username and password as fallback
mock_connect.assert_any_call(
f"DRIVER={db.driver};SERVER={db.server};DATABASE={db.database};UID={db.username};PWD={db.password}",
timeout=5
)

# Assert that the connection returned is the mock connection
assert conn == mock_conn


def test_dict_cursor():
# Create a mock cursor
mock_cursor = MagicMock()

# Simulate the cursor.description and cursor.fetchall
mock_cursor.description = [("id",), ("name",), ("age",)]
mock_cursor.fetchall.return_value = [(1, "Alice", 30), (2, "Bob", 25)]

# Call the dict_cursor function
result = db.dict_cursor(mock_cursor)

# Verify the result
expected_result = [{'id': 1, 'name': 'Alice', 'age': 30}, {'id': 2, 'name': 'Bob', 'age': 25}]
assert result == expected_result
4 changes: 4 additions & 0 deletions ClientAdvisor/AzureFunction/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# FROM mcr.microsoft.com/azure-functions/python:4-python3.11-appservice
FROM mcr.microsoft.com/azure-functions/python:4-python3.11

# Install Microsoft ODBC Driver
RUN apt-get update && \
ACCEPT_EULA=Y apt-get install -y msodbcsql18

ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
AzureFunctionsJobHost__Logging__Console__IsEnabled=true

Expand Down
Loading
Loading