From 9c10090ededb7a4675f4f084fa17c03be988d94e Mon Sep 17 00:00:00 2001 From: Markus Meyer Date: Tue, 3 Mar 2020 15:12:11 +0100 Subject: [PATCH 1/2] #66 Refactor --- .../Controllers/MealService.cs | 4 +- .../Controllers/OrderService.cs | 136 +++++++++--------- .../ServiceBus/ButlerBus.cs | 108 ++++++++++++++ 3 files changed, 174 insertions(+), 74 deletions(-) create mode 100644 PlanB.Butler.Services/PlanB.Butler.Services/ServiceBus/ButlerBus.cs diff --git a/PlanB.Butler.Services/PlanB.Butler.Services/Controllers/MealService.cs b/PlanB.Butler.Services/PlanB.Butler.Services/Controllers/MealService.cs index 01bdb4e..5cd0511 100644 --- a/PlanB.Butler.Services/PlanB.Butler.Services/Controllers/MealService.cs +++ b/PlanB.Butler.Services/PlanB.Butler.Services/Controllers/MealService.cs @@ -293,7 +293,7 @@ public static async Task UpdateMealById( Details = e.StackTrace, Message = e.Message, }; - actionResult = new BadRequestObjectResult(mealModel); + actionResult = new BadRequestObjectResult(errorModel); } finally { @@ -480,7 +480,7 @@ public static IActionResult GetMealById( Details = e.StackTrace, Message = e.Message, }; - actionResult = new BadRequestObjectResult(mealModel); + actionResult = new BadRequestObjectResult(errorModel); } finally { diff --git a/PlanB.Butler.Services/PlanB.Butler.Services/Controllers/OrderService.cs b/PlanB.Butler.Services/PlanB.Butler.Services/Controllers/OrderService.cs index 8dda202..43ca78e 100644 --- a/PlanB.Butler.Services/PlanB.Butler.Services/Controllers/OrderService.cs +++ b/PlanB.Butler.Services/PlanB.Butler.Services/Controllers/OrderService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Net.Mime; using System.Reflection; @@ -140,78 +141,6 @@ public static async Task GetOrder( return actionResult; } - /// - /// Posts the document order. - /// - /// The message header. - /// The BLOB. - /// The log. - /// The context. - [Singleton] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [Consumes(MediaTypeNames.Application.Json)] - [ProducesResponseType(typeof(ErrorModel), StatusCodes.Status400BadRequest)] - [FunctionName(nameof(PostDocumentOrder))] - public static async void PostDocumentOrder( - [ServiceBusTrigger("q.planbutlerupdateorder", Connection = "butlerSend")]Message messageHeader, - [Blob("orders/{Label}", FileAccess.ReadWrite, Connection = "StorageSend")]CloudBlockBlob blob, - ILogger log, - ExecutionContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Guid correlationId = new Guid(messageHeader.CorrelationId); - var methodName = MethodBase.GetCurrentMethod().Name; - var trace = new Dictionary(); - EventId eventId = new EventId(correlationId.GetHashCode(), Constants.ButlerCorrelationTraceName); - try - { - string payload = Encoding.Default.GetString(messageHeader.Body); - OrdersModel orderBlob = new OrdersModel - { - Orders = new List(), - }; - orderBlob = JsonConvert.DeserializeObject(payload); - string name = string.Empty; - DateTime date = DateTime.Now; - foreach (var item in orderBlob.Orders) - { - name = item.Name; - date = item.Date; - break; - } - - var stringDate = date.ToString("yyyy-MM-dd"); - - log.LogInformation(correlationId, $"'{methodName}' - success", trace); - - blob.Metadata.Add("user", name); - blob.Metadata.Add("date", stringDate); - blob.Metadata.Add(Constants.ButlerCorrelationTraceName, correlationId.ToString().Replace("-", string.Empty)); - await blob.UploadTextAsync(payload); - await blob.SetMetadataAsync(); - trace.Add("data", payload); - trace.Add("date", date.ToString()); - trace.Add("name", name); - trace.Add("requestbody", messageHeader.Body.ToString()); - } - catch (Exception e) - { - trace.Add(string.Format("{0} - {1}", methodName, "rejected"), e.Message); - trace.Add(string.Format("{0} - {1} - StackTrace", methodName, "rejected"), e.StackTrace); - log.LogInformation(correlationId, $"'{methodName}' - rejected", trace); - log.LogError(correlationId, $"'{methodName}' - rejected", trace); - } - finally - { - log.LogTrace(eventId, $"'{methodName}' - finished"); - log.LogInformation(correlationId, $"'{methodName}' - finished", trace); - } - } - /// /// Creates the order. /// @@ -285,5 +214,68 @@ public static IActionResult CreateOrder( return actionResult; } + + /// + /// Gets the order by identifier. + /// + /// The req. + /// The identifier. + /// The BLOB. + /// The log. + /// The context. + /// Order. + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ErrorModel), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(OrderModel), StatusCodes.Status200OK)] + [FunctionName("GetOrderById")] + public static IActionResult GetOrderById( + [HttpTrigger(AuthorizationLevel.Function, "get", Route = "orders/{id}")] HttpRequest req, + string id, + [Blob("orders/{id}.json", FileAccess.ReadWrite, Connection = "StorageSend")] string blob, + ILogger log, + ExecutionContext context) + { + Guid correlationId = Util.ReadCorrelationId(req.Headers); + var methodName = MethodBase.GetCurrentMethod().Name; + var trace = new Dictionary(); + EventId eventId = new EventId(correlationId.GetHashCode(), Constants.ButlerCorrelationTraceName); + IActionResult actionResult = null; + + OrderModel orderModel = null; + using (log.BeginScope("Method:{methodName} CorrelationId:{CorrelationId} Label:{Label}", methodName, correlationId.ToString(), context.InvocationId.ToString())) + { + try + { + trace.Add(Constants.ButlerCorrelationTraceName, correlationId.ToString()); + trace.Add("id", id); + orderModel = JsonConvert.DeserializeObject(blob); + + log.LogInformation(correlationId, $"'{methodName}' - success", trace); + actionResult = new OkObjectResult(orderModel); + } + catch (Exception e) + { + trace.Add(string.Format("{0} - {1}", methodName, "rejected"), e.Message); + trace.Add(string.Format("{0} - {1} - StackTrace", methodName, "rejected"), e.StackTrace); + log.LogInformation(correlationId, $"'{methodName}' - rejected", trace); + log.LogError(correlationId, $"'{methodName}' - rejected", trace); + + ErrorModel errorModel = new ErrorModel() + { + CorrelationId = correlationId, + Details = e.StackTrace, + Message = e.Message, + }; + actionResult = new BadRequestObjectResult(errorModel); + } + finally + { + log.LogTrace(eventId, $"'{methodName}' - finished"); + log.LogInformation(correlationId, $"'{methodName}' - finished", trace); + } + } + + return actionResult; + } } } diff --git a/PlanB.Butler.Services/PlanB.Butler.Services/ServiceBus/ButlerBus.cs b/PlanB.Butler.Services/PlanB.Butler.Services/ServiceBus/ButlerBus.cs new file mode 100644 index 0000000..3796c51 --- /dev/null +++ b/PlanB.Butler.Services/PlanB.Butler.Services/ServiceBus/ButlerBus.cs @@ -0,0 +1,108 @@ +// Copyright (c) PlanB. GmbH. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.Azure.ServiceBus; +using Microsoft.Azure.WebJobs; +using Microsoft.Extensions.Logging; +using Microsoft.WindowsAzure.Storage.Blob; +using Newtonsoft.Json; +using PlanB.Butler.Services.Extensions; +using PlanB.Butler.Services.Models; + +namespace PlanB.Butler.Services.ServiceBus +{ + /// + /// ButlerBus. + /// + public static class ButlerBus + { + /// + /// The meta date. + /// + private const string MetaDate = "date"; + + /// + /// The meta user. + /// + private const string MetaUser = "user"; + + /// + /// Saves the order. + /// + /// The message header. + /// The BLOB. + /// The log. + /// The context. + [Singleton] + [FunctionName(nameof(PushOrder))] + public static void PushOrder( + [ServiceBusTrigger("q.planbutlerupdateorder", Connection = "butlerSend")]Message messageHeader, + [Blob("orders/{Label}", FileAccess.ReadWrite, Connection = "StorageSend")]CloudBlockBlob blob, + ILogger log, + ExecutionContext context) + { + Guid correlationId = new Guid(messageHeader.CorrelationId); + var methodName = MethodBase.GetCurrentMethod().Name; + var trace = new Dictionary(); + EventId eventId = new EventId(correlationId.GetHashCode(), Constants.ButlerCorrelationTraceName); + using (log.BeginScope("Method:{methodName} CorrelationId:{CorrelationId} Label:{Label}", methodName, correlationId.ToString(), context.InvocationId.ToString())) + { + try + { + trace.Add(Constants.ButlerCorrelationTraceHeader, correlationId.ToString()); + string payload = Encoding.Default.GetString(messageHeader.Body); + trace.Add("payload", payload); + OrdersModel ordersModel = new OrdersModel + { + Orders = new List(), + }; + ordersModel = JsonConvert.DeserializeObject(payload); + + trace.Add("ordersModel.LoginName", ordersModel.LoginName); + string name = System.Web.HttpUtility.UrlEncode(ordersModel.LoginName); + trace.Add("name", name); + + DateTime date = ordersModel.Date; + var formatedDate = date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + trace.Add("formatedDate", formatedDate); + + log.LogInformation(correlationId, $"'{methodName}' - success", trace); + + blob.Metadata.Add(MetaUser, name); + blob.Metadata.Add(MetaDate, formatedDate); + blob.Metadata.Add(Constants.ButlerCorrelationTraceName, correlationId.ToString().Replace("-", string.Empty)); + + Task upload = blob.UploadTextAsync(payload); + upload.Wait(); + trace.Add("upload", "success"); + + Task metaData = blob.SetMetadataAsync(); + metaData.Wait(); + trace.Add("metaData", "success"); + + log.LogInformation(correlationId, $"'{methodName}' - success", trace); + } + catch (Exception e) + { + trace.Add(string.Format("{0} - {1}", methodName, "rejected"), e.Message); + trace.Add(string.Format("{0} - {1} - StackTrace", methodName, "rejected"), e.StackTrace); + log.LogInformation(correlationId, $"'{methodName}' - rejected", trace); + log.LogError(correlationId, $"'{methodName}' - rejected", trace); + } + finally + { + log.LogTrace(eventId, $"'{methodName}' - finished"); + log.LogInformation(correlationId, $"'{methodName}' - finished", trace); + } + } + } + } +} From 63ecd5003bb81359011940df2483d57087d741d7 Mon Sep 17 00:00:00 2001 From: Markus Meyer Date: Wed, 4 Mar 2020 06:57:12 +0100 Subject: [PATCH 2/2] #78 Use Service for Meal --- PlanB.Butler.Bot/BotConfig.cs | 16 ++++ PlanB.Butler.Bot/Constants.cs | 26 +++++++ PlanB.Butler.Bot/Dialogs/CreditDialog.cs | 2 +- PlanB.Butler.Bot/Dialogs/DailyCreditDialog.cs | 2 +- ...letOrderDialog.cs => DeleteOrderDialog.cs} | 66 ++++++++-------- PlanB.Butler.Bot/Dialogs/ExcellDialog.cs | 3 +- PlanB.Butler.Bot/Dialogs/InterruptDialog.cs | 6 +- PlanB.Butler.Bot/Dialogs/MainDialog.cs | 53 +++++++++---- PlanB.Butler.Bot/Dialogs/NextOrder.cs | 2 +- PlanB.Butler.Bot/Dialogs/OrderDialog.cs | 28 +++---- .../Dialogs/OrderForOtherDayDialog.cs | 2 +- PlanB.Butler.Bot/Dialogs/OverviewDialog.cs | 76 +++++++++++++------ PlanB.Butler.Bot/Dialogs/PlanDialog.cs | 36 +++++---- PlanB.Butler.Bot/Models/MealModel.cs | 68 +++++++++++++++++ PlanB.Butler.Bot/Services/MealService.cs | 72 ++++++++++++++++++ PlanB.Butler.Bot/Startup.cs | 8 ++ .../PlanB.Butler.Library/BotModels/Plan.cs | 2 + 17 files changed, 355 insertions(+), 113 deletions(-) create mode 100644 PlanB.Butler.Bot/Constants.cs rename PlanB.Butler.Bot/Dialogs/{DeletOrderDialog.cs => DeleteOrderDialog.cs} (93%) create mode 100644 PlanB.Butler.Bot/Models/MealModel.cs create mode 100644 PlanB.Butler.Bot/Services/MealService.cs diff --git a/PlanB.Butler.Bot/BotConfig.cs b/PlanB.Butler.Bot/BotConfig.cs index a59c3df..03a577f 100644 --- a/PlanB.Butler.Bot/BotConfig.cs +++ b/PlanB.Butler.Bot/BotConfig.cs @@ -8,6 +8,22 @@ namespace PlanB.Butler.Bot /// public class BotConfig { + /// + /// Gets or sets the butler service URL. + /// + /// + /// The butler service URL. + /// + public string ButlerServiceUrl { get; set; } + + /// + /// Gets or sets the butler service key. + /// + /// + /// The butler service key. + /// + public string ButlerServiceKey { get; set; } + /// /// Gets or sets the storage account URL. /// diff --git a/PlanB.Butler.Bot/Constants.cs b/PlanB.Butler.Bot/Constants.cs new file mode 100644 index 0000000..6b08d02 --- /dev/null +++ b/PlanB.Butler.Bot/Constants.cs @@ -0,0 +1,26 @@ +// Copyright (c) PlanB. GmbH. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace PlanB.Butler.Bot +{ + /// + /// Constants. + /// + internal static class Constants + { + /// + /// The butler correlation trace name. + /// + internal const string ButlerCorrelationTraceName = "Butler-Correlation-Id"; + + /// + /// The butler correlation trace header. + /// + internal const string ButlerCorrelationTraceHeader = "ButlerCorrelationId"; + + /// + /// The functions key header. + /// + internal const string FunctionsKeyHeader = "x-functions-key"; + } +} \ No newline at end of file diff --git a/PlanB.Butler.Bot/Dialogs/CreditDialog.cs b/PlanB.Butler.Bot/Dialogs/CreditDialog.cs index 7b70063..79d89d0 100644 --- a/PlanB.Butler.Bot/Dialogs/CreditDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/CreditDialog.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; -namespace PlanB.Butler.Bot +namespace PlanB.Butler.Bot.Dialogs { public class CreditDialog : ComponentDialog { diff --git a/PlanB.Butler.Bot/Dialogs/DailyCreditDialog.cs b/PlanB.Butler.Bot/Dialogs/DailyCreditDialog.cs index 7c17721..4e780d2 100644 --- a/PlanB.Butler.Bot/Dialogs/DailyCreditDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/DailyCreditDialog.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; -namespace PlanB.Butler.Bot +namespace PlanB.Butler.Bot.Dialogs { /// /// DailyCreditDialog. diff --git a/PlanB.Butler.Bot/Dialogs/DeletOrderDialog.cs b/PlanB.Butler.Bot/Dialogs/DeleteOrderDialog.cs similarity index 93% rename from PlanB.Butler.Bot/Dialogs/DeletOrderDialog.cs rename to PlanB.Butler.Bot/Dialogs/DeleteOrderDialog.cs index 6a18756..b88cf39 100644 --- a/PlanB.Butler.Bot/Dialogs/DeletOrderDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/DeleteOrderDialog.cs @@ -1,6 +1,9 @@ -using System; +// Copyright (c) PlanB. GmbH. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System; using System.Collections.Generic; -using System.Net; +using System.Net.Http; using System.Reflection; using System.Resources; using System.Threading; @@ -13,9 +16,8 @@ using Microsoft.Bot.Schema; using Microsoft.Extensions.Options; using Newtonsoft.Json; -using PlanB.Butler.Bot; -namespace PlanB.Butler.Bot +namespace PlanB.Butler.Bot.Dialogs { /// /// DeleteOrderDialog. @@ -23,34 +25,35 @@ namespace PlanB.Butler.Bot /// public class DeleteOrderDialog : ComponentDialog { + /// + /// The client factory. + /// + private readonly IHttpClientFactory clientFactory; + + /// + /// The bot configuration. + /// + private readonly IOptions botConfig; + static Plan plan = new Plan(); static Plan orderedfood = new Plan(); static int valueDay; const double grand = 3.30; static string dayName; + [Obsolete("Why is this still in use?")] static string[] weekDays = { "Montag", "Dienstag", "Mitwoch", "Donnerstag", "Freitag" }; + [Obsolete("Why is this still in use?")] static string[] weekDaysEN = { "monday", "tuesday", "wednesday", "thursday", "friday" }; static int indexer = 0; + [Obsolete("Why is this still in use?")] static string[] companyStatus = { "intern", "extern", "internship" }; + [Obsolete("Why is this still in use?")] static string[] companyStatusD = { "Für mich", "Kunde", "Praktikant" }; + [Obsolete("Naming Conventions!")] static Order obj = new Order(); - /// - /// DeletDialogTimePrompt. - /// DeletDialogWhoPrompt - /// NextOrderDialogMyself - /// NextOrderDialolTrainee - /// NextOrderDialogCostumer - /// DeletDialogNoOrder - /// DeletDialogDeleteSucess - /// DeletDialogDeletePrompt - /// DeletDialogYes - /// DeletDialogNo - /// OtherDayDialogError2. - /// - private static string deletDialogTimePrompt = string.Empty; - private static string deletDialogWhoPrompt = string.Empty; + private static string deletDialogWhoPrompt = string.Empty; private static string nextOrderDialogMyself = string.Empty; private static string nextOrderDialogTrainee = string.Empty; private static string nextOrderDialogCostumer = string.Empty; @@ -61,20 +64,18 @@ public class DeleteOrderDialog : ComponentDialog private static string deletDialogNo = string.Empty; private static string otherDayDialogError2 = string.Empty; - - /// - /// The bot configuration. - /// - private readonly IOptions botConfig; - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration. - public DeleteOrderDialog(IOptions config, IBotTelemetryClient telemetryClient) + /// The telemetry client. + /// The HTTP client factory. + public DeleteOrderDialog(IOptions config, IBotTelemetryClient telemetryClient, IHttpClientFactory httpClientFactory) : base(nameof(DeleteOrderDialog)) { ResourceManager rm = new ResourceManager("PlanB.Butler.Bot.Dictionary.Dialogs", Assembly.GetExecutingAssembly()); + this.clientFactory = httpClientFactory; + this.botConfig = config; deletDialogTimePrompt = rm.GetString("DeletDialog_TimePrompt"); deletDialogWhoPrompt = rm.GetString("DeletDialog_WhoPrompt"); @@ -88,15 +89,10 @@ public DeleteOrderDialog(IOptions config, IBotTelemetryClient telemet deletDialogNo = rm.GetString("no"); otherDayDialogError2 = rm.GetString("OtherDayDialog_Error2"); + //// Get the Plan + //string food = BotMethods.GetDocument("eatingplan", "ButlerOverview.json", this.botConfig.Value.StorageAccountUrl, this.botConfig.Value.StorageAccountKey); - - - this.botConfig = config; - - // Get the Plan - string food = BotMethods.GetDocument("eatingplan", "ButlerOverview.json", this.botConfig.Value.StorageAccountUrl, this.botConfig.Value.StorageAccountKey); - - plan = JsonConvert.DeserializeObject(food); + //plan = JsonConvert.DeserializeObject(food); // This array defines how the Waterfall will execute. var waterfallSteps = new WaterfallStep[] { diff --git a/PlanB.Butler.Bot/Dialogs/ExcellDialog.cs b/PlanB.Butler.Bot/Dialogs/ExcellDialog.cs index ece386c..d6d6494 100644 --- a/PlanB.Butler.Bot/Dialogs/ExcellDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/ExcellDialog.cs @@ -8,7 +8,7 @@ using Microsoft.Bot.Builder.Dialogs.Choices; using Microsoft.Extensions.Options; -namespace PlanB.Butler.Bot +namespace PlanB.Butler.Bot.Dialogs { /// /// ExcellDialog. @@ -16,6 +16,7 @@ namespace PlanB.Butler.Bot /// public class ExcellDialog : ComponentDialog { + [Obsolete("This has to be replaced")] private static string[] months = { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" }; private static string indexer = "0"; diff --git a/PlanB.Butler.Bot/Dialogs/InterruptDialog.cs b/PlanB.Butler.Bot/Dialogs/InterruptDialog.cs index 17b2a9e..7adc456 100644 --- a/PlanB.Butler.Bot/Dialogs/InterruptDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/InterruptDialog.cs @@ -17,7 +17,7 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; -namespace PlanB.Butler.Bot +namespace PlanB.Butler.Bot.Dialogs { /// /// InterruptDialog. @@ -35,14 +35,14 @@ public class InterruptDialog : ComponentDialog private static Plan plan = new Plan(); private readonly IOptions botConfig; - public InterruptDialog(string v, IOptions config, IBotTelemetryClient telemetryClient) + public InterruptDialog(string v, IOptions config, IBotTelemetryClient telemetryClient, IHttpClientFactory clientFactory) : base(nameof(InterruptDialog)) { ResourceManager rm = new ResourceManager("PlanB.Butler.Bot.Dictionary.Dialogs", Assembly.GetExecutingAssembly()); interruptDialogHelpText = rm.GetString("InterruptDialog_HelpText"); this.botConfig = config; - this.AddDialog(new OverviewDialog(config, telemetryClient)); + this.AddDialog(new OverviewDialog(config, telemetryClient, clientFactory)); this.AddDialog(new ExcellDialog(config, telemetryClient)); } diff --git a/PlanB.Butler.Bot/Dialogs/MainDialog.cs b/PlanB.Butler.Bot/Dialogs/MainDialog.cs index 1c2d9d5..bacc88d 100644 --- a/PlanB.Butler.Bot/Dialogs/MainDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/MainDialog.cs @@ -1,25 +1,46 @@ -namespace PlanB.Butler.Bot -{ - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using AdaptiveCards; - using Microsoft.Bot.Builder; - using Microsoft.Bot.Builder.Dialogs; - using Microsoft.Bot.Builder.Dialogs.Choices; - using Microsoft.Bot.Schema; - using Microsoft.Extensions.Options; - using Newtonsoft.Json.Linq; - using PlanB.Butler.Bot; +// Copyright (c) PlanB. GmbH. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +using AdaptiveCards; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Options; +using Newtonsoft.Json.Linq; +using PlanB.Butler.Bot; +namespace PlanB.Butler.Bot.Dialogs +{ + /// + /// MainDialog. + /// + /// public class MainDialog : InterruptDialog { - public MainDialog(IBotTelemetryClient telemetryClient, IOptions config) - : base(nameof(MainDialog), config,telemetryClient) + /// + /// The client factory. + /// + private readonly IHttpClientFactory clientFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The telemetry client. + /// The configuration. + /// The HTTP client factory. + public MainDialog(IBotTelemetryClient telemetryClient, IOptions config, IHttpClientFactory httpClientFactory) + : base(nameof(MainDialog), config, telemetryClient, httpClientFactory) { // Set the telemetry client for this and all child dialogs. this.TelemetryClient = telemetryClient; + this.clientFactory = httpClientFactory; // This array defines how the Waterfall will execute. var waterfallSteps = new WaterfallStep[] diff --git a/PlanB.Butler.Bot/Dialogs/NextOrder.cs b/PlanB.Butler.Bot/Dialogs/NextOrder.cs index 2bb11ad..b1239a2 100644 --- a/PlanB.Butler.Bot/Dialogs/NextOrder.cs +++ b/PlanB.Butler.Bot/Dialogs/NextOrder.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; -namespace PlanB.Butler.Bot +namespace PlanB.Butler.Bot.Dialogs { /// /// NextOrder. diff --git a/PlanB.Butler.Bot/Dialogs/OrderDialog.cs b/PlanB.Butler.Bot/Dialogs/OrderDialog.cs index d02aa24..9ce5e7f 100644 --- a/PlanB.Butler.Bot/Dialogs/OrderDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/OrderDialog.cs @@ -1,18 +1,18 @@ -namespace PlanB.Butler.Bot +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +using BotLibraryV2; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace PlanB.Butler.Bot.Dialogs { - using System; - using System.Collections.Generic; - using System.Net; - using System.Threading; - using System.Threading.Tasks; - using BotLibraryV2; - using Microsoft.Bot.Builder; - using Microsoft.Bot.Builder.Dialogs; - using Microsoft.Bot.Builder.Dialogs.Choices; - using Microsoft.Bot.Schema; - using Microsoft.Extensions.Options; - using Newtonsoft.Json; - public class OrderDialog : ComponentDialog { private static Plan plan = new Plan(); diff --git a/PlanB.Butler.Bot/Dialogs/OrderForOtherDayDialog.cs b/PlanB.Butler.Bot/Dialogs/OrderForOtherDayDialog.cs index caaa352..9d8d832 100644 --- a/PlanB.Butler.Bot/Dialogs/OrderForOtherDayDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/OrderForOtherDayDialog.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; -namespace PlanB.Butler.Bot +namespace PlanB.Butler.Bot.Dialogs { /// /// OrderForOtherDayDialog. diff --git a/PlanB.Butler.Bot/Dialogs/OverviewDialog.cs b/PlanB.Butler.Bot/Dialogs/OverviewDialog.cs index 0ae0476..0dcd507 100644 --- a/PlanB.Butler.Bot/Dialogs/OverviewDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/OverviewDialog.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Net.Http; using System.Reflection; using System.Resources; using System.Threading; @@ -15,9 +16,9 @@ using Microsoft.Bot.Schema; using Microsoft.Extensions.Options; using Newtonsoft.Json; -using PlanB.Butler.Bot; +using PlanB.Butler.Bot.Services; -namespace PlanB.Butler.Bot +namespace PlanB.Butler.Bot.Dialogs { /// /// OverviewDialog. @@ -25,13 +26,18 @@ namespace PlanB.Butler.Bot /// public class OverviewDialog : ComponentDialog { + /// + /// The client factory. + /// + private readonly IHttpClientFactory clientFactory; + private static Plan plan = new Plan(); private static int dayId; + [Obsolete("Why is this still in use?")] private static string[] weekDaysEN = { "monday", "tuesday", "wednesday", "thursday", "friday" }; private static int indexer = 0; private static bool valid; - private static ComponentDialog[] dialogs; /// /// OverviewDialogHelp. /// OverviewDialogOrderFood @@ -42,6 +48,7 @@ public class OverviewDialog : ComponentDialog /// OverviewDialogWhatNow /// OverviewDialogError. /// + private static ComponentDialog[] dialogs; private static string overviewDialogHelp = string.Empty; private static string overviewDialogOrderFood = string.Empty; @@ -55,19 +62,24 @@ public class OverviewDialog : ComponentDialog // In this Array you can Easy modify your choice List. private static string[] choices; - + /// /// The bot configuration. /// private readonly IOptions botConfig; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration. - public OverviewDialog(IOptions config, IBotTelemetryClient telemetryClient) + /// The telemetry client. + /// The HTTP client factory. + public OverviewDialog(IOptions config, IBotTelemetryClient telemetryClient, IHttpClientFactory httpClientFactory) : base(nameof(OverviewDialog)) { + this.botConfig = config; + this.clientFactory = httpClientFactory; + ResourceManager rm = new ResourceManager("PlanB.Butler.Bot.Dictionary.Dialogs", Assembly.GetExecutingAssembly()); overviewDialogHelp = rm.GetString("OverviewDialog_Help"); @@ -80,15 +92,13 @@ public OverviewDialog(IOptions config, IBotTelemetryClient telemetryC overviewDialogError = rm.GetString("OtherDayDialog_Error2"); otherDayDialogOrder = rm.GetString("OtherDayDialog_Order"); - this.botConfig = config; - - choices = new string[]{ overviewDialogOrderFood, overviewDialogOtherDay, overviewDialogDeleteOrder, overviewDialogShowDepts, overviewDialogDaysOrder }; + choices = new string[] { overviewDialogOrderFood, overviewDialogOtherDay, overviewDialogDeleteOrder, overviewDialogShowDepts, overviewDialogDaysOrder }; OrderDialog orderDialog = new OrderDialog(config, telemetryClient); NextOrder nextorderDialog = new NextOrder(config, telemetryClient); PlanDialog planDialog = new PlanDialog(config, telemetryClient); CreditDialog creditDialog = new CreditDialog(config, telemetryClient); OrderForOtherDayDialog orderForAnotherDay = new OrderForOtherDayDialog(config, telemetryClient); - DeleteOrderDialog deleteOrderDialog = new DeleteOrderDialog(config, telemetryClient); + DeleteOrderDialog deleteOrderDialog = new DeleteOrderDialog(config, telemetryClient, this.clientFactory); List dialogsList = new List(); DailyCreditDialog dailyCreditDialog = new DailyCreditDialog(config, telemetryClient); ExcellDialog excellDialog = new ExcellDialog(config, telemetryClient); @@ -117,7 +127,7 @@ public OverviewDialog(IOptions config, IBotTelemetryClient telemetryC this.AddDialog(new CreditDialog(config, telemetryClient)); this.AddDialog(new PlanDialog(config, telemetryClient)); this.AddDialog(new OrderForOtherDayDialog(config, telemetryClient)); - this.AddDialog(new DeleteOrderDialog(config, telemetryClient)); + this.AddDialog(new DeleteOrderDialog(config, telemetryClient, this.clientFactory)); this.AddDialog(new NextOrder(config, telemetryClient)); this.AddDialog(new DailyCreditDialog(config, telemetryClient)); this.AddDialog(new ExcellDialog(config, telemetryClient)); @@ -166,32 +176,50 @@ private async Task InitialStepAsync(WaterfallStepContext stepC valid = false; } - List choise = new List(); - var day = plan.Planday[dayId]; - if (day.Restaurant1 != null) + IMealService mealService = new MealService(this.clientFactory.CreateClient(), this.botConfig.Value); + var meals = await mealService.GetMeals(string.Empty, string.Empty); + var mealEnumerator = meals.GetEnumerator(); + PlanDay day = new PlanDay(); + while (mealEnumerator.MoveNext()) { - choise.Add(day.Restaurant1); - } + if (string.IsNullOrEmpty(day.Restaurant1)) + { + day.Restaurant1 = mealEnumerator.Current.Restaurant; + } - if (day.Restaurant2 != null) - { - choise.Add(day.Restaurant2); + if (string.IsNullOrEmpty(day.Restaurant2) && day.Restaurant1 != mealEnumerator.Current.Restaurant) + { + day.Restaurant2 = mealEnumerator.Current.Restaurant; + } } + List restaurants = new List(); + //var day = plan.Planday[dayId]; + //if (day.Restaurant1 != null) + //{ + // restaurants.Add(day.Restaurant1); + //} + + //if (day.Restaurant2 != null) + //{ + // restaurants.Add(day.Restaurant2); + //} + string msg = string.Empty; - bool temp = false;; - foreach (var item in choise) + bool temp = false; + + // TODO: What is the idea of this? + foreach (var restaurant in restaurants) { if (temp == false) { - var otherDayDialog_Order1 = MessageFactory.Text(string.Format(otherDayDialogOrder, item)); + var otherDayDialog_Order1 = MessageFactory.Text(string.Format(otherDayDialogOrder, restaurant)); msg = $" {otherDayDialog_Order1}"; temp = true; } else { - - + // TODO: ?? } } diff --git a/PlanB.Butler.Bot/Dialogs/PlanDialog.cs b/PlanB.Butler.Bot/Dialogs/PlanDialog.cs index b087dd1..e9cd93a 100644 --- a/PlanB.Butler.Bot/Dialogs/PlanDialog.cs +++ b/PlanB.Butler.Bot/Dialogs/PlanDialog.cs @@ -1,20 +1,24 @@ -namespace PlanB.Butler.Bot -{ - using System; - using System.Collections.Generic; - using System.Reflection; - using System.Resources; - using System.Threading; - using System.Threading.Tasks; - using BotLibraryV2; - using Microsoft.Bot.Builder; - using Microsoft.Bot.Builder.Dialogs; - using Microsoft.Bot.Builder.Dialogs.Choices; - using Microsoft.Bot.Schema; - using Microsoft.Extensions.Localization; - using Microsoft.Extensions.Options; - using Newtonsoft.Json; +// Copyright (c) PlanB. GmbH. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Resources; +using System.Threading; +using System.Threading.Tasks; +using BotLibraryV2; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace PlanB.Butler.Bot.Dialogs +{ public class PlanDialog : ComponentDialog { /// diff --git a/PlanB.Butler.Bot/Models/MealModel.cs b/PlanB.Butler.Bot/Models/MealModel.cs new file mode 100644 index 0000000..736fac3 --- /dev/null +++ b/PlanB.Butler.Bot/Models/MealModel.cs @@ -0,0 +1,68 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace PlanB.Butler.Bot.Models +{ + /// + /// MealModel. + /// + public class MealModel + { + /// + /// Gets or sets the identifier. + /// + /// + /// The identifier. + /// + [JsonProperty("id")] + public string Id { get; set; } + + /// + /// Gets or sets the correlation identifier. + /// + /// + /// The correlation identifier. + /// + [JsonProperty("correlationId")] + public Guid? CorrelationId { get; set; } + + /// + /// Gets or sets the date. + /// + /// + /// The date. + /// + [JsonProperty("date")] + public DateTime Date { get; set; } + + /// + /// Gets or sets the price. + /// + /// + /// The price. + /// + [JsonProperty("price")] + public double Price { get; set; } + + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Gets or sets the restaurant. + /// + /// + /// The restaurant. + /// + [JsonProperty("restaurant")] + public string Restaurant { get; set; } + } +} diff --git a/PlanB.Butler.Bot/Services/MealService.cs b/PlanB.Butler.Bot/Services/MealService.cs new file mode 100644 index 0000000..e2764a1 --- /dev/null +++ b/PlanB.Butler.Bot/Services/MealService.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using PlanB.Butler.Bot.Models; + +namespace PlanB.Butler.Bot.Services +{ + /// + /// MealService. + /// + /// + public class MealService : IMealService + { + /// + /// The HTTP client. + /// + private readonly HttpClient httpClient; + + /// + /// The configuration. + /// + private readonly BotConfig config; + + /// + /// Initializes a new instance of the class. + /// + /// The HTTP client. + /// The bot configuration. + public MealService(HttpClient httpClient, BotConfig botConfig) + { + this.httpClient = httpClient; + this.config = botConfig; + } + + /// + /// Gets the meals. + /// + /// The start date. + /// The end date. + /// + /// Meals. + /// + public async Task> GetMeals(string startDate, string endDate) + { + var uri = this.config.ButlerServiceUrl; + this.httpClient.DefaultRequestHeaders.Add(Constants.FunctionsKeyHeader, "8NL2rP9nV8agFOGWmwTrlpcrEsIyr7rJINX3qpbZb4WEfyWgzTWH0Q=="); + var responseString = this.httpClient.GetStringAsync(uri).Result; + + var meals = JsonConvert.DeserializeObject>(responseString); + return meals; + } + } + + /// + /// IMealService. + /// + public interface IMealService + { + /// + /// Gets the meals. + /// + /// The start date. + /// The end date. + /// List of Meals. + Task> GetMeals(string startDate, string endDate); + } +} diff --git a/PlanB.Butler.Bot/Startup.cs b/PlanB.Butler.Bot/Startup.cs index 2c9c8fa..4f35afd 100644 --- a/PlanB.Butler.Bot/Startup.cs +++ b/PlanB.Butler.Bot/Startup.cs @@ -21,6 +21,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using PlanB.Butler.Bot.Dialogs; namespace PlanB.Butler.Bot { @@ -56,6 +57,13 @@ public void ConfigureServices(IServiceCollection services) services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + // The Bot needs an HttpClient to download and upload files. + //services.AddHttpClient(); + services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + + // Create the Bot Framework Adapter with error handling enabled. services.AddSingleton(); diff --git a/PlanB.Butler.Library/PlanB.Butler.Library/BotModels/Plan.cs b/PlanB.Butler.Library/PlanB.Butler.Library/BotModels/Plan.cs index c622240..ac3d3b9 100644 --- a/PlanB.Butler.Library/PlanB.Butler.Library/BotModels/Plan.cs +++ b/PlanB.Butler.Library/PlanB.Butler.Library/BotModels/Plan.cs @@ -27,10 +27,12 @@ public class PlanDay { public string Name { get; set; } + [Obsolete("Replace Restaurant1 with List")] public string Restaurant1 { get; set; } public List Meal1 { get; set; } + [Obsolete("Replace Restaurant2 with List")] public string Restaurant2 { get; set; } public List Meal2 { get; set; }