mirror of
https://github.com/bitwarden/server.git
synced 2025-07-19 16:37:08 -05:00
[PM-23575] Use the input text as question and avoid additional call to freshdesk (#6073)
This commit is contained in:
@ -144,7 +144,7 @@ public class FreshdeskController : Controller
|
|||||||
|
|
||||||
[HttpPost("webhook-onyx-ai")]
|
[HttpPost("webhook-onyx-ai")]
|
||||||
public async Task<IActionResult> PostWebhookOnyxAi([FromQuery, Required] string key,
|
public async Task<IActionResult> PostWebhookOnyxAi([FromQuery, Required] string key,
|
||||||
[FromBody, Required] FreshdeskWebhookModel model)
|
[FromBody, Required] FreshdeskOnyxAiWebhookModel model)
|
||||||
{
|
{
|
||||||
// ensure that the key is from Freshdesk
|
// ensure that the key is from Freshdesk
|
||||||
if (!IsValidRequestFromFreshdesk(key))
|
if (!IsValidRequestFromFreshdesk(key))
|
||||||
@ -152,28 +152,8 @@ public class FreshdeskController : Controller
|
|||||||
return new BadRequestResult();
|
return new BadRequestResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get ticket info from Freshdesk
|
|
||||||
var getTicketRequest = new HttpRequestMessage(HttpMethod.Get,
|
|
||||||
string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}", model.TicketId));
|
|
||||||
var getTicketResponse = await CallFreshdeskApiAsync(getTicketRequest);
|
|
||||||
|
|
||||||
// check if we have a valid response from freshdesk
|
|
||||||
if (getTicketResponse.StatusCode != System.Net.HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
_logger.LogError("Error getting ticket info from Freshdesk. Ticket Id: {0}. Status code: {1}",
|
|
||||||
model.TicketId, getTicketResponse.StatusCode);
|
|
||||||
return BadRequest("Failed to retrieve ticket info from Freshdesk");
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract info from the response
|
|
||||||
var ticketInfo = await ExtractTicketInfoFromResponse(getTicketResponse);
|
|
||||||
if (ticketInfo == null)
|
|
||||||
{
|
|
||||||
return BadRequest("Failed to extract ticket info from Freshdesk response");
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the onyx `answer-with-citation` request
|
// create the onyx `answer-with-citation` request
|
||||||
var onyxRequestModel = new OnyxAnswerWithCitationRequestModel(ticketInfo.DescriptionText);
|
var onyxRequestModel = new OnyxAnswerWithCitationRequestModel(model.TicketDescriptionText);
|
||||||
var onyxRequest = new HttpRequestMessage(HttpMethod.Post,
|
var onyxRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||||
string.Format("{0}/query/answer-with-citation", _billingSettings.Onyx.BaseUrl))
|
string.Format("{0}/query/answer-with-citation", _billingSettings.Onyx.BaseUrl))
|
||||||
{
|
{
|
||||||
@ -249,29 +229,6 @@ public class FreshdeskController : Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<FreshdeskViewTicketModel> ExtractTicketInfoFromResponse(HttpResponseMessage getTicketResponse)
|
|
||||||
{
|
|
||||||
var responseString = string.Empty;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
responseString = await getTicketResponse.Content.ReadAsStringAsync();
|
|
||||||
var ticketInfo = JsonSerializer.Deserialize<FreshdeskViewTicketModel>(responseString,
|
|
||||||
options: new System.Text.Json.JsonSerializerOptions
|
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return ticketInfo;
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError("Error deserializing ticket info from Freshdesk response. Response: {0}. Exception {1}",
|
|
||||||
responseString, ex.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> CallFreshdeskApiAsync(HttpRequestMessage request, int retriedCount = 0)
|
private async Task<HttpResponseMessage> CallFreshdeskApiAsync(HttpRequestMessage request, int retriedCount = 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
// FIXME: Update this file to be null safe and then delete the line below
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Bit.Billing.Models;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
public class FreshdeskViewTicketModel
|
|
||||||
{
|
|
||||||
[JsonPropertyName("spam")]
|
|
||||||
public bool? Spam { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("priority")]
|
|
||||||
public int? Priority { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("source")]
|
|
||||||
public int? Source { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("status")]
|
|
||||||
public int? Status { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("subject")]
|
|
||||||
public string Subject { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("support_email")]
|
|
||||||
public string SupportEmail { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("id")]
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("description")]
|
|
||||||
public string Description { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("description_text")]
|
|
||||||
public string DescriptionText { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("created_at")]
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("updated_at")]
|
|
||||||
public DateTime UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("tags")]
|
|
||||||
public List<string> Tags { get; set; }
|
|
||||||
}
|
|
@ -16,3 +16,9 @@ public class FreshdeskWebhookModel
|
|||||||
[JsonPropertyName("ticket_tags")]
|
[JsonPropertyName("ticket_tags")]
|
||||||
public string TicketTags { get; set; }
|
public string TicketTags { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class FreshdeskOnyxAiWebhookModel : FreshdeskWebhookModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("ticket_description_text")]
|
||||||
|
public string TicketDescriptionText { get; set; }
|
||||||
|
}
|
||||||
|
@ -112,7 +112,7 @@ public class FreshdeskControllerTests
|
|||||||
[BitAutoData((string)null)]
|
[BitAutoData((string)null)]
|
||||||
[BitAutoData(WebhookKey, null)]
|
[BitAutoData(WebhookKey, null)]
|
||||||
public async Task PostWebhookOnyxAi_InvalidWebhookKey_results_in_BadRequest(
|
public async Task PostWebhookOnyxAi_InvalidWebhookKey_results_in_BadRequest(
|
||||||
string freshdeskWebhookKey, FreshdeskWebhookModel model,
|
string freshdeskWebhookKey, FreshdeskOnyxAiWebhookModel model,
|
||||||
BillingSettings billingSettings, SutProvider<FreshdeskController> sutProvider)
|
BillingSettings billingSettings, SutProvider<FreshdeskController> sutProvider)
|
||||||
{
|
{
|
||||||
sutProvider.GetDependency<IOptions<BillingSettings>>()
|
sutProvider.GetDependency<IOptions<BillingSettings>>()
|
||||||
@ -124,59 +124,11 @@ public class FreshdeskControllerTests
|
|||||||
Assert.Equal(StatusCodes.Status400BadRequest, statusCodeResult.StatusCode);
|
Assert.Equal(StatusCodes.Status400BadRequest, statusCodeResult.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(WebhookKey)]
|
|
||||||
public async Task PostWebhookOnyxAi_invalid_ticketid_results_in_BadRequest(
|
|
||||||
string freshdeskWebhookKey, FreshdeskWebhookModel model, SutProvider<FreshdeskController> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<IOptions<BillingSettings>>()
|
|
||||||
.Value.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey);
|
|
||||||
|
|
||||||
var mockHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
|
|
||||||
var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
|
|
||||||
mockHttpMessageHandler.Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
|
|
||||||
.Returns(mockResponse);
|
|
||||||
var httpClient = new HttpClient(mockHttpMessageHandler);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IHttpClientFactory>().CreateClient("FreshdeskApi").Returns(httpClient);
|
|
||||||
|
|
||||||
var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model);
|
|
||||||
|
|
||||||
var result = Assert.IsAssignableFrom<BadRequestObjectResult>(response);
|
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(WebhookKey)]
|
|
||||||
public async Task PostWebhookOnyxAi_invalid_freshdesk_response_results_in_BadRequest(
|
|
||||||
string freshdeskWebhookKey, FreshdeskWebhookModel model,
|
|
||||||
SutProvider<FreshdeskController> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<IOptions<BillingSettings>>()
|
|
||||||
.Value.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey);
|
|
||||||
|
|
||||||
var mockHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
|
|
||||||
var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
Content = new StringContent("non json content. expect json deserializer to throw error")
|
|
||||||
};
|
|
||||||
mockHttpMessageHandler.Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
|
|
||||||
.Returns(mockResponse);
|
|
||||||
var httpClient = new HttpClient(mockHttpMessageHandler);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IHttpClientFactory>().CreateClient("FreshdeskApi").Returns(httpClient);
|
|
||||||
|
|
||||||
var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model);
|
|
||||||
|
|
||||||
var result = Assert.IsAssignableFrom<BadRequestObjectResult>(response);
|
|
||||||
Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(WebhookKey)]
|
[BitAutoData(WebhookKey)]
|
||||||
public async Task PostWebhookOnyxAi_invalid_onyx_response_results_in_BadRequest(
|
public async Task PostWebhookOnyxAi_invalid_onyx_response_results_in_BadRequest(
|
||||||
string freshdeskWebhookKey, FreshdeskWebhookModel model,
|
string freshdeskWebhookKey, FreshdeskOnyxAiWebhookModel model,
|
||||||
FreshdeskViewTicketModel freshdeskTicketInfo, SutProvider<FreshdeskController> sutProvider)
|
SutProvider<FreshdeskController> sutProvider)
|
||||||
{
|
{
|
||||||
var billingSettings = sutProvider.GetDependency<IOptions<BillingSettings>>().Value;
|
var billingSettings = sutProvider.GetDependency<IOptions<BillingSettings>>().Value;
|
||||||
billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey);
|
billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey);
|
||||||
@ -184,12 +136,6 @@ public class FreshdeskControllerTests
|
|||||||
|
|
||||||
// mocking freshdesk Api request for ticket info
|
// mocking freshdesk Api request for ticket info
|
||||||
var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
|
var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
|
||||||
var mockFreshdeskResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
Content = new StringContent(JsonSerializer.Serialize(freshdeskTicketInfo))
|
|
||||||
};
|
|
||||||
mockFreshdeskHttpMessageHandler.Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
|
|
||||||
.Returns(mockFreshdeskResponse);
|
|
||||||
var freshdeskHttpClient = new HttpClient(mockFreshdeskHttpMessageHandler);
|
var freshdeskHttpClient = new HttpClient(mockFreshdeskHttpMessageHandler);
|
||||||
|
|
||||||
// mocking Onyx api response given a ticket description
|
// mocking Onyx api response given a ticket description
|
||||||
@ -211,8 +157,7 @@ public class FreshdeskControllerTests
|
|||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(WebhookKey)]
|
[BitAutoData(WebhookKey)]
|
||||||
public async Task PostWebhookOnyxAi_success(
|
public async Task PostWebhookOnyxAi_success(
|
||||||
string freshdeskWebhookKey, FreshdeskWebhookModel model,
|
string freshdeskWebhookKey, FreshdeskOnyxAiWebhookModel model,
|
||||||
FreshdeskViewTicketModel freshdeskTicketInfo,
|
|
||||||
OnyxAnswerWithCitationResponseModel onyxResponse,
|
OnyxAnswerWithCitationResponseModel onyxResponse,
|
||||||
SutProvider<FreshdeskController> sutProvider)
|
SutProvider<FreshdeskController> sutProvider)
|
||||||
{
|
{
|
||||||
@ -220,18 +165,8 @@ public class FreshdeskControllerTests
|
|||||||
billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey);
|
billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey);
|
||||||
billingSettings.Onyx.BaseUrl.Returns("http://simulate-onyx-api.com/api");
|
billingSettings.Onyx.BaseUrl.Returns("http://simulate-onyx-api.com/api");
|
||||||
|
|
||||||
// mocking freshdesk Api request for ticket info (GET)
|
|
||||||
var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
|
|
||||||
var mockFreshdeskResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
Content = new StringContent(JsonSerializer.Serialize(freshdeskTicketInfo))
|
|
||||||
};
|
|
||||||
mockFreshdeskHttpMessageHandler.Send(
|
|
||||||
Arg.Is<HttpRequestMessage>(_ => _.Method == HttpMethod.Get),
|
|
||||||
Arg.Any<CancellationToken>())
|
|
||||||
.Returns(mockFreshdeskResponse);
|
|
||||||
|
|
||||||
// mocking freshdesk api add note request (POST)
|
// mocking freshdesk api add note request (POST)
|
||||||
|
var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
|
||||||
var mockFreshdeskAddNoteResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
|
var mockFreshdeskAddNoteResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
|
||||||
mockFreshdeskHttpMessageHandler.Send(
|
mockFreshdeskHttpMessageHandler.Send(
|
||||||
Arg.Is<HttpRequestMessage>(_ => _.Method == HttpMethod.Post),
|
Arg.Is<HttpRequestMessage>(_ => _.Method == HttpMethod.Post),
|
||||||
|
Reference in New Issue
Block a user