From 662863a11e7ba6a52f0bb6c56ae265614f73fd30 Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Mon, 23 Jan 2023 18:48:43 -0700 Subject: [PATCH] Export --- Archive/Controllers/ExportController.cs | 1 - ClientHub/Pages/Export.razor | 60 ++++++------ ClientHub/Pages/Export.razor.cs | 63 +++++++++--- ClientHub/Pages/RunInfo.razor | 3 +- ClientHub/Pages/RunInfo.razor.cs | 8 +- ClientHub/Program.cs | 1 + ClientHub/wwwroot/site.js | 10 +- ClientHub/wwwroot/styles/Site.css | 6 +- Server/ApiControllers/ToolTypesController.cs | 17 ++++ Server/Controllers/ExportController.cs | 2 +- Server/Data/Tests/GetExportDataApi.json | 1 + Server/OI.Metrology.Server.csproj | 3 + Server/Repositories/ToolTypesRepository.cs | 95 ++++++++++++++++++- .../Models/Stateless/IToolTypesRepository.cs | 3 +- Tests/UnitTestToolTypesController.cs | 26 +++++ 15 files changed, 247 insertions(+), 52 deletions(-) create mode 100644 Server/Data/Tests/GetExportDataApi.json diff --git a/Archive/Controllers/ExportController.cs b/Archive/Controllers/ExportController.cs index 5e19293..f2bd78f 100644 --- a/Archive/Controllers/ExportController.cs +++ b/Archive/Controllers/ExportController.cs @@ -40,7 +40,6 @@ public class ExportController : Controller { Export model = new() { - ToolType = "", StartTime = DateTime.Now.AddMonths(-1), EndTime = DateTime.Now }; diff --git a/ClientHub/Pages/Export.razor b/ClientHub/Pages/Export.razor index ea2a3ec..2e01744 100644 --- a/ClientHub/Pages/Export.razor +++ b/ClientHub/Pages/Export.razor @@ -1,6 +1,8 @@ -@page "/Export/{Model?}" +@page "/Export" @using Microsoft.AspNetCore.Components.Web +@using MudBlazor +@using OI.Metrology.Shared.DataModels @namespace OI.Metrology.ClientHub.Pages @@ -10,31 +12,31 @@
-
-
- -
- @* @Microsoft.AspNetCore.Html.ValidationMessage("ToolType", new { @class = "text-danger" }) *@ -
-
- -
-
- @* @Microsoft.AspNetCore.Html.ValidationMessage("StartDate", new { @class = "text-danger" }) *@ -
-
- -
-
- @* @Microsoft.AspNetCore.Html.ValidationMessage("EndDate", new { @class = "text-danger" }) *@ -
-
- -
- -
- @* @Microsoft.AspNetCore.Html.ValidationMessage("Exception", new { @class = "text-danger" }) *@ -
-
- -
\ No newline at end of file +@if (_TimeSpan is null || _DateRange is null || _ToolTypeNameId is null || _ToolTypeNameIdCollection is null) +{ +

Loading...

+} +else +{ + + + + + + + +
+ + @foreach (ToolTypeNameId toolTypeNameId in _ToolTypeNameIdCollection) + { + + } + + + + + + + Download +
+} diff --git a/ClientHub/Pages/Export.razor.cs b/ClientHub/Pages/Export.razor.cs index 2e30a21..4ac3f38 100644 --- a/ClientHub/Pages/Export.razor.cs +++ b/ClientHub/Pages/Export.razor.cs @@ -1,24 +1,26 @@ using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; +using MudBlazor; +using OI.Metrology.Shared.DataModels; +using OI.Metrology.Shared.Models.Stateless; +using System.Net; namespace OI.Metrology.ClientHub.Pages; public partial class Export { + protected TimeSpan? _TimeSpan; + protected DateRange? _DateRange; + protected ToolTypeNameId? _ToolTypeNameId; + protected ToolTypeNameId[]? _ToolTypeNameIdCollection; + + [Inject] protected HttpClient? HttpClient { get; set; } [Inject] protected IJSRuntime? JSRuntime { get; set; } [Inject] protected Models.AppSettings? AppSettings { get; set; } - [Parameter] public Metrology.Shared.ViewModels.Export? Model { get; set; } + [Inject] protected ILogger? Logger { get; set; } - public Export() - { - Model ??= new() - { - ToolType = "", - StartTime = DateTime.Now.AddMonths(-1), - EndTime = DateTime.Now - }; - } + protected Func _ConvertFunc = toolTypeNameId => string.Concat(toolTypeNameId?.ToolTypeName); protected override Task OnAfterRenderAsync(bool firstRender) { @@ -32,5 +34,44 @@ public partial class Export } return Task.CompletedTask; } - + + protected override async Task OnInitializedAsync() + { + _ToolTypeNameId ??= new(); + _TimeSpan ??= new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, 00); + _DateRange ??= new DateRange(DateTime.Now.AddMonths(-1).Date, DateTime.Now.Date); + if (Logger is null) + throw new NullReferenceException(nameof(Logger)); + if (HttpClient is null) + throw new NullReferenceException(nameof(HttpClient)); + string controllerName = IToolTypesController.GetRouteName(); + try + { + Result? result = await HttpClient.GetFromJsonAsync>($"api/{controllerName}"); + if (result is not null) + _ToolTypeNameIdCollection = result.Results; + } + catch (Exception) + { + string json = await HttpClient.GetStringAsync($"api/{controllerName}"); + Logger.LogInformation(message: json); + } + } + + protected async Task DownloadAsync() + { + if (JSRuntime is null) + throw new NullReferenceException(nameof(JSRuntime)); + if (HttpClient is null) + throw new NullReferenceException(nameof(HttpClient)); + if (_TimeSpan is null || _DateRange is null || _ToolTypeNameId is null || _ToolTypeNameIdCollection is null || _ToolTypeNameId.ToolTypeName is null || _DateRange.Start is null || _DateRange.End is null) + return; + string controllerName = IToolTypesController.GetRouteName(); + string endTime = _DateRange.End.Value.AddTicks(_TimeSpan.Value.Ticks).ToString(); + string startTime = _DateRange.Start.Value.AddTicks(_TimeSpan.Value.Ticks).ToString(); + string fileName = $"Export_{_ToolTypeNameId.ToolTypeName}_{startTime:yyyyMMddHHmm}_to_{endTime:yyyyMMddHHmm}.csv"; + string query = $"datebegin={startTime:MM/dd/yyyy hh:mm tt}&dateend={endTime:MM/dd/yyyy hh:mm tt}&filename={WebUtility.UrlEncode(fileName)}"; + await JSRuntime.InvokeVoidAsync("triggerFileDownload", fileName, $"{HttpClient.BaseAddress}api/{controllerName}/{_ToolTypeNameId.ID}/csv?{query}"); + } + } \ No newline at end of file diff --git a/ClientHub/Pages/RunInfo.razor b/ClientHub/Pages/RunInfo.razor index a4202be..6d6d0eb 100644 --- a/ClientHub/Pages/RunInfo.razor +++ b/ClientHub/Pages/RunInfo.razor @@ -1,5 +1,4 @@ -@page "/RunInfo/{Model?}" -@page "/RunInfo/{ToolTypeId:int?}/{HeaderId:int?}" +@page "/RunInfo/{ToolTypeId:int?}/{HeaderId:int?}" @using Microsoft.AspNetCore.Components.Web @using MudBlazor diff --git a/ClientHub/Pages/RunInfo.razor.cs b/ClientHub/Pages/RunInfo.razor.cs index c40d545..7903e17 100644 --- a/ClientHub/Pages/RunInfo.razor.cs +++ b/ClientHub/Pages/RunInfo.razor.cs @@ -8,7 +8,6 @@ public partial class RunInfo [Parameter] public int? HeaderId { get; set; } [Parameter] public int? ToolTypeId { get; set; } - [Parameter] public Metrology.Shared.ViewModels.RunInfo? Model { get; set; } [Inject] protected IJSRuntime? JSRuntime { get; set; } [Inject] protected Models.AppSettings? AppSettings { get; set; } @@ -21,9 +20,10 @@ public partial class RunInfo throw new NullReferenceException(nameof(JSRuntime)); if (AppSettings is null) throw new NullReferenceException(nameof(AppSettings)); - int initialHeaderId = Model is not null ? Model.HeaderID : HeaderId is not null ? HeaderId.Value : 0; - string initialHeaderAttachmentId = Model is not null ? Model.HeaderAttachmentID.ToString() : string.Empty; - int initialToolTypeID = Model is not null ? Model.ToolTypeID : ToolTypeId is not null ? ToolTypeId.Value : 1; + Metrology.Shared.ViewModels.RunInfo? model = null; + int initialHeaderId = model is not null ? model.HeaderID : HeaderId is not null ? HeaderId.Value : 0; + string initialHeaderAttachmentId = model is not null ? model.HeaderAttachmentID.ToString() : string.Empty; + int initialToolTypeID = model is not null ? model.ToolTypeID : ToolTypeId is not null ? ToolTypeId.Value : 1; return JSRuntime.InvokeVoidAsync("initRunInfo", AppSettings.ApiUrl, initialToolTypeID, initialHeaderId, initialHeaderAttachmentId).AsTask(); } return Task.CompletedTask; diff --git a/ClientHub/Program.cs b/ClientHub/Program.cs index bc4374f..e3b7caf 100644 --- a/ClientHub/Program.cs +++ b/ClientHub/Program.cs @@ -30,6 +30,7 @@ internal class Program _ = builder.Services.AddSingleton(_ => appSettings); _ = builder.Services.AddSingleton(); + _ = builder.Services.AddSingleton(); _ = builder.Services.AddScoped(serviceProvider => new HttpClient { BaseAddress = new Uri(appSettings.ApiUrl) }); WebApplication app = builder.Build(); diff --git a/ClientHub/wwwroot/site.js b/ClientHub/wwwroot/site.js index 98c3849..0998664 100644 --- a/ClientHub/wwwroot/site.js +++ b/ClientHub/wwwroot/site.js @@ -537,4 +537,12 @@ function initRunInfo(apiUrl, initialToolTypeID, initialHeaderId, initialHeaderAt $("#LoadHeadersButton").click(); } }, 180000); -}; \ No newline at end of file +}; + +function triggerFileDownload(fileName, url) { + const anchorElement = document.createElement('a'); + anchorElement.href = url; + anchorElement.download = fileName ?? ''; + anchorElement.click(); + anchorElement.remove(); +} \ No newline at end of file diff --git a/ClientHub/wwwroot/styles/Site.css b/ClientHub/wwwroot/styles/Site.css index 418f67e..7082f4e 100644 --- a/ClientHub/wwwroot/styles/Site.css +++ b/ClientHub/wwwroot/styles/Site.css @@ -1,7 +1,7 @@ body { padding-top: 50px; padding-bottom: 20px; - background-color:darkgrey; + background-color: darkgrey; } /* Set padding to keep content from hitting the edges */ @@ -104,3 +104,7 @@ div.modal-content-warning { .mud-table-cell { font-size: 1.875rem; } + +#ExportData { + zoom: 2; +} \ No newline at end of file diff --git a/Server/ApiControllers/ToolTypesController.cs b/Server/ApiControllers/ToolTypesController.cs index a0dd426..3f33de1 100644 --- a/Server/ApiControllers/ToolTypesController.cs +++ b/Server/ApiControllers/ToolTypesController.cs @@ -66,6 +66,23 @@ public class ToolTypesController : Controller, IToolTypesController r = _ToolTypesRepository.GetExportData(_MetrologyRepo, toolTypeId, datebegin, dateend); + string json = JsonConvert.SerializeObject(r); + return Content(json); + } + + [HttpGet] + [Route("{toolTypeId}/csv")] + public IActionResult GetCSVExport(int toolTypeId, [FromQuery] DateTime? datebegin, [FromQuery] DateTime? dateend, [FromQuery] string? filename) + { + byte[] r = _ToolTypesRepository.GetCSVExport(_MetrologyRepo, toolTypeId, datebegin, dateend); + return File(r, "application/octet-stream", filename); + } + [HttpGet] [Route("{toolTypeId}/{tabletype}/files/{attachmentId}/{filename}")] public IActionResult GetAttachment(int toolTypeId, string tabletype, string attachmentId, string filename) diff --git a/Server/Controllers/ExportController.cs b/Server/Controllers/ExportController.cs index b139a2c..dc90a3a 100644 --- a/Server/Controllers/ExportController.cs +++ b/Server/Controllers/ExportController.cs @@ -29,13 +29,13 @@ public class ExportController : Controller base.OnActionExecuted(context); ViewBag.IsTestDatabase = _IsTestDatabase; } + [HttpGet] [Route("/Export")] public ActionResult Index() { Export model = new() { - ToolType = "", StartTime = DateTime.Now.AddMonths(-1), EndTime = DateTime.Now }; diff --git a/Server/Data/Tests/GetExportDataApi.json b/Server/Data/Tests/GetExportDataApi.json new file mode 100644 index 0000000..a60dc1c --- /dev/null +++ b/Server/Data/Tests/GetExportDataApi.json @@ -0,0 +1 @@ +{"Results":[],"TotalRows":0} \ No newline at end of file diff --git a/Server/OI.Metrology.Server.csproj b/Server/OI.Metrology.Server.csproj index 1a5692b..2200ae3 100644 --- a/Server/OI.Metrology.Server.csproj +++ b/Server/OI.Metrology.Server.csproj @@ -82,6 +82,9 @@ Always + + Always + Always diff --git a/Server/Repositories/ToolTypesRepository.cs b/Server/Repositories/ToolTypesRepository.cs index d4b0bbc..916113c 100644 --- a/Server/Repositories/ToolTypesRepository.cs +++ b/Server/Repositories/ToolTypesRepository.cs @@ -2,6 +2,7 @@ using OI.Metrology.Shared.DataModels; using OI.Metrology.Shared.Models.Stateless; using OI.Metrology.Shared.Services; using System.Data; +using System.Text; using System.Text.Json; namespace OI.Metrology.Server.Repository; @@ -216,7 +217,7 @@ public class ToolTypesRepository : IToolTypesRepository // The second table has the header data if (ds.Tables[1].Rows.Count != 1) throw new Exception("Error exporting, invalid header data"); - System.Text.StringBuilder sb = new(); + StringBuilder sb = new(); foreach (object? o in ds.Tables[1].Rows[0].ItemArray) { if ((o is not null) && (!Convert.IsDBNull(o))) @@ -244,4 +245,96 @@ public class ToolTypesRepository : IToolTypesRepository return result; } + Result IToolTypesRepository.GetExportData(IMetrologyRepository metrologyRepository, int toolTypeId, DateTime? datebegin, DateTime? dateend) + { + Result? r; + if (!string.IsNullOrEmpty(_MockRoot)) + { + string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), "GetExportDataApi.json")); + r = Newtonsoft.Json.JsonConvert.DeserializeObject>(json); + if (r is null) + throw new NullReferenceException(nameof(r)); + } + else + { + dateend ??= DateTime.Now; + datebegin ??= dateend.Value.AddMonths(-1); + ToolType tt = metrologyRepository.GetToolTypeByID(toolTypeId); + if (string.IsNullOrEmpty(tt.ExportSPName)) + throw new NullReferenceException(nameof(tt.ExportSPName)); + DataTable dataTable = metrologyRepository.ExportData(tt.ExportSPName, datebegin.Value, dateend.Value); + r = new() + { + Results = dataTable, + TotalRows = dataTable.Rows.Count, + }; + } + return r; + } + + protected static string GetRowData(DataRow dr) + { + StringBuilder result = new(); + for (int i = 0; i < dr.Table.Columns.Count; i++) + { + if (i > 0) + _ = result.Append(','); + object v = dr[i]; + if (v is not null && !Convert.IsDBNull(v)) + _ = result.Append(FormatForCSV(string.Concat(Convert.ToString(v)))); + } + return result.ToString(); + } + + protected static string GetColumnHeaders(DataTable dataTable) + { + StringBuilder result = new(); + for (int i = 0; i < dataTable.Columns.Count; i++) + { + if (i > 0) + _ = result.Append(','); + _ = result.Append(FormatForCSV(dataTable.Columns[i].ColumnName.TrimEnd('_'))); + } + return result.ToString(); + } + + protected static string FormatForCSV(string v) + { + StringBuilder result = new(v.Length + 2); + bool doubleQuoted = false; + if (v.StartsWith(' ') || v.EndsWith(' ') || v.Contains(',') || v.Contains('"')) + { + _ = result.Append('"'); + doubleQuoted = true; + } + foreach (char c in v) + { + _ = c switch + { + '\r' or '\n' => result.Append(' '), + '"' => result.Append("\"\""), + _ => result.Append(c), + }; + } + if (doubleQuoted) + _ = result.Append('"'); + return result.ToString(); + } + + byte[] IToolTypesRepository.GetCSVExport(IMetrologyRepository metrologyRepository, int toolTypeId, DateTime? datebegin, DateTime? dateend) + { + byte[] results; + Result result; + IToolTypesRepository repository = this; + result = repository.GetExportData(metrologyRepository, toolTypeId, datebegin, dateend); + if (result.Results is null) + throw new NullReferenceException(nameof(result.Results)); + StringBuilder stringBuilder = new(); + _ = stringBuilder.AppendLine(GetColumnHeaders(result.Results)); + foreach (DataRow dr in result.Results.Rows) + _ = stringBuilder.AppendLine(GetRowData(dr)); + results = Encoding.UTF8.GetBytes(stringBuilder.ToString()); + return results; + } + } \ No newline at end of file diff --git a/Shared/Models/Stateless/IToolTypesRepository.cs b/Shared/Models/Stateless/IToolTypesRepository.cs index c711a25..1f81b60 100644 --- a/Shared/Models/Stateless/IToolTypesRepository.cs +++ b/Shared/Models/Stateless/IToolTypesRepository.cs @@ -15,5 +15,6 @@ public interface IToolTypesRepository Result GetData(IMetrologyRepository metrologyRepository, int id, long headerid); (string?, string?, Stream?) GetAttachment(IMetrologyRepository metrologyRepository, IAttachmentsService attachmentsService, int toolTypeId, string tabletype, string attachmentId, string filename); Exception? OIExport(IMetrologyRepository metrologyRepository, string oiExportPath, int toolTypeId, long headerid); - + Result GetExportData(IMetrologyRepository metrologyRepository, int toolTypeId, DateTime? datebegin, DateTime? dateend); + byte[] GetCSVExport(IMetrologyRepository metrologyRepository, int toolTypeId, DateTime? datebegin, DateTime? dateend); } \ No newline at end of file diff --git a/Tests/UnitTestToolTypesController.cs b/Tests/UnitTestToolTypesController.cs index 1fdcba2..a9c05d5 100644 --- a/Tests/UnitTestToolTypesController.cs +++ b/Tests/UnitTestToolTypesController.cs @@ -201,6 +201,32 @@ public class UnitTestToolTypesController _Logger.Information($"{_TestContext?.TestName} completed"); } + [TestMethod] + public void GetExportData() + { + _Logger.Information("Starting Web Application"); + IServiceProvider serviceProvider = _WebApplicationFactory.Services.CreateScope().ServiceProvider; + IMetrologyRepository metrologyRepository = serviceProvider.GetRequiredService(); + IToolTypesRepository toolTypesRepository = serviceProvider.GetRequiredService(); + Result result = toolTypesRepository.GetExportData(metrologyRepository, toolTypeId: 1, datebegin: null, dateend: null); + Assert.IsNotNull(result?.Results); + Assert.IsNotNull(result.Results.Rows.Count > 0); + _Logger.Information($"{_TestContext?.TestName} completed"); + } + + [TestMethod] + public async Task GetExportDataApi() + { + HttpClient httpClient = _WebApplicationFactory.CreateClient(); + _Logger.Information("Starting Web Application"); + string? json = await httpClient.GetStringAsync($"api/{_ControllerName}/1/export"); + File.WriteAllText(Path.Combine(AppContext.BaseDirectory, $"{nameof(GetExportDataApi)}.json"), json); + Result? result = Newtonsoft.Json.JsonConvert.DeserializeObject>(json); + Assert.IsNotNull(result?.Results); + Assert.IsNotNull(result.Results.Rows.Count > 0); + _Logger.Information($"{_TestContext?.TestName} completed"); + } + [TestMethod] [Ignore] public void GetAttachment()