From 5125e7b6c939855ba046c4d60353fb914875903f Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Tue, 12 Dec 2023 13:25:43 -0800 Subject: [PATCH 1/9] Fix package downgrade issues Azure.Identity package version upgrade in PR #28 was not compatible with Microsoft.Identity.Client version that was only being directly referenced from the project. Bump the Microsoft.Identity.Client version to avoid dependency downgrade issues. --- app/backend/CustomerSupportServiceSample.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/backend/CustomerSupportServiceSample.csproj b/app/backend/CustomerSupportServiceSample.csproj index 2f14e27..e05c790 100644 --- a/app/backend/CustomerSupportServiceSample.csproj +++ b/app/backend/CustomerSupportServiceSample.csproj @@ -17,13 +17,13 @@ - + - + From b0057b3536eadc58da30eac08fc4aadda3e66020 Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Tue, 12 Dec 2023 14:31:40 -0800 Subject: [PATCH 2/9] Fix email UI Quick fixes to make sure the object returned by SendSummaryDetails is what SummaryWindow expects. --- app/frontend/src/components/Agent/SummaryWindow.tsx | 4 +++- app/frontend/src/utils/SendSummaryDetails.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/frontend/src/components/Agent/SummaryWindow.tsx b/app/frontend/src/components/Agent/SummaryWindow.tsx index 0c39163..4ca8d1c 100644 --- a/app/frontend/src/components/Agent/SummaryWindow.tsx +++ b/app/frontend/src/components/Agent/SummaryWindow.tsx @@ -61,8 +61,10 @@ const SummaryWindow: React.FC = () => { }; const response = await SendSummaryDetails(email); - if (response.status !== 'Succeeded') { + if (response.result !== 'Succeeded') { alert('Email failed with error code: ' + response.status); + } else { + console.log('Email sent succesfully!'); } }; diff --git a/app/frontend/src/utils/SendSummaryDetails.tsx b/app/frontend/src/utils/SendSummaryDetails.tsx index c02942c..4f90e97 100644 --- a/app/frontend/src/utils/SendSummaryDetails.tsx +++ b/app/frontend/src/utils/SendSummaryDetails.tsx @@ -22,12 +22,14 @@ export const SendSummaryDetails = async (summaryDetails: SummaryEmailData) => { body: JSON.stringify(summaryDetails) }); if (response.ok) { - return response.json(); + return await response.json(); } else { console.error('Failed to send summary data'); + return { result: 'Failed' }; } } catch (error) { console.error('An error occurred:', error); + return { result: 'Failed' }; } }; From fd7869c0537d1f56c49d98211a34ad05387bca49 Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Thu, 21 Dec 2023 14:48:05 -0800 Subject: [PATCH 3/9] Added solution file for backend app --- app/backend/backend.sln | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/backend/backend.sln diff --git a/app/backend/backend.sln b/app/backend/backend.sln new file mode 100644 index 0000000..78f7661 --- /dev/null +++ b/app/backend/backend.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomerSupportServiceSample", "CustomerSupportServiceSample.csproj", "{8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A187B7FF-4E2B-4E67-A1C4-8CEA462F2D4B} + EndGlobalSection +EndGlobal From 0890c71cdaaff3b19a7499de2af267ce8b5201a0 Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Thu, 21 Dec 2023 14:56:36 -0800 Subject: [PATCH 4/9] Close email modal on send --- app/frontend/src/components/Agent/SummaryWindow.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/frontend/src/components/Agent/SummaryWindow.tsx b/app/frontend/src/components/Agent/SummaryWindow.tsx index 4ca8d1c..5ce52f1 100644 --- a/app/frontend/src/components/Agent/SummaryWindow.tsx +++ b/app/frontend/src/components/Agent/SummaryWindow.tsx @@ -66,6 +66,8 @@ const SummaryWindow: React.FC = () => { } else { console.log('Email sent succesfully!'); } + + setIsModalOpen(false); }; return ( From 3e63368f5d6fc85c7443861a9c2808d8a842c6cb Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Thu, 21 Dec 2023 15:32:22 -0800 Subject: [PATCH 5/9] Support custom email addresses Add ability to enter Customer email address from Agent's "send email" UI. --- README.md | 1 - app/backend/Models/EmailSummaryRequest.cs | 3 ++- app/backend/Services/SummaryService.cs | 12 ++++-------- .../src/components/Agent/SummaryWindow.tsx | 11 +++++++++++ app/frontend/src/styles/SummaryWindow.css | 17 +++++++++++------ app/frontend/src/utils/SendSummaryDetails.tsx | 1 + 6 files changed, 29 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c045db1..9278ebe 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,6 @@ Almost there! You need to do three simple steps manually in the [Azure portal](h - Update the backend App Service application settings - Open the web app resource created for backend application and navigate to the Environment variables blade. - Update values for `AcsPhoneNumber` and `EmailSender` with the phone number and sender email address obtained in previous steps. - - Update the value for `EmailRecipient` with your email address where you would like to receive emails sent out by the sample applications. - Remember to save settings. ## Setup Instructions – Local environment diff --git a/app/backend/Models/EmailSummaryRequest.cs b/app/backend/Models/EmailSummaryRequest.cs index 9a3b2d2..ddce196 100644 --- a/app/backend/Models/EmailSummaryRequest.cs +++ b/app/backend/Models/EmailSummaryRequest.cs @@ -5,7 +5,8 @@ namespace CustomerSupportServiceSample.Models { public class SummaryRequest { - [JsonPropertyName("body")] + public string? Address { get; set; } + public string? Body { get; set; } } } \ No newline at end of file diff --git a/app/backend/Services/SummaryService.cs b/app/backend/Services/SummaryService.cs index 6e880cf..dab7abd 100644 --- a/app/backend/Services/SummaryService.cs +++ b/app/backend/Services/SummaryService.cs @@ -11,7 +11,6 @@ public class SummaryService : ISummaryService private readonly ILogger logger; private readonly string acsConnectionString; private readonly string sender; - private readonly string recipient; public SummaryService( IChatService chatService, @@ -25,7 +24,6 @@ public SummaryService( this.configuration = configuration; acsConnectionString = this.configuration["AcsConnectionString"] ?? ""; sender = this.configuration["EmailSender"] ?? ""; - recipient = this.configuration["EmailRecipient"] ?? ""; ArgumentException.ThrowIfNullOrEmpty(acsConnectionString); } @@ -52,17 +50,15 @@ public async Task SendSummaryEmail(SummaryRequest summary) var htmlContent = summary.Body; try { - logger.LogInformation("Sending email: to={}, from={}, body={}", recipient, sender, htmlContent); + logger.LogInformation("Sending email: to={}, from={}, body={}", summary.Address, sender, htmlContent); ArgumentException.ThrowIfNullOrEmpty(sender); - ArgumentException.ThrowIfNullOrEmpty(recipient); - // Note: - // This quickstart sample uses receiver email address from app configuration for simplicity - // In production scenario customer would provide their preferred email address + ArgumentException.ThrowIfNullOrEmpty(summary.Address); + EmailClient emailClient = new(this.acsConnectionString); EmailSendOperation emailSendOperation = await emailClient.SendAsync( WaitUntil.Completed, sender, - recipient, + summary.Address, "Follow up on support conversation", htmlContent); return emailSendOperation.Value.Status.ToString(); diff --git a/app/frontend/src/components/Agent/SummaryWindow.tsx b/app/frontend/src/components/Agent/SummaryWindow.tsx index 5ce52f1..2024c26 100644 --- a/app/frontend/src/components/Agent/SummaryWindow.tsx +++ b/app/frontend/src/components/Agent/SummaryWindow.tsx @@ -30,6 +30,8 @@ const SummaryWindow: React.FC = () => { const [isLoading, setIsLoading] = useState(false); const [summaryDetails, setSummaryDetails] = useState(); const [summaryConversation, setSummaryConversation] = useState(); + const [customerEmailAddress, setCustomerEmail] = useState(''); + const chatThreadId = localStorage.getItem('chatThreadId'); useEffect(() => { if (isModalOpen) { @@ -57,6 +59,7 @@ const SummaryWindow: React.FC = () => { const handleSendSummary = async () => { const emailBodyTemplate = await getEmailTemplate(summaryConversation?.result); const email: SummaryEmailData = { + address: customerEmailAddress, body: emailBodyTemplate }; @@ -70,6 +73,10 @@ const SummaryWindow: React.FC = () => { setIsModalOpen(false); }; + const handleEmailUpdate = (event: React.ChangeEvent) => { + setCustomerEmail(event.target.value); + }; + return (
@@ -90,6 +97,10 @@ const SummaryWindow: React.FC = () => {

Summary

{summaryDetails?.summaryItems[0].description}

+
+

Customer email address

+ +

Tasks

    diff --git a/app/frontend/src/styles/SummaryWindow.css b/app/frontend/src/styles/SummaryWindow.css index ff9215d..fc0ce80 100644 --- a/app/frontend/src/styles/SummaryWindow.css +++ b/app/frontend/src/styles/SummaryWindow.css @@ -4,10 +4,10 @@ Licensed under the MIT License.*/ .dialog { font-family: Arial, sans-serif; width: 950px; - height: 590px; + /* height: 590px; */ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); background-color: #fff; - overflow: hidden; + /* overflow: hidden; */ border-radius: 8px; } @@ -20,11 +20,16 @@ Licensed under the MIT License.*/ .summary-section, .tasks-section, +.email-address-section, .communication-section { padding: 4px; padding-left: 16px; } +.email-address-input { + width: 300px; +} + .bordered-content { border: 2px solid lightgray; border-radius: 5px; @@ -42,7 +47,7 @@ Licensed under the MIT License.*/ margin-top: 8px; width: 900px; white-space: pre-line; - height: 190px; + height: 100px; overflow-y: scroll; } @@ -87,9 +92,9 @@ Licensed under the MIT License.*/ text-align: right; } - .send-summary-button:hover { - background-color: #00894F; - } +.send-summary-button:hover { + background-color: #00894F; +} .button-group { display: flex; diff --git a/app/frontend/src/utils/SendSummaryDetails.tsx b/app/frontend/src/utils/SendSummaryDetails.tsx index 4f90e97..325dd6c 100644 --- a/app/frontend/src/utils/SendSummaryDetails.tsx +++ b/app/frontend/src/utils/SendSummaryDetails.tsx @@ -5,6 +5,7 @@ import config from '../appsettings.json'; const BASE_URL = config.baseUrl; export interface SummaryEmailData { + address: string; body: string; } From a129933eefbb7e080805dd6405c75dba4080cc59 Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Thu, 21 Dec 2023 14:48:05 -0800 Subject: [PATCH 6/9] Added solution file for backend app --- app/backend/backend.sln | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/backend/backend.sln diff --git a/app/backend/backend.sln b/app/backend/backend.sln new file mode 100644 index 0000000..78f7661 --- /dev/null +++ b/app/backend/backend.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomerSupportServiceSample", "CustomerSupportServiceSample.csproj", "{8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E16C0A1-74F6-4A8A-A19F-48137D38DEAA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A187B7FF-4E2B-4E67-A1C4-8CEA462F2D4B} + EndGlobalSection +EndGlobal From ede03699cfce2093e80fcc7ab5ebcafbcc6b8f6f Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Thu, 21 Dec 2023 14:56:36 -0800 Subject: [PATCH 7/9] Close email modal on send --- app/frontend/src/components/Agent/SummaryWindow.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/frontend/src/components/Agent/SummaryWindow.tsx b/app/frontend/src/components/Agent/SummaryWindow.tsx index 4ca8d1c..5ce52f1 100644 --- a/app/frontend/src/components/Agent/SummaryWindow.tsx +++ b/app/frontend/src/components/Agent/SummaryWindow.tsx @@ -66,6 +66,8 @@ const SummaryWindow: React.FC = () => { } else { console.log('Email sent succesfully!'); } + + setIsModalOpen(false); }; return ( From cc4258375a1a63d2b910d48ffa1fe1909f821a51 Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Thu, 21 Dec 2023 15:32:22 -0800 Subject: [PATCH 8/9] Support custom email addresses Add ability to enter Customer email address from Agent's "send email" UI. --- README.md | 1 - app/backend/Models/EmailSummaryRequest.cs | 3 ++- app/backend/Services/SummaryService.cs | 12 ++++-------- .../src/components/Agent/SummaryWindow.tsx | 11 +++++++++++ app/frontend/src/styles/SummaryWindow.css | 17 +++++++++++------ app/frontend/src/utils/SendSummaryDetails.tsx | 1 + 6 files changed, 29 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c045db1..9278ebe 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,6 @@ Almost there! You need to do three simple steps manually in the [Azure portal](h - Update the backend App Service application settings - Open the web app resource created for backend application and navigate to the Environment variables blade. - Update values for `AcsPhoneNumber` and `EmailSender` with the phone number and sender email address obtained in previous steps. - - Update the value for `EmailRecipient` with your email address where you would like to receive emails sent out by the sample applications. - Remember to save settings. ## Setup Instructions – Local environment diff --git a/app/backend/Models/EmailSummaryRequest.cs b/app/backend/Models/EmailSummaryRequest.cs index 9a3b2d2..ddce196 100644 --- a/app/backend/Models/EmailSummaryRequest.cs +++ b/app/backend/Models/EmailSummaryRequest.cs @@ -5,7 +5,8 @@ namespace CustomerSupportServiceSample.Models { public class SummaryRequest { - [JsonPropertyName("body")] + public string? Address { get; set; } + public string? Body { get; set; } } } \ No newline at end of file diff --git a/app/backend/Services/SummaryService.cs b/app/backend/Services/SummaryService.cs index 6e880cf..dab7abd 100644 --- a/app/backend/Services/SummaryService.cs +++ b/app/backend/Services/SummaryService.cs @@ -11,7 +11,6 @@ public class SummaryService : ISummaryService private readonly ILogger logger; private readonly string acsConnectionString; private readonly string sender; - private readonly string recipient; public SummaryService( IChatService chatService, @@ -25,7 +24,6 @@ public SummaryService( this.configuration = configuration; acsConnectionString = this.configuration["AcsConnectionString"] ?? ""; sender = this.configuration["EmailSender"] ?? ""; - recipient = this.configuration["EmailRecipient"] ?? ""; ArgumentException.ThrowIfNullOrEmpty(acsConnectionString); } @@ -52,17 +50,15 @@ public async Task SendSummaryEmail(SummaryRequest summary) var htmlContent = summary.Body; try { - logger.LogInformation("Sending email: to={}, from={}, body={}", recipient, sender, htmlContent); + logger.LogInformation("Sending email: to={}, from={}, body={}", summary.Address, sender, htmlContent); ArgumentException.ThrowIfNullOrEmpty(sender); - ArgumentException.ThrowIfNullOrEmpty(recipient); - // Note: - // This quickstart sample uses receiver email address from app configuration for simplicity - // In production scenario customer would provide their preferred email address + ArgumentException.ThrowIfNullOrEmpty(summary.Address); + EmailClient emailClient = new(this.acsConnectionString); EmailSendOperation emailSendOperation = await emailClient.SendAsync( WaitUntil.Completed, sender, - recipient, + summary.Address, "Follow up on support conversation", htmlContent); return emailSendOperation.Value.Status.ToString(); diff --git a/app/frontend/src/components/Agent/SummaryWindow.tsx b/app/frontend/src/components/Agent/SummaryWindow.tsx index 5ce52f1..2024c26 100644 --- a/app/frontend/src/components/Agent/SummaryWindow.tsx +++ b/app/frontend/src/components/Agent/SummaryWindow.tsx @@ -30,6 +30,8 @@ const SummaryWindow: React.FC = () => { const [isLoading, setIsLoading] = useState(false); const [summaryDetails, setSummaryDetails] = useState(); const [summaryConversation, setSummaryConversation] = useState(); + const [customerEmailAddress, setCustomerEmail] = useState(''); + const chatThreadId = localStorage.getItem('chatThreadId'); useEffect(() => { if (isModalOpen) { @@ -57,6 +59,7 @@ const SummaryWindow: React.FC = () => { const handleSendSummary = async () => { const emailBodyTemplate = await getEmailTemplate(summaryConversation?.result); const email: SummaryEmailData = { + address: customerEmailAddress, body: emailBodyTemplate }; @@ -70,6 +73,10 @@ const SummaryWindow: React.FC = () => { setIsModalOpen(false); }; + const handleEmailUpdate = (event: React.ChangeEvent) => { + setCustomerEmail(event.target.value); + }; + return (
    @@ -90,6 +97,10 @@ const SummaryWindow: React.FC = () => {

    Summary

    {summaryDetails?.summaryItems[0].description}

+
+

Customer email address

+ +

Tasks

    diff --git a/app/frontend/src/styles/SummaryWindow.css b/app/frontend/src/styles/SummaryWindow.css index ff9215d..fc0ce80 100644 --- a/app/frontend/src/styles/SummaryWindow.css +++ b/app/frontend/src/styles/SummaryWindow.css @@ -4,10 +4,10 @@ Licensed under the MIT License.*/ .dialog { font-family: Arial, sans-serif; width: 950px; - height: 590px; + /* height: 590px; */ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); background-color: #fff; - overflow: hidden; + /* overflow: hidden; */ border-radius: 8px; } @@ -20,11 +20,16 @@ Licensed under the MIT License.*/ .summary-section, .tasks-section, +.email-address-section, .communication-section { padding: 4px; padding-left: 16px; } +.email-address-input { + width: 300px; +} + .bordered-content { border: 2px solid lightgray; border-radius: 5px; @@ -42,7 +47,7 @@ Licensed under the MIT License.*/ margin-top: 8px; width: 900px; white-space: pre-line; - height: 190px; + height: 100px; overflow-y: scroll; } @@ -87,9 +92,9 @@ Licensed under the MIT License.*/ text-align: right; } - .send-summary-button:hover { - background-color: #00894F; - } +.send-summary-button:hover { + background-color: #00894F; +} .button-group { display: flex; diff --git a/app/frontend/src/utils/SendSummaryDetails.tsx b/app/frontend/src/utils/SendSummaryDetails.tsx index 4f90e97..325dd6c 100644 --- a/app/frontend/src/utils/SendSummaryDetails.tsx +++ b/app/frontend/src/utils/SendSummaryDetails.tsx @@ -5,6 +5,7 @@ import config from '../appsettings.json'; const BASE_URL = config.baseUrl; export interface SummaryEmailData { + address: string; body: string; } From 81b2a0e91bb95599c0f21f2e9b4cf0428e15a587 Mon Sep 17 00:00:00 2001 From: Mikhail Bakalov Date: Thu, 21 Dec 2023 16:04:00 -0800 Subject: [PATCH 9/9] Better var name --- app/frontend/src/components/Agent/SummaryWindow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/frontend/src/components/Agent/SummaryWindow.tsx b/app/frontend/src/components/Agent/SummaryWindow.tsx index 2024c26..307b2aa 100644 --- a/app/frontend/src/components/Agent/SummaryWindow.tsx +++ b/app/frontend/src/components/Agent/SummaryWindow.tsx @@ -30,7 +30,7 @@ const SummaryWindow: React.FC = () => { const [isLoading, setIsLoading] = useState(false); const [summaryDetails, setSummaryDetails] = useState(); const [summaryConversation, setSummaryConversation] = useState(); - const [customerEmailAddress, setCustomerEmail] = useState(''); + const [customerEmailAddress, setCustomerEmailAddress] = useState(''); const chatThreadId = localStorage.getItem('chatThreadId'); useEffect(() => { @@ -74,7 +74,7 @@ const SummaryWindow: React.FC = () => { }; const handleEmailUpdate = (event: React.ChangeEvent) => { - setCustomerEmail(event.target.value); + setCustomerEmailAddress(event.target.value); }; return (