From 55e2b4a6854d758497285ac42f3904aebca241f1 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Mon, 16 Dec 2024 14:57:20 +0100 Subject: [PATCH 01/13] Use export controller with csvConfig --- src/api/BdmsContextExtensions.cs | 3 + src/api/BoreholeGeometry/AzIncFormat.cs | 2 +- src/api/BoreholeGeometry/PitchRollFormat.cs | 2 +- src/api/BoreholeGeometry/XYZFormat.cs | 2 +- src/api/Controllers/BoreholeController.cs | 57 ----- src/api/Controllers/ExportController.cs | 163 +++++++++++++ src/api/Controllers/ImportController.cs | 12 +- .../{BoreholeGeometry => }/CsvConfigHelper.cs | 13 +- src/api/Models/Borehole.cs | 19 ++ src/client/cypress/e2e/helpers/testHelpers.js | 20 +- src/client/cypress/e2e/mainPage/export.cy.js | 171 ++++++++++++- .../api/Controllers/BoreholeControllerTest.cs | 55 ----- tests/api/Controllers/ExportControllerTest.cs | 230 ++++++++++++++++++ 13 files changed, 598 insertions(+), 151 deletions(-) create mode 100644 src/api/Controllers/ExportController.cs rename src/api/{BoreholeGeometry => }/CsvConfigHelper.cs (74%) create mode 100644 tests/api/Controllers/ExportControllerTest.cs diff --git a/src/api/BdmsContextExtensions.cs b/src/api/BdmsContextExtensions.cs index 8afbf51d7..587d27d3f 100644 --- a/src/api/BdmsContextExtensions.cs +++ b/src/api/BdmsContextExtensions.cs @@ -202,6 +202,9 @@ public static void SeedData(this BdmsContext context) .RuleFor(o => o.PrecisionLocationY, f => f.PickRandom(Enumerable.Range(0, 10))) .RuleFor(o => o.PrecisionLocationXLV03, f => f.PickRandom(Enumerable.Range(0, 10))) .RuleFor(o => o.PrecisionLocationYLV03, f => f.PickRandom(Enumerable.Range(0, 10))) + .RuleFor(o => o.TotalDepthTvd, _ => null) + .RuleFor(o => o.TopBedrockFreshTvd, _ => null) + .RuleFor(o => o.TopBedrockWeatheredTvd, _ => null) .RuleFor(o => o.Observations, _ => new Collection()) .FinishWith((f, o) => { o.Name = o.OriginalName; }); diff --git a/src/api/BoreholeGeometry/AzIncFormat.cs b/src/api/BoreholeGeometry/AzIncFormat.cs index 3f6e3d754..0e91c4575 100644 --- a/src/api/BoreholeGeometry/AzIncFormat.cs +++ b/src/api/BoreholeGeometry/AzIncFormat.cs @@ -21,7 +21,7 @@ internal sealed class AzIncFormat : IBoreholeGeometryFormat public IList ReadCsv(IFormFile file, int boreholeId) { using var reader = new StreamReader(file.OpenReadStream()); - using var csv = new CsvReader(reader, CsvConfigHelper.CsvConfig); + using var csv = new CsvReader(reader, CsvConfigHelper.CsvReadConfig); var data = csv.GetRecords().ToList(); diff --git a/src/api/BoreholeGeometry/PitchRollFormat.cs b/src/api/BoreholeGeometry/PitchRollFormat.cs index 05054b975..aadc4f0fe 100644 --- a/src/api/BoreholeGeometry/PitchRollFormat.cs +++ b/src/api/BoreholeGeometry/PitchRollFormat.cs @@ -21,7 +21,7 @@ internal sealed class PitchRollFormat : IBoreholeGeometryFormat public IList ReadCsv(IFormFile file, int boreholeId) { using var reader = new StreamReader(file.OpenReadStream()); - using var csv = new CsvReader(reader, CsvConfigHelper.CsvConfig); + using var csv = new CsvReader(reader, CsvConfigHelper.CsvReadConfig); var data = csv.GetRecords().ToList(); diff --git a/src/api/BoreholeGeometry/XYZFormat.cs b/src/api/BoreholeGeometry/XYZFormat.cs index f7787b395..1f142d0e6 100644 --- a/src/api/BoreholeGeometry/XYZFormat.cs +++ b/src/api/BoreholeGeometry/XYZFormat.cs @@ -20,7 +20,7 @@ internal sealed class XYZFormat : IBoreholeGeometryFormat public IList ReadCsv(IFormFile file, int boreholeId) { using var reader = new StreamReader(file.OpenReadStream()); - using var csv = new CsvReader(reader, CsvConfigHelper.CsvConfig); + using var csv = new CsvReader(reader, CsvConfigHelper.CsvReadConfig); var data = csv.GetRecords(); return ToBoreholeGeometry(data, boreholeId); diff --git a/src/api/Controllers/BoreholeController.cs b/src/api/Controllers/BoreholeController.cs index d25d2ab0c..9c283a541 100644 --- a/src/api/Controllers/BoreholeController.cs +++ b/src/api/Controllers/BoreholeController.cs @@ -120,63 +120,6 @@ public async Task> GetByIdAsync(int id) return Ok(borehole); } - /// - /// Exports the details of up to boreholes as a CSV file. Filters the boreholes based on the provided list of IDs. - /// - /// The list of IDs for the boreholes to be exported. - /// A CSV file containing the details specified boreholes. - [HttpGet("export-csv")] - [Authorize(Policy = PolicyNames.Viewer)] - public async Task DownloadCsvAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable ids) - { - ids = ids.Take(MaxPageSize).ToList(); - if (!ids.Any()) return BadRequest("The list of IDs must not be empty."); - - var boreholes = await Context.Boreholes - .Where(borehole => ids.Contains(borehole.Id)) - .Select(b => new - { - b.Id, - b.OriginalName, - b.ProjectName, - b.Name, - b.RestrictionId, - b.RestrictionUntil, - b.NationalInterest, - b.LocationX, - b.LocationY, - b.LocationPrecisionId, - b.ElevationZ, - b.ElevationPrecisionId, - b.ReferenceElevation, - b.ReferenceElevationTypeId, - b.ReferenceElevationPrecisionId, - b.HrsId, - b.TypeId, - b.PurposeId, - b.StatusId, - b.Remarks, - b.TotalDepth, - b.DepthPrecisionId, - b.TopBedrockFreshMd, - b.TopBedrockWeatheredMd, - b.HasGroundwater, - b.LithologyTopBedrockId, - b.ChronostratigraphyTopBedrockId, - b.LithostratigraphyTopBedrockId, - }) - .ToListAsync() - .ConfigureAwait(false); - - if (boreholes.Count == 0) return NotFound("No borehole(s) found for the provided id(s)."); - - using var stringWriter = new StringWriter(); - using var csvWriter = new CsvWriter(stringWriter, CultureInfo.InvariantCulture); - await csvWriter.WriteRecordsAsync(boreholes).ConfigureAwait(false); - - return File(Encoding.UTF8.GetBytes(stringWriter.ToString()), "text/csv", "boreholes_export.csv"); - } - /// /// Asynchronously copies a . /// diff --git a/src/api/Controllers/ExportController.cs b/src/api/Controllers/ExportController.cs new file mode 100644 index 000000000..ee8988e58 --- /dev/null +++ b/src/api/Controllers/ExportController.cs @@ -0,0 +1,163 @@ +using BDMS.Authentication; +using BDMS.Models; +using CsvHelper; +using CsvHelper.Configuration; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Text; + +namespace BDMS.Controllers; + +[ApiController] +[Route("api/v{version:apiVersion}/[controller]")] +public class ExportController : ControllerBase +{ + // Limit the maximum number of items per request to 100. + // This also applies to the number of filtered ids to ensure the URL length does not exceed the maximum allowed length. + private const int MaxPageSize = 100; + private readonly BdmsContext context; + private readonly ILogger logger; + + public ExportController(BdmsContext context, ILogger logger) + { + this.context = context; + this.logger = logger; + } + + /// + /// Exports the details of up to boreholes as a CSV file. Filters the boreholes based on the provided list of IDs. + /// + /// The list of IDs for the boreholes to be exported. + /// A CSV file containing the details of the specified boreholes. + [HttpGet("export-csv")] + [Authorize(Policy = PolicyNames.Viewer)] + public async Task DownloadCsvAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable ids) + { + List idList = ids.Take(MaxPageSize).ToList(); + if (idList.Count < 1) return BadRequest("The list of IDs must not be empty."); + + var boreholes = await context.Boreholes + .Include(b => b.BoreholeCodelists) + .ThenInclude(bc => bc.Codelist) + .Where(borehole => idList.Contains(borehole.Id)) + .OrderBy(b => idList.IndexOf(b.Id)) + .ToListAsync() + .ConfigureAwait(false); + + if (boreholes.Count == 0) return NotFound("No borehole(s) found for the provided id(s)."); + + using var stringWriter = new StringWriter(); + using var csvWriter = new CsvWriter(stringWriter, CsvConfigHelper.CsvWriteConfig); + + // Write headers for standard fields + csvWriter.WriteField(nameof(Borehole.Id)); + csvWriter.WriteField(nameof(Borehole.OriginalName)); + csvWriter.WriteField(nameof(Borehole.ProjectName)); + csvWriter.WriteField(nameof(Borehole.Name)); + csvWriter.WriteField(nameof(Borehole.RestrictionId)); + csvWriter.WriteField(nameof(Borehole.RestrictionUntil)); + csvWriter.WriteField(nameof(Borehole.NationalInterest)); + csvWriter.WriteField(nameof(Borehole.LocationX)); + csvWriter.WriteField(nameof(Borehole.LocationY)); + csvWriter.WriteField(nameof(Borehole.LocationPrecisionId)); + csvWriter.WriteField(nameof(Borehole.ElevationZ)); + csvWriter.WriteField(nameof(Borehole.ElevationPrecisionId)); + csvWriter.WriteField(nameof(Borehole.ReferenceElevation)); + csvWriter.WriteField(nameof(Borehole.ReferenceElevationTypeId)); + csvWriter.WriteField(nameof(Borehole.ReferenceElevationPrecisionId)); + csvWriter.WriteField(nameof(Borehole.HrsId)); + csvWriter.WriteField(nameof(Borehole.TypeId)); + csvWriter.WriteField(nameof(Borehole.PurposeId)); + csvWriter.WriteField(nameof(Borehole.StatusId)); + csvWriter.WriteField(nameof(Borehole.Remarks)); + csvWriter.WriteField(nameof(Borehole.TotalDepth)); + csvWriter.WriteField(nameof(Borehole.DepthPrecisionId)); + csvWriter.WriteField(nameof(Borehole.TopBedrockFreshMd)); + csvWriter.WriteField(nameof(Borehole.TopBedrockWeatheredMd)); + csvWriter.WriteField(nameof(Borehole.TotalDepthTvd)); + csvWriter.WriteField(nameof(Borehole.TopBedrockFreshTvd)); + csvWriter.WriteField(nameof(Borehole.TopBedrockWeatheredTvd)); + csvWriter.WriteField(nameof(Borehole.HasGroundwater)); + csvWriter.WriteField(nameof(Borehole.LithologyTopBedrockId)); + csvWriter.WriteField(nameof(Borehole.ChronostratigraphyTopBedrockId)); + csvWriter.WriteField(nameof(Borehole.LithostratigraphyTopBedrockId)); + + // Write dynamic headers for each distinct custom Id + var customIdHeaders = boreholes + .SelectMany(b => b.BoreholeCodelists ?? Enumerable.Empty()) + .Select(bc => new { bc.CodelistId, bc.Codelist?.En }) + .Distinct() + .OrderBy(x => x.CodelistId) + .ToList(); + + foreach (var header in customIdHeaders) + { + csvWriter.WriteField(header.En.Replace(" ", "", StringComparison.OrdinalIgnoreCase)); + } + + // Move to the next line + await csvWriter.NextRecordAsync().ConfigureAwait(false); + + // Write data for standard fields + foreach (var b in boreholes) + { + var boreholeGeometry = await context.BoreholeGeometry + .AsNoTracking() + .Where(g => g.BoreholeId == b.Id) + .ToListAsync() + .ConfigureAwait(false); + + b.TotalDepthTvd = boreholeGeometry.GetTVDIfGeometryExists(b.TotalDepth); + b.TopBedrockFreshTvd = boreholeGeometry.GetTVDIfGeometryExists(b.TopBedrockFreshMd); + b.TopBedrockWeatheredTvd = boreholeGeometry.GetTVDIfGeometryExists(b.TopBedrockWeatheredMd); + + csvWriter.WriteField(b.Id); + csvWriter.WriteField(b.OriginalName); + csvWriter.WriteField(b.ProjectName); + csvWriter.WriteField(b.Name); + csvWriter.WriteField(b.RestrictionId); + csvWriter.WriteField(b.RestrictionUntil); + csvWriter.WriteField(b.NationalInterest); + csvWriter.WriteField(b.LocationX); + csvWriter.WriteField(b.LocationY); + csvWriter.WriteField(b.LocationPrecisionId); + csvWriter.WriteField(b.ElevationZ); + csvWriter.WriteField(b.ElevationPrecisionId); + csvWriter.WriteField(b.ReferenceElevation); + csvWriter.WriteField(b.ReferenceElevationTypeId); + csvWriter.WriteField(b.ReferenceElevationPrecisionId); + csvWriter.WriteField(b.HrsId); + csvWriter.WriteField(b.TypeId); + csvWriter.WriteField(b.PurposeId); + csvWriter.WriteField(b.StatusId); + csvWriter.WriteField(b.Remarks); + csvWriter.WriteField(b.TotalDepth); + csvWriter.WriteField(b.DepthPrecisionId); + csvWriter.WriteField(b.TopBedrockFreshMd); + csvWriter.WriteField(b.TopBedrockWeatheredMd); + csvWriter.WriteField(b.TotalDepthTvd); + csvWriter.WriteField(b.TopBedrockFreshTvd); + csvWriter.WriteField(b.TopBedrockWeatheredTvd); + csvWriter.WriteField(b.HasGroundwater); + csvWriter.WriteField(b.LithologyTopBedrockId); + csvWriter.WriteField(b.ChronostratigraphyTopBedrockId); + csvWriter.WriteField(b.LithostratigraphyTopBedrockId); + + // Write dynamic fields for custom Ids + foreach (var header in customIdHeaders) + { + var codelistValue = (b.BoreholeCodelists ?? Enumerable.Empty()).FirstOrDefault(bc => bc.CodelistId == header.CodelistId)?.Value; + csvWriter.WriteField(codelistValue ?? ""); + } + + // Move to the next line + await csvWriter.NextRecordAsync().ConfigureAwait(false); + } + + await csvWriter.FlushAsync().ConfigureAwait(false); + return File(Encoding.UTF8.GetBytes(stringWriter.ToString()), "text/csv", "boreholes_export.csv"); + } +} diff --git a/src/api/Controllers/ImportController.cs b/src/api/Controllers/ImportController.cs index 091e85cf0..657ac00d2 100644 --- a/src/api/Controllers/ImportController.cs +++ b/src/api/Controllers/ImportController.cs @@ -25,13 +25,6 @@ public class ImportController : ControllerBase private readonly int sridLv95 = 2056; private readonly int sridLv03 = 21781; private readonly string nullOrEmptyMsg = "Field '{0}' is required."; - private readonly CsvConfiguration csvConfig = new(new CultureInfo("de-CH")) - { - Delimiter = ";", - IgnoreReferences = true, - PrepareHeaderForMatch = args => args.Header.Humanize(LetterCasing.Title), - MissingFieldFound = null, - }; private static readonly JsonSerializerOptions jsonImportOptions = new() { PropertyNameCaseInsensitive = true }; @@ -350,7 +343,7 @@ internal static bool CompareValuesWithTolerance(double? firstValue, double? seco private List ReadBoreholesFromCsv(IFormFile file) { using var reader = new StreamReader(file.OpenReadStream()); - using var csv = new CsvReader(reader, csvConfig); + using var csv = new CsvReader(reader, CsvConfigHelper.CsvReadConfig); csv.Context.RegisterClassMap(new CsvImportBoreholeMap()); @@ -445,6 +438,9 @@ public CsvImportBoreholeMap() Map(b => b.Canton).Ignore(); Map(b => b.Country).Ignore(); Map(m => m.Id).Ignore(); + Map(m => m.TotalDepthTvd).Ignore(); + Map(m => m.TopBedrockFreshTvd).Ignore(); + Map(m => m.TopBedrockWeatheredTvd).Ignore(); // Define additional mapping logic Map(m => m.BoreholeCodelists).Convert(args => diff --git a/src/api/BoreholeGeometry/CsvConfigHelper.cs b/src/api/CsvConfigHelper.cs similarity index 74% rename from src/api/BoreholeGeometry/CsvConfigHelper.cs rename to src/api/CsvConfigHelper.cs index 211f2a71d..8c523a3b2 100644 --- a/src/api/BoreholeGeometry/CsvConfigHelper.cs +++ b/src/api/CsvConfigHelper.cs @@ -6,11 +6,11 @@ using NetTopologySuite.Utilities; using System.Globalization; -namespace BDMS.BoreholeGeometry; +namespace BDMS; public static class CsvConfigHelper { - internal static readonly CsvConfiguration CsvConfig = new(new CultureInfo("de-CH")) + internal static readonly CsvConfiguration CsvReadConfig = new(new CultureInfo("de-CH")) { Delimiter = ";", IgnoreReferences = true, @@ -18,15 +18,20 @@ public static class CsvConfigHelper MissingFieldFound = null, }; + internal static readonly CsvConfiguration CsvWriteConfig = new(new CultureInfo("de-CH")) + { + Delimiter = ";", + }; + /// /// Get the CSV header for a class of type . - /// Uses the map generated by . + /// Uses the map generated by . /// If a property has multiple possible column names only the first is considered. /// /// The class to get the header for. internal static string GetCsvHeader() { - var context = new CsvContext(CsvConfig); + var context = new CsvContext(CsvReadConfig); var map = context.AutoMap(); return string.Join("; ", map.MemberMaps .Select(m => diff --git a/src/api/Models/Borehole.cs b/src/api/Models/Borehole.cs index bc7017a41..56b9d79d4 100644 --- a/src/api/Models/Borehole.cs +++ b/src/api/Models/Borehole.cs @@ -1,5 +1,6 @@ using NetTopologySuite.Geometries; using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics; using System.Text.Json.Serialization; namespace BDMS.Models; @@ -350,6 +351,24 @@ public class Borehole : IChangeTracking, IIdentifyable [Column("reference_elevation_type_id_cli")] public int? ReferenceElevationTypeId { get; set; } + /// + /// Gets or sets the 's true vertical total depth. + /// + [NotMapped] + public double? TotalDepthTvd { get; set; } + + /// + /// Gets or sets the 's true vertical top bedrock fresh depth. + /// + [NotMapped] + public double? TopBedrockFreshTvd { get; set; } + + /// + /// Gets or sets the 's true vertical top bedrock weathered depth. + /// + [NotMapped] + public double? TopBedrockWeatheredTvd { get; set; } + /// /// Gets or sets the 's reference elevation type. /// diff --git a/src/client/cypress/e2e/helpers/testHelpers.js b/src/client/cypress/e2e/helpers/testHelpers.js index 8b125d7f1..f6c5afe60 100644 --- a/src/client/cypress/e2e/helpers/testHelpers.js +++ b/src/client/cypress/e2e/helpers/testHelpers.js @@ -263,6 +263,10 @@ export const returnToOverview = () => { cy.wait(["@edit_list", "@borehole"]); }; +export const getElementByDataCy = attribute => { + return cy.get(`[data-cy=${attribute}]`); +}; + export const deleteBorehole = id => { cy.get("@id_token").then(token => { cy.request({ @@ -332,26 +336,20 @@ export const setValueOfInputElement = function (inputElement, inputValue) { // Deletes a downloaded file in Cypress' downloads folder export const deleteDownloadedFile = fileName => { // Get the path to the downloaded file you want to delete - let filePath = "cypress/downloads/" + fileName; + const filePath = Cypress.platform === "win32" ? `cypress\\downloads\\${fileName}` : `cypress/downloads/${fileName}`; // If file exists in download folder, delete it. - cy.task("fileExistsInDownloadFolder", "languages/en.yml").then(exists => { + cy.task("fileExistsInDownloadFolder", filePath).then(exists => { if (exists) { - // Set the command in case of linux os - let command = "rm -f"; - - // Override the command and path in case of windows os - if (Cypress.platform === "win32") { - command = "del"; - filePath = "cypress\\downloads\\" + fileName; - } + // Set the command to delete the file based on the OS + const command = Cypress.platform === "win32" ? "del" : "rm -f"; cy.exec(`${command} ${filePath}`).then(result => { // Check if the command executed successfully expect(result.code).to.equal(0); // Check that the file has been deleted - cy.readFile(filePath, { log: false }).should("not.exist"); + cy.readFile(filePath, { log: false, timeout: 10000 }).should("not.exist"); }); } }); diff --git a/src/client/cypress/e2e/mainPage/export.cy.js b/src/client/cypress/e2e/mainPage/export.cy.js index 5be5dcddb..06b9bec38 100644 --- a/src/client/cypress/e2e/mainPage/export.cy.js +++ b/src/client/cypress/e2e/mainPage/export.cy.js @@ -1,45 +1,192 @@ -import { exportCSVItem, exportJsonItem } from "../helpers/buttonHelpers"; +import { addItem, deleteItem, exportCSVItem, exportJsonItem, saveWithSaveBar } from "../helpers/buttonHelpers"; import { checkAllVisibleRows, checkRowWithText, showTableAndWaitForData } from "../helpers/dataGridHelpers.js"; +import { evaluateInput, setInput, setSelect } from "../helpers/formHelpers"; import { createBorehole, deleteDownloadedFile, + getElementByDataCy, + getImportFileFromFixtures, + goToRouteAndAcceptTerms, handlePrompt, - loginAsAdmin, + newEditableBorehole, prepareDownloadPath, readDownloadedFile, + returnToOverview, + startBoreholeEditing, + stopBoreholeEditing, } from "../helpers/testHelpers"; const jsonFileName = `bulkexport_${new Date().toISOString().split("T")[0]}.json`; const csvFileName = `bulkexport_${new Date().toISOString().split("T")[0]}.csv`; +const splitFileContent = fileContent => { + const lines = fileContent.split("\n"); + const rows = lines.map(row => row.split(";")); + return { lines, rows }; +}; + +const verifyTVDContentInCSVFile = ( + fileName, + expectedTotalDepthVD, + expectedTopBedrockFreshTVD, + expectedTopBedrockWeatheredTVD, +) => { + cy.readFile(prepareDownloadPath(fileName)).then(fileContent => { + const { lines, rows } = splitFileContent(fileContent); + expect(lines.length).to.equal(3); + expect(rows[0][24]).to.equal("TotalDepthTvd"); + expect(rows[1][24]).to.equal(expectedTotalDepthVD); + expect(rows[0][25]).to.equal("TopBedrockFreshTvd"); + expect(rows[1][25]).to.equal(expectedTopBedrockFreshTVD); + expect(rows[0][26]).to.equal("TopBedrockWeatheredTvd"); + expect(rows[1][26]).to.equal(expectedTopBedrockWeatheredTVD); + }); +}; + describe("Test for exporting boreholes.", () => { it("bulk exports boreholes to json and csv", () => { + deleteDownloadedFile(jsonFileName); + deleteDownloadedFile(csvFileName); createBorehole({ "extended.original_name": "AAA_NINTIC", "custom.alternate_name": "AAA_NINTIC" }).as( "borehole_id_1", ); createBorehole({ "extended.original_name": "AAA_LOMONE", "custom.alternate_name": "AAA_LOMONE" }).as( "borehole_id_2", ); - loginAsAdmin(); + goToRouteAndAcceptTerms("/"); showTableAndWaitForData(); - cy.get('[data-cy="borehole-table"]').within(() => { - checkRowWithText("AAA_NINTIC"); - checkRowWithText("AAA_LOMONE"); - }); + checkRowWithText("AAA_NINTIC"); + checkRowWithText("AAA_LOMONE"); - deleteDownloadedFile(jsonFileName); - deleteDownloadedFile(csvFileName); exportJsonItem(); exportCSVItem(); readDownloadedFile(jsonFileName); readDownloadedFile(csvFileName); + deleteItem(); // bulk delete all added boreholes + handlePrompt("Do you really want to delete these 2 boreholes? This cannot be undone.", "Delete"); + }); + + it("exports TVD for a borehole with and without geometry", () => { + const boreholeName = "AAA_FROGGY"; + const secondBoreholeName = "AAA_FISHY"; + const fileName = `${boreholeName}.csv`; + const secondFileName = `${secondBoreholeName}.csv`; + + deleteDownloadedFile(fileName); + deleteDownloadedFile(secondFileName); + + createBorehole({ "extended.original_name": boreholeName, "custom.alternate_name": boreholeName }).as("borehole_id"); + + cy.get("@borehole_id").then(id => { + goToRouteAndAcceptTerms(`/${id}`); + }); + + // add geometry to borehole and verify export tvd changed + getElementByDataCy("borehole-menu-item").click(); + startBoreholeEditing(); + setInput("totalDepth", 700); + setInput("topBedrockFreshMd", 800); + setInput("topBedrockWeatheredMd", 900); + evaluateInput("totalDepth", "700"); + evaluateInput("total_depth_tvd", "700"); + evaluateInput("topBedrockFreshMd", "800"); + evaluateInput("top_bedrock_fresh_tvd", "800"); + evaluateInput("topBedrockWeatheredMd", "900"); + evaluateInput("top_bedrock_weathered_tvd", "900"); + + saveWithSaveBar(); + + stopBoreholeEditing(); + exportCSVItem(); + + verifyTVDContentInCSVFile(fileName, "700", "800", "900"); + startBoreholeEditing(); + + getElementByDataCy("geometry-tab").click(); + getElementByDataCy("boreholegeometryimport-button").should("be.disabled"); + + // upload geometry csv file + let geometryFile = new DataTransfer(); + getImportFileFromFixtures("geometry_azimuth_inclination.csv", null).then(fileContent => { + const file = new File([fileContent], "geometry_azimuth_inclination.csv", { + type: "text/csv", + }); + geometryFile.items.add(file); + }); + getElementByDataCy("import-geometry-input").within(() => { + cy.get("input[type=file]", { force: true }).then(input => { + input[0].files = geometryFile.files; + input[0].dispatchEvent(new Event("change", { bubbles: true })); + }); + }); + + setSelect("geometryFormat", 1); + getElementByDataCy("boreholegeometryimport-button").click(); + cy.wait(["@boreholegeometry_POST", "@boreholegeometry_GET"]); + cy.get(".MuiTableBody-root").should("exist"); + + getElementByDataCy("general-tab").click(); + evaluateInput("totalDepth", "700"); + evaluateInput("total_depth_tvd", "674.87"); + getElementByDataCy("location-menu-item").click(); + setInput("originalName", secondBoreholeName); // change name to avoid potential CSV filename conflict + saveWithSaveBar(); + stopBoreholeEditing(); + exportCSVItem(); + verifyTVDContentInCSVFile(secondFileName, "674.8678208299723", "762.6098263945338", "846.9637100889873"); + startBoreholeEditing(); + getElementByDataCy("deleteborehole-button").click(); + handlePrompt("Do you really want to delete this borehole? This cannot be undone.", "Delete"); + }); + + it("exports custom Ids form borehole", () => { + const firstBoreholeName = "AAA_DUCKY"; + const secondBoreholeName = "AAA_SNAKEY"; + deleteDownloadedFile(csvFileName); + newEditableBorehole().as("borehole_id"); + setInput("name", firstBoreholeName); + addItem("addIdentifier"); + setSelect("boreholeCodelists.0.codelistId", 3); + setInput("boreholeCodelists.0.value", 13); + saveWithSaveBar(); + returnToOverview(); + + newEditableBorehole().as("borehole_id_2"); + setInput("name", secondBoreholeName); + addItem("addIdentifier"); + setSelect("boreholeCodelists.0.codelistId", 4); + setInput("boreholeCodelists.0.value", 14); + saveWithSaveBar(); + returnToOverview(); + showTableAndWaitForData(); + checkRowWithText(firstBoreholeName); + checkRowWithText(secondBoreholeName); + exportCSVItem(); + cy.readFile(prepareDownloadPath(csvFileName)).then(fileContent => { + const { lines, rows } = splitFileContent(fileContent); + expect(lines.length).to.equal(4); + + expect(rows[0][3]).to.equal("Name"); + expect(rows[1][3]).to.equal(firstBoreholeName); + expect(rows[2][3]).to.equal(secondBoreholeName); + + expect(rows[0][31]).to.equal("IDInfoGeol"); + expect(rows[1][31]).to.equal(""); + expect(rows[2][31]).to.equal("14"); + + expect(rows[0][32]).to.equal("IDGeODin\r"); + expect(rows[1][32]).to.equal("13\r"); + expect(rows[2][32]).to.equal("\r"); + }); + deleteItem(); + handlePrompt("Do you really want to delete these 2 boreholes? This cannot be undone.", "Delete"); }); it("downloads a maximum of 100 boreholes", () => { - loginAsAdmin(); + deleteDownloadedFile(csvFileName); + deleteDownloadedFile(jsonFileName); showTableAndWaitForData(); checkAllVisibleRows(); - deleteDownloadedFile(csvFileName); exportCSVItem(); const moreThan100SelectedPrompt = @@ -56,8 +203,6 @@ describe("Test for exporting boreholes.", () => { const lines = fileContent.split("\n"); expect(lines.length).to.equal(102); }); - - deleteDownloadedFile(jsonFileName); exportJsonItem(); handlePrompt(moreThan100SelectedPrompt, "Cancel"); }); diff --git a/tests/api/Controllers/BoreholeControllerTest.cs b/tests/api/Controllers/BoreholeControllerTest.cs index 68708fe28..22c17b6a5 100644 --- a/tests/api/Controllers/BoreholeControllerTest.cs +++ b/tests/api/Controllers/BoreholeControllerTest.cs @@ -687,59 +687,4 @@ public async Task CopyWithNonAdminUser() Assert.IsNotNull(copiedBoreholeId); Assert.IsInstanceOfType(copiedBoreholeId, typeof(int)); } - - [TestMethod] - public async Task DownloadCsvWithValidIdsReturnsFileResultWithMax100Boreholes() - { - var ids = Enumerable.Range(testBoreholeId, 120).ToList(); - - var result = await controller.DownloadCsvAsync(ids) as FileContentResult; - - Assert.IsNotNull(result); - Assert.AreEqual("text/csv", result.ContentType); - Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); - var csvData = Encoding.UTF8.GetString(result.FileContents); - var fileLength = csvData.Split('\n').Length; - var recordCount = fileLength - 2; // Remove header and last line break - Assert.AreEqual(100, recordCount); - } - - [TestMethod] - public async Task DownloadCsvWithInvalidIdsReturnsNotFound() - { - var ids = new List { 8, 2, 11, 87 }; - - var result = await controller.DownloadCsvAsync(ids) as NotFoundObjectResult; - - Assert.IsNotNull(result); - Assert.AreEqual("No borehole(s) found for the provided id(s).", result.Value); - } - - [TestMethod] - public async Task DownloadCsvWithPartiallyValidIdsReturnsFileForPartillyValidIds() - { - var ids = new List { 9, 8, 0, testBoreholeId }; - - var result = await controller.DownloadCsvAsync(ids) as FileContentResult; - - Assert.IsNotNull(result); - Assert.IsNotNull(result); - Assert.AreEqual("text/csv", result.ContentType); - Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); - var csvData = Encoding.UTF8.GetString(result.FileContents); - var fileLength = csvData.Split('\n').Length; - var recordCount = fileLength - 2; - Assert.AreEqual(recordCount, 1); - } - - [TestMethod] - public async Task DownloadCsvEmptyIdsReturnsBadRequest() - { - var ids = new List(); - - var result = await controller.DownloadCsvAsync(ids) as BadRequestObjectResult; - - Assert.IsNotNull(result); - Assert.AreEqual("The list of IDs must not be empty.", result.Value); - } } diff --git a/tests/api/Controllers/ExportControllerTest.cs b/tests/api/Controllers/ExportControllerTest.cs new file mode 100644 index 000000000..2d0e4d806 --- /dev/null +++ b/tests/api/Controllers/ExportControllerTest.cs @@ -0,0 +1,230 @@ +using BDMS.Models; +using CsvHelper; +using CsvHelper.Configuration; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Globalization; +using System.Text; +using static BDMS.Helpers; + +namespace BDMS.Controllers; + +[DeploymentItem("TestData")] +[TestClass] +public class ExportControllerTest +{ + private BdmsContext context; + private ExportController controller; + private Mock> loggerMock; + private static int testBoreholeId = 1000068; + + [TestInitialize] + public void TestInitialize() + { + var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.Development.json").Build(); + + context = ContextFactory.CreateContext(); + loggerMock = new Mock>(); + + controller = new ExportController(context, loggerMock.Object) { ControllerContext = GetControllerContextAdmin() }; + } + + [TestMethod] + public async Task DownloadCsvWithValidIdsReturnsFileResultWithMax100Boreholes() + { + var ids = Enumerable.Range(testBoreholeId, 120).ToList(); + + var result = await controller.DownloadCsvAsync(ids) as FileContentResult; + + Assert.IsNotNull(result); + Assert.AreEqual("text/csv", result.ContentType); + Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); + var csvData = Encoding.UTF8.GetString(result.FileContents); + var fileLength = csvData.Split('\n').Length; + var recordCount = fileLength - 2; // Remove header and last line break + Assert.AreEqual(100, recordCount); + } + + [TestMethod] + public async Task DownloadCsvReturnsTVD() + { + var boreholeQuery = context.Boreholes + .Include(b => b.BoreholeGeometry); + + var boreholeIdsWithoutGeometry = boreholeQuery + .Where(b => b.BoreholeGeometry.Count < 2) + .Take(3).Select(b => b.Id); + + var boreholeIdsWithGeometry = boreholeQuery + .Where(b => b.BoreholeGeometry.Count > 1) + .Take(3).Select(b => b.Id); + + var boreholeIds = await boreholeIdsWithoutGeometry.Concat(boreholeIdsWithGeometry).ToListAsync(); + var result = await controller.DownloadCsvAsync(boreholeIds) as FileContentResult; + + Assert.IsNotNull(result); + Assert.AreEqual("text/csv", result.ContentType); + Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); + var records = GetRecordsFromFileContent(result); + + foreach (var record in records) + { + var totalDepthTvd = record.TotalDepthTvd; + var totalDepthMd = record.TotalDepth; + var topBedrockFreshTvd = record.TopBedrockFreshTvd; + var topBedrockFreshMd = record.TopBedrockFreshMd; + var topBedrockWeatheredTvd = record.TopBedrockWeatheredTvd; + var topBedrockWeatheredMd = record.TopBedrockWeatheredMd; + + if (boreholeIdsWithoutGeometry.Select(b => b.ToString()).ToList().Contains(record.Id)) + { + Assert.AreEqual(totalDepthMd, totalDepthTvd); + Assert.AreEqual(topBedrockFreshMd, topBedrockFreshTvd); + Assert.AreEqual(topBedrockWeatheredMd, topBedrockWeatheredTvd); + } + + if (boreholeIdsWithGeometry.Select(b => b.ToString()).ToList().Contains(record.Id)) + { + Assert.AreNotEqual(totalDepthMd, totalDepthTvd); + Assert.AreNotEqual(topBedrockFreshMd, topBedrockFreshTvd); + if (topBedrockWeatheredMd != "") + { + Assert.AreNotEqual(topBedrockWeatheredMd, topBedrockWeatheredTvd); + } + else + { + Assert.AreEqual("", topBedrockWeatheredTvd); + } + } + } + + // Assert values for single borehole with geometry + var singleRecord = records.Single(r => r.Id == "1000002"); + Assert.AreEqual("680.5358560199551", singleRecord.TotalDepth); + Assert.AreEqual("601.9441138962023", singleRecord.TopBedrockFreshMd); + Assert.AreEqual("", singleRecord.TopBedrockWeatheredMd); + Assert.AreEqual("216.25173394135473", singleRecord.TotalDepthTvd); + Assert.AreEqual("191.34988682963814", singleRecord.TopBedrockFreshTvd); + Assert.AreEqual("", singleRecord.TopBedrockWeatheredTvd); + } + + [TestMethod] + public async Task DownloadCsvWithCustomIds() + { + var firstBoreholeId = 1_009_068; + var boreholeWithCustomIds = new Borehole + { + Id = firstBoreholeId, + BoreholeCodelists = new List + { + new BoreholeCodelist + { + BoreholeId = firstBoreholeId, + CodelistId = 100000010, + Value = "ID GeoDIN value", + }, + new BoreholeCodelist + { + BoreholeId = firstBoreholeId, + CodelistId = 100000011, + Value = "ID Kernlager value", + }, + }, + }; + + var secondBoreholeId = 1_009_069; + var boreholeWithOtherCustomIds = new Borehole + { + Id = secondBoreholeId, + BoreholeCodelists = new List + { + new BoreholeCodelist + { + BoreholeId = secondBoreholeId, + CodelistId = 100000010, + Value = "ID GeoDIN value", + }, + new BoreholeCodelist + { + BoreholeId = secondBoreholeId, + CodelistId = 100000009, + Value = "ID TopFels value", + }, + }, + }; + + context.AddRange(boreholeWithCustomIds, boreholeWithOtherCustomIds); + + var ids = new List { firstBoreholeId, secondBoreholeId }; + + var result = await controller.DownloadCsvAsync(ids) as FileContentResult; + Assert.IsNotNull(result); + Assert.AreEqual("text/csv", result.ContentType); + Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); + + var records = GetRecordsFromFileContent(result); + + var firstBorehole = records.Find(r => r.Id == firstBoreholeId.ToString()); + Assert.IsNotNull(firstBorehole); + Assert.AreEqual("ID GeoDIN value", firstBorehole.IDGeODin); + Assert.AreEqual("ID Kernlager value", firstBorehole.IDKernlager); + Assert.AreEqual("", firstBorehole.IDTopFels); + + var secondBorehole = records.Find(r => r.Id == secondBoreholeId.ToString()); + Assert.IsNotNull(secondBorehole); + Assert.AreEqual("ID GeoDIN value", secondBorehole.IDGeODin); + Assert.AreEqual("", secondBorehole.IDKernlager); + Assert.AreEqual("ID TopFels value", secondBorehole.IDTopFels); + } + + [TestMethod] + public async Task DownloadCsvWithInvalidIdsReturnsNotFound() + { + var ids = new List { 8, 2, 11, 87 }; + + var result = await controller.DownloadCsvAsync(ids) as NotFoundObjectResult; + + Assert.IsNotNull(result); + Assert.AreEqual("No borehole(s) found for the provided id(s).", result.Value); + } + + [TestMethod] + public async Task DownloadCsvWithPartiallyValidIdsReturnsFileForPartillyValidIds() + { + var ids = new List { 9, 8, 0, testBoreholeId }; + + var result = await controller.DownloadCsvAsync(ids) as FileContentResult; + + Assert.IsNotNull(result); + Assert.IsNotNull(result); + Assert.AreEqual("text/csv", result.ContentType); + Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); + var csvData = Encoding.UTF8.GetString(result.FileContents); + var fileLength = csvData.Split('\n').Length; + var recordCount = fileLength - 2; + Assert.AreEqual(recordCount, 1); + } + + [TestMethod] + public async Task DownloadCsvEmptyIdsReturnsBadRequest() + { + var ids = new List(); + + var result = await controller.DownloadCsvAsync(ids) as BadRequestObjectResult; + + Assert.IsNotNull(result); + Assert.AreEqual("The list of IDs must not be empty.", result.Value); + } + + private static List GetRecordsFromFileContent(FileContentResult result) + { + var memoryStream = new MemoryStream(result.FileContents); + var reader = new StreamReader(memoryStream); + var csv = new CsvReader(reader, CsvConfigHelper.CsvWriteConfig); + return csv.GetRecords().ToList(); + } +} From 51170e3320abcf9bed164a3a65992cf7a0569ad5 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 12:13:01 +0100 Subject: [PATCH 02/13] Fix endpoint path --- src/api/Controllers/ExportController.cs | 4 +- src/client/cypress/e2e/helpers/testHelpers.js | 2 +- src/client/src/api/borehole.ts | 2 +- tests/api/Controllers/ExportControllerTest.cs | 81 +++++++++++-------- 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/api/Controllers/ExportController.cs b/src/api/Controllers/ExportController.cs index ee8988e58..86eaa9d22 100644 --- a/src/api/Controllers/ExportController.cs +++ b/src/api/Controllers/ExportController.cs @@ -32,9 +32,9 @@ public ExportController(BdmsContext context, ILogger logger) /// /// The list of IDs for the boreholes to be exported. /// A CSV file containing the details of the specified boreholes. - [HttpGet("export-csv")] + [HttpGet("csv")] [Authorize(Policy = PolicyNames.Viewer)] - public async Task DownloadCsvAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable ids) + public async Task ExportCsvAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable ids) { List idList = ids.Take(MaxPageSize).ToList(); if (idList.Count < 1) return BadRequest("The list of IDs must not be empty."); diff --git a/src/client/cypress/e2e/helpers/testHelpers.js b/src/client/cypress/e2e/helpers/testHelpers.js index f6c5afe60..9811eb999 100644 --- a/src/client/cypress/e2e/helpers/testHelpers.js +++ b/src/client/cypress/e2e/helpers/testHelpers.js @@ -33,7 +33,7 @@ export const interceptApiCalls = () => { cy.intercept("PUT", "/api/v2/layer").as("update-layer"); cy.intercept("/api/v2/location/identify**").as("location"); cy.intercept("/api/v2/borehole/copy*").as("borehole_copy"); - cy.intercept("/api/v2/borehole/export-csv**").as("borehole_export_csv"); + cy.intercept("/api/v2/export/csv**").as("borehole_export_csv"); cy.intercept("/api/v2/borehole/**").as("borehole_by_id"); cy.intercept("PUT", "/api/v2/borehole").as("update-borehole"); diff --git a/src/client/src/api/borehole.ts b/src/client/src/api/borehole.ts index 2f4ff93dc..3d922cd2e 100644 --- a/src/client/src/api/borehole.ts +++ b/src/client/src/api/borehole.ts @@ -87,5 +87,5 @@ export const getAllBoreholes = async (ids: number[] | GridRowSelectionModel, pag export const exportCSVBorehole = async (boreholeIds: GridRowSelectionModel) => { const idsQuery = boreholeIds.map(id => `ids=${id}`).join("&"); - return await fetchApiV2(`borehole/export-csv?${idsQuery}`, "GET"); + return await fetchApiV2(`export/csv?${idsQuery}`, "GET"); }; diff --git a/tests/api/Controllers/ExportControllerTest.cs b/tests/api/Controllers/ExportControllerTest.cs index 2d0e4d806..18bd9b507 100644 --- a/tests/api/Controllers/ExportControllerTest.cs +++ b/tests/api/Controllers/ExportControllerTest.cs @@ -19,6 +19,7 @@ public class ExportControllerTest { private BdmsContext context; private ExportController controller; + private BoreholeController boreholeController; private Mock> loggerMock; private static int testBoreholeId = 1000068; @@ -27,9 +28,13 @@ public void TestInitialize() { var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.Development.json").Build(); - context = ContextFactory.CreateContext(); + context = ContextFactory.GetTestContext(); loggerMock = new Mock>(); - + var boreholeLockServiceMock = new Mock(MockBehavior.Strict); + boreholeLockServiceMock + .Setup(x => x.IsBoreholeLockedAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(false); + boreholeController = new BoreholeController(context, new Mock>().Object, boreholeLockServiceMock.Object) { ControllerContext = GetControllerContextAdmin() }; controller = new ExportController(context, loggerMock.Object) { ControllerContext = GetControllerContextAdmin() }; } @@ -38,7 +43,7 @@ public async Task DownloadCsvWithValidIdsReturnsFileResultWithMax100Boreholes() { var ids = Enumerable.Range(testBoreholeId, 120).ToList(); - var result = await controller.DownloadCsvAsync(ids) as FileContentResult; + var result = await controller.ExportCsvAsync(ids) as FileContentResult; Assert.IsNotNull(result); Assert.AreEqual("text/csv", result.ContentType); @@ -64,7 +69,7 @@ public async Task DownloadCsvReturnsTVD() .Take(3).Select(b => b.Id); var boreholeIds = await boreholeIdsWithoutGeometry.Concat(boreholeIdsWithGeometry).ToListAsync(); - var result = await controller.DownloadCsvAsync(boreholeIds) as FileContentResult; + var result = await controller.ExportCsvAsync(boreholeIds) as FileContentResult; Assert.IsNotNull(result); Assert.AreEqual("text/csv", result.ContentType); @@ -115,25 +120,32 @@ public async Task DownloadCsvReturnsTVD() [TestMethod] public async Task DownloadCsvWithCustomIds() { + // These codelists are used to make the TestContext aware of the Codelists, so that they can be included in the download controller. + var codelistGeoDIN = new Codelist { Id = 100000010, En = "ID GeODin" }; + var codelistKernlager = new Codelist { Id = 100000011, En = "ID Kernlager" }; + var codelistTopFels = new Codelist { Id = 100000009, En = "ID TopFels" }; + var firstBoreholeId = 1_009_068; var boreholeWithCustomIds = new Borehole { Id = firstBoreholeId, BoreholeCodelists = new List + { + new BoreholeCodelist { - new BoreholeCodelist - { - BoreholeId = firstBoreholeId, - CodelistId = 100000010, - Value = "ID GeoDIN value", - }, - new BoreholeCodelist - { - BoreholeId = firstBoreholeId, - CodelistId = 100000011, - Value = "ID Kernlager value", - }, + BoreholeId = firstBoreholeId, + CodelistId = codelistGeoDIN.Id, + Codelist = codelistGeoDIN, + Value = "ID GeoDIN value", + }, + new BoreholeCodelist + { + BoreholeId = firstBoreholeId, + CodelistId = codelistKernlager.Id, + Codelist = codelistKernlager, + Value = "ID Kernlager value", }, + }, }; var secondBoreholeId = 1_009_069; @@ -141,27 +153,30 @@ public async Task DownloadCsvWithCustomIds() { Id = secondBoreholeId, BoreholeCodelists = new List + { + new BoreholeCodelist { - new BoreholeCodelist - { - BoreholeId = secondBoreholeId, - CodelistId = 100000010, - Value = "ID GeoDIN value", - }, - new BoreholeCodelist - { - BoreholeId = secondBoreholeId, - CodelistId = 100000009, - Value = "ID TopFels value", - }, + BoreholeId = secondBoreholeId, + CodelistId = codelistGeoDIN.Id, + Codelist = codelistGeoDIN, + Value = "ID GeoDIN value", + }, + new BoreholeCodelist + { + BoreholeId = secondBoreholeId, + CodelistId = codelistTopFels.Id, + Codelist = codelistTopFels, + Value = "ID TopFels value", }, + }, }; - context.AddRange(boreholeWithCustomIds, boreholeWithOtherCustomIds); + await boreholeController.CreateAsync(boreholeWithCustomIds).ConfigureAwait(false); + await boreholeController.CreateAsync(boreholeWithOtherCustomIds).ConfigureAwait(false); var ids = new List { firstBoreholeId, secondBoreholeId }; - var result = await controller.DownloadCsvAsync(ids) as FileContentResult; + var result = await controller.ExportCsvAsync(ids) as FileContentResult; Assert.IsNotNull(result); Assert.AreEqual("text/csv", result.ContentType); Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); @@ -186,7 +201,7 @@ public async Task DownloadCsvWithInvalidIdsReturnsNotFound() { var ids = new List { 8, 2, 11, 87 }; - var result = await controller.DownloadCsvAsync(ids) as NotFoundObjectResult; + var result = await controller.ExportCsvAsync(ids) as NotFoundObjectResult; Assert.IsNotNull(result); Assert.AreEqual("No borehole(s) found for the provided id(s).", result.Value); @@ -197,7 +212,7 @@ public async Task DownloadCsvWithPartiallyValidIdsReturnsFileForPartillyValidIds { var ids = new List { 9, 8, 0, testBoreholeId }; - var result = await controller.DownloadCsvAsync(ids) as FileContentResult; + var result = await controller.ExportCsvAsync(ids) as FileContentResult; Assert.IsNotNull(result); Assert.IsNotNull(result); @@ -214,7 +229,7 @@ public async Task DownloadCsvEmptyIdsReturnsBadRequest() { var ids = new List(); - var result = await controller.DownloadCsvAsync(ids) as BadRequestObjectResult; + var result = await controller.ExportCsvAsync(ids) as BadRequestObjectResult; Assert.IsNotNull(result); Assert.AreEqual("The list of IDs must not be empty.", result.Value); From f1816e88835ded759976bd2bfdb7d2272408f8fb Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 12:19:13 +0100 Subject: [PATCH 03/13] Add LV03 to export --- src/api/Controllers/ExportController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/api/Controllers/ExportController.cs b/src/api/Controllers/ExportController.cs index 86eaa9d22..999eb3a51 100644 --- a/src/api/Controllers/ExportController.cs +++ b/src/api/Controllers/ExportController.cs @@ -28,7 +28,7 @@ public ExportController(BdmsContext context, ILogger logger) } /// - /// Exports the details of up to boreholes as a CSV file. Filters the boreholes based on the provided list of IDs. + /// Exports the details of up to boreholes as a CSV file. Filters the boreholes based on the provided list of IDs.export /// /// The list of IDs for the boreholes to be exported. /// A CSV file containing the details of the specified boreholes. @@ -62,6 +62,8 @@ public async Task ExportCsvAsync([FromQuery][MaxLength(MaxPageSiz csvWriter.WriteField(nameof(Borehole.NationalInterest)); csvWriter.WriteField(nameof(Borehole.LocationX)); csvWriter.WriteField(nameof(Borehole.LocationY)); + csvWriter.WriteField(nameof(Borehole.LocationXLV03)); + csvWriter.WriteField(nameof(Borehole.LocationYLV03)); csvWriter.WriteField(nameof(Borehole.LocationPrecisionId)); csvWriter.WriteField(nameof(Borehole.ElevationZ)); csvWriter.WriteField(nameof(Borehole.ElevationPrecisionId)); @@ -123,6 +125,8 @@ public async Task ExportCsvAsync([FromQuery][MaxLength(MaxPageSiz csvWriter.WriteField(b.NationalInterest); csvWriter.WriteField(b.LocationX); csvWriter.WriteField(b.LocationY); + csvWriter.WriteField(b.LocationXLV03); + csvWriter.WriteField(b.LocationYLV03); csvWriter.WriteField(b.LocationPrecisionId); csvWriter.WriteField(b.ElevationZ); csvWriter.WriteField(b.ElevationPrecisionId); From 21bcb31fcfadcb0b2f7449441f419e6470240ef2 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 12:27:32 +0100 Subject: [PATCH 04/13] Fix warning --- src/api/Controllers/ExportController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/Controllers/ExportController.cs b/src/api/Controllers/ExportController.cs index 999eb3a51..5a7046bb3 100644 --- a/src/api/Controllers/ExportController.cs +++ b/src/api/Controllers/ExportController.cs @@ -28,7 +28,7 @@ public ExportController(BdmsContext context, ILogger logger) } /// - /// Exports the details of up to boreholes as a CSV file. Filters the boreholes based on the provided list of IDs.export + /// Exports the details of up to boreholes as a CSV file. Filters the boreholes based on the provided list of IDs. /// /// The list of IDs for the boreholes to be exported. /// A CSV file containing the details of the specified boreholes. From 797f16188972c4b3e2944fcb45473234e5cb9848 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 12:57:26 +0100 Subject: [PATCH 05/13] Fix export controller test --- tests/api/Controllers/ExportControllerTest.cs | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/tests/api/Controllers/ExportControllerTest.cs b/tests/api/Controllers/ExportControllerTest.cs index 18bd9b507..7cb2323c0 100644 --- a/tests/api/Controllers/ExportControllerTest.cs +++ b/tests/api/Controllers/ExportControllerTest.cs @@ -19,7 +19,6 @@ public class ExportControllerTest { private BdmsContext context; private ExportController controller; - private BoreholeController boreholeController; private Mock> loggerMock; private static int testBoreholeId = 1000068; @@ -30,11 +29,6 @@ public void TestInitialize() context = ContextFactory.GetTestContext(); loggerMock = new Mock>(); - var boreholeLockServiceMock = new Mock(MockBehavior.Strict); - boreholeLockServiceMock - .Setup(x => x.IsBoreholeLockedAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(false); - boreholeController = new BoreholeController(context, new Mock>().Object, boreholeLockServiceMock.Object) { ControllerContext = GetControllerContextAdmin() }; controller = new ExportController(context, loggerMock.Object) { ControllerContext = GetControllerContextAdmin() }; } @@ -120,11 +114,6 @@ public async Task DownloadCsvReturnsTVD() [TestMethod] public async Task DownloadCsvWithCustomIds() { - // These codelists are used to make the TestContext aware of the Codelists, so that they can be included in the download controller. - var codelistGeoDIN = new Codelist { Id = 100000010, En = "ID GeODin" }; - var codelistKernlager = new Codelist { Id = 100000011, En = "ID Kernlager" }; - var codelistTopFels = new Codelist { Id = 100000009, En = "ID TopFels" }; - var firstBoreholeId = 1_009_068; var boreholeWithCustomIds = new Borehole { @@ -134,15 +123,13 @@ public async Task DownloadCsvWithCustomIds() new BoreholeCodelist { BoreholeId = firstBoreholeId, - CodelistId = codelistGeoDIN.Id, - Codelist = codelistGeoDIN, + CodelistId = 100000010, Value = "ID GeoDIN value", }, new BoreholeCodelist { BoreholeId = firstBoreholeId, - CodelistId = codelistKernlager.Id, - Codelist = codelistKernlager, + CodelistId = 100000011, Value = "ID Kernlager value", }, }, @@ -157,22 +144,20 @@ public async Task DownloadCsvWithCustomIds() new BoreholeCodelist { BoreholeId = secondBoreholeId, - CodelistId = codelistGeoDIN.Id, - Codelist = codelistGeoDIN, + CodelistId = 100000010, Value = "ID GeoDIN value", }, new BoreholeCodelist { BoreholeId = secondBoreholeId, - CodelistId = codelistTopFels.Id, - Codelist = codelistTopFels, + CodelistId = 100000009, Value = "ID TopFels value", }, }, }; - await boreholeController.CreateAsync(boreholeWithCustomIds).ConfigureAwait(false); - await boreholeController.CreateAsync(boreholeWithOtherCustomIds).ConfigureAwait(false); + context.Boreholes.AddRange(boreholeWithCustomIds, boreholeWithOtherCustomIds); + await context.SaveChangesAsync(); var ids = new List { firstBoreholeId, secondBoreholeId }; From 93044984cff91d365f4bcd89cc6e28dddd6747e1 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 12:58:59 +0100 Subject: [PATCH 06/13] Fix csv indexes --- src/client/cypress/e2e/mainPage/export.cy.js | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/client/cypress/e2e/mainPage/export.cy.js b/src/client/cypress/e2e/mainPage/export.cy.js index 06b9bec38..693da6e56 100644 --- a/src/client/cypress/e2e/mainPage/export.cy.js +++ b/src/client/cypress/e2e/mainPage/export.cy.js @@ -34,12 +34,12 @@ const verifyTVDContentInCSVFile = ( cy.readFile(prepareDownloadPath(fileName)).then(fileContent => { const { lines, rows } = splitFileContent(fileContent); expect(lines.length).to.equal(3); - expect(rows[0][24]).to.equal("TotalDepthTvd"); - expect(rows[1][24]).to.equal(expectedTotalDepthVD); - expect(rows[0][25]).to.equal("TopBedrockFreshTvd"); - expect(rows[1][25]).to.equal(expectedTopBedrockFreshTVD); - expect(rows[0][26]).to.equal("TopBedrockWeatheredTvd"); - expect(rows[1][26]).to.equal(expectedTopBedrockWeatheredTVD); + expect(rows[0][26]).to.equal("TotalDepthTvd"); + expect(rows[1][26]).to.equal(expectedTotalDepthVD); + expect(rows[0][27]).to.equal("TopBedrockFreshTvd"); + expect(rows[1][27]).to.equal(expectedTopBedrockFreshTVD); + expect(rows[0][28]).to.equal("TopBedrockWeatheredTvd"); + expect(rows[1][28]).to.equal(expectedTopBedrockWeatheredTVD); }); }; @@ -170,13 +170,13 @@ describe("Test for exporting boreholes.", () => { expect(rows[1][3]).to.equal(firstBoreholeName); expect(rows[2][3]).to.equal(secondBoreholeName); - expect(rows[0][31]).to.equal("IDInfoGeol"); - expect(rows[1][31]).to.equal(""); - expect(rows[2][31]).to.equal("14"); + expect(rows[0][33]).to.equal("IDInfoGeol"); + expect(rows[1][33]).to.equal(""); + expect(rows[2][33]).to.equal("14"); - expect(rows[0][32]).to.equal("IDGeODin\r"); - expect(rows[1][32]).to.equal("13\r"); - expect(rows[2][32]).to.equal("\r"); + expect(rows[0][34]).to.equal("IDGeODin\r"); + expect(rows[1][34]).to.equal("13\r"); + expect(rows[2][34]).to.equal("\r"); }); deleteItem(); handlePrompt("Do you really want to delete these 2 boreholes? This cannot be undone.", "Delete"); From 452b9390aaa3b77556e5a2671180c1d9d3f4b281 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 13:32:22 +0100 Subject: [PATCH 07/13] Fix some sonar cloud issues --- src/api/Controllers/ExportController.cs | 6 +- src/api/Controllers/ImportController.cs | 2 +- tests/api/Controllers/ExportControllerTest.cs | 59 +++++++++---------- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/api/Controllers/ExportController.cs b/src/api/Controllers/ExportController.cs index 5a7046bb3..34588804e 100644 --- a/src/api/Controllers/ExportController.cs +++ b/src/api/Controllers/ExportController.cs @@ -1,12 +1,10 @@ using BDMS.Authentication; using BDMS.Models; using CsvHelper; -using CsvHelper.Configuration; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Text; namespace BDMS.Controllers; @@ -19,12 +17,10 @@ public class ExportController : ControllerBase // This also applies to the number of filtered ids to ensure the URL length does not exceed the maximum allowed length. private const int MaxPageSize = 100; private readonly BdmsContext context; - private readonly ILogger logger; - public ExportController(BdmsContext context, ILogger logger) + public ExportController(BdmsContext context) { this.context = context; - this.logger = logger; } /// diff --git a/src/api/Controllers/ImportController.cs b/src/api/Controllers/ImportController.cs index 657ac00d2..ad234e7c3 100644 --- a/src/api/Controllers/ImportController.cs +++ b/src/api/Controllers/ImportController.cs @@ -340,7 +340,7 @@ internal static bool CompareValuesWithTolerance(double? firstValue, double? seco return Math.Abs(firstValue.Value - secondValue.Value) <= tolerance; } - private List ReadBoreholesFromCsv(IFormFile file) + private static List ReadBoreholesFromCsv(IFormFile file) { using var reader = new StreamReader(file.OpenReadStream()); using var csv = new CsvReader(reader, CsvConfigHelper.CsvReadConfig); diff --git a/tests/api/Controllers/ExportControllerTest.cs b/tests/api/Controllers/ExportControllerTest.cs index 7cb2323c0..e65093225 100644 --- a/tests/api/Controllers/ExportControllerTest.cs +++ b/tests/api/Controllers/ExportControllerTest.cs @@ -1,13 +1,9 @@ using BDMS.Models; using CsvHelper; -using CsvHelper.Configuration; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using System.Globalization; using System.Text; using static BDMS.Helpers; @@ -17,9 +13,10 @@ namespace BDMS.Controllers; [TestClass] public class ExportControllerTest { + private const string TestCsvString = "text/csv"; + private const string ExportFileName = "boreholes_export.csv"; private BdmsContext context; private ExportController controller; - private Mock> loggerMock; private static int testBoreholeId = 1000068; [TestInitialize] @@ -28,8 +25,7 @@ public void TestInitialize() var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.Development.json").Build(); context = ContextFactory.GetTestContext(); - loggerMock = new Mock>(); - controller = new ExportController(context, loggerMock.Object) { ControllerContext = GetControllerContextAdmin() }; + controller = new ExportController(context) { ControllerContext = GetControllerContextAdmin() }; } [TestMethod] @@ -40,8 +36,8 @@ public async Task DownloadCsvWithValidIdsReturnsFileResultWithMax100Boreholes() var result = await controller.ExportCsvAsync(ids) as FileContentResult; Assert.IsNotNull(result); - Assert.AreEqual("text/csv", result.ContentType); - Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); + Assert.AreEqual(TestCsvString, result.ContentType); + Assert.AreEqual(ExportFileName, result.FileDownloadName); var csvData = Encoding.UTF8.GetString(result.FileContents); var fileLength = csvData.Split('\n').Length; var recordCount = fileLength - 2; // Remove header and last line break @@ -54,20 +50,20 @@ public async Task DownloadCsvReturnsTVD() var boreholeQuery = context.Boreholes .Include(b => b.BoreholeGeometry); - var boreholeIdsWithoutGeometry = boreholeQuery + var boreholeIdsWithoutGeometry = await boreholeQuery .Where(b => b.BoreholeGeometry.Count < 2) - .Take(3).Select(b => b.Id); + .Take(3).Select(b => b.Id).ToListAsync(); - var boreholeIdsWithGeometry = boreholeQuery + var boreholeIdsWithGeometry = await boreholeQuery .Where(b => b.BoreholeGeometry.Count > 1) - .Take(3).Select(b => b.Id); + .Take(3).Select(b => b.Id).ToListAsync(); - var boreholeIds = await boreholeIdsWithoutGeometry.Concat(boreholeIdsWithGeometry).ToListAsync(); + var boreholeIds = boreholeIdsWithoutGeometry.Concat(boreholeIdsWithGeometry); var result = await controller.ExportCsvAsync(boreholeIds) as FileContentResult; Assert.IsNotNull(result); - Assert.AreEqual("text/csv", result.ContentType); - Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); + Assert.AreEqual(TestCsvString, result.ContentType); + Assert.AreEqual(ExportFileName, result.FileDownloadName); var records = GetRecordsFromFileContent(result); foreach (var record in records) @@ -79,14 +75,14 @@ public async Task DownloadCsvReturnsTVD() var topBedrockWeatheredTvd = record.TopBedrockWeatheredTvd; var topBedrockWeatheredMd = record.TopBedrockWeatheredMd; - if (boreholeIdsWithoutGeometry.Select(b => b.ToString()).ToList().Contains(record.Id)) + if (boreholeIdsWithoutGeometry.Contains(int.Parse(record.Id))) { Assert.AreEqual(totalDepthMd, totalDepthTvd); Assert.AreEqual(topBedrockFreshMd, topBedrockFreshTvd); Assert.AreEqual(topBedrockWeatheredMd, topBedrockWeatheredTvd); } - if (boreholeIdsWithGeometry.Select(b => b.ToString()).ToList().Contains(record.Id)) + if (boreholeIdsWithGeometry.Contains(int.Parse(record.Id))) { Assert.AreNotEqual(totalDepthMd, totalDepthTvd); Assert.AreNotEqual(topBedrockFreshMd, topBedrockFreshTvd); @@ -114,6 +110,9 @@ public async Task DownloadCsvReturnsTVD() [TestMethod] public async Task DownloadCsvWithCustomIds() { + string idGeoDinValue = "ID GeoDIN value"; + string idTopFelsValue = "ID TopFels value"; + string idKernlagerValue = "ID Kernlager value"; var firstBoreholeId = 1_009_068; var boreholeWithCustomIds = new Borehole { @@ -124,13 +123,13 @@ public async Task DownloadCsvWithCustomIds() { BoreholeId = firstBoreholeId, CodelistId = 100000010, - Value = "ID GeoDIN value", + Value = idGeoDinValue, }, new BoreholeCodelist { BoreholeId = firstBoreholeId, CodelistId = 100000011, - Value = "ID Kernlager value", + Value = idKernlagerValue, }, }, }; @@ -145,13 +144,13 @@ public async Task DownloadCsvWithCustomIds() { BoreholeId = secondBoreholeId, CodelistId = 100000010, - Value = "ID GeoDIN value", + Value = idGeoDinValue, }, new BoreholeCodelist { BoreholeId = secondBoreholeId, CodelistId = 100000009, - Value = "ID TopFels value", + Value = idTopFelsValue, }, }, }; @@ -163,22 +162,22 @@ public async Task DownloadCsvWithCustomIds() var result = await controller.ExportCsvAsync(ids) as FileContentResult; Assert.IsNotNull(result); - Assert.AreEqual("text/csv", result.ContentType); - Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); + Assert.AreEqual(TestCsvString, result.ContentType); + Assert.AreEqual(ExportFileName, result.FileDownloadName); var records = GetRecordsFromFileContent(result); var firstBorehole = records.Find(r => r.Id == firstBoreholeId.ToString()); Assert.IsNotNull(firstBorehole); - Assert.AreEqual("ID GeoDIN value", firstBorehole.IDGeODin); - Assert.AreEqual("ID Kernlager value", firstBorehole.IDKernlager); + Assert.AreEqual(idGeoDinValue, firstBorehole.IDGeODin); + Assert.AreEqual(idKernlagerValue, firstBorehole.IDKernlager); Assert.AreEqual("", firstBorehole.IDTopFels); var secondBorehole = records.Find(r => r.Id == secondBoreholeId.ToString()); Assert.IsNotNull(secondBorehole); - Assert.AreEqual("ID GeoDIN value", secondBorehole.IDGeODin); + Assert.AreEqual(idGeoDinValue, secondBorehole.IDGeODin); Assert.AreEqual("", secondBorehole.IDKernlager); - Assert.AreEqual("ID TopFels value", secondBorehole.IDTopFels); + Assert.AreEqual(idTopFelsValue, secondBorehole.IDTopFels); } [TestMethod] @@ -201,8 +200,8 @@ public async Task DownloadCsvWithPartiallyValidIdsReturnsFileForPartillyValidIds Assert.IsNotNull(result); Assert.IsNotNull(result); - Assert.AreEqual("text/csv", result.ContentType); - Assert.AreEqual("boreholes_export.csv", result.FileDownloadName); + Assert.AreEqual(TestCsvString, result.ContentType); + Assert.AreEqual(ExportFileName, result.FileDownloadName); var csvData = Encoding.UTF8.GetString(result.FileContents); var fileLength = csvData.Split('\n').Length; var recordCount = fileLength - 2; From 3b770680a67a5508ce159ee8b44f7023a64baeaa Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 13:57:46 +0100 Subject: [PATCH 08/13] Reuse csv config --- src/api/Controllers/ImportController.cs | 8 +------- tests/api/Controllers/ExportControllerTest.cs | 2 -- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/api/Controllers/ImportController.cs b/src/api/Controllers/ImportController.cs index ad234e7c3..1c8ebaf2c 100644 --- a/src/api/Controllers/ImportController.cs +++ b/src/api/Controllers/ImportController.cs @@ -385,13 +385,7 @@ private sealed class CsvImportBoreholeMap : ClassMap public CsvImportBoreholeMap() { - var config = new CsvConfiguration(swissCulture) - { - IgnoreReferences = true, - PrepareHeaderForMatch = args => args.Header.Humanize(LetterCasing.Title), - }; - - AutoMap(config); + AutoMap(CsvConfigHelper.CsvReadConfig); // Define all optional properties of Borehole (ef navigation properties do not need to be defined as optional). Map(m => m.CreatedById).Optional(); diff --git a/tests/api/Controllers/ExportControllerTest.cs b/tests/api/Controllers/ExportControllerTest.cs index e65093225..a18d32d41 100644 --- a/tests/api/Controllers/ExportControllerTest.cs +++ b/tests/api/Controllers/ExportControllerTest.cs @@ -22,8 +22,6 @@ public class ExportControllerTest [TestInitialize] public void TestInitialize() { - var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.Development.json").Build(); - context = ContextFactory.GetTestContext(); controller = new ExportController(context) { ControllerContext = GetControllerContextAdmin() }; } From f2f9c74d1c595978b9d5615b7477919c02d89657 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 14:02:38 +0100 Subject: [PATCH 09/13] Remove unused fields --- src/api/Controllers/ImportController.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/Controllers/ImportController.cs b/src/api/Controllers/ImportController.cs index 1c8ebaf2c..e836d828a 100644 --- a/src/api/Controllers/ImportController.cs +++ b/src/api/Controllers/ImportController.cs @@ -381,8 +381,6 @@ private void AddValidationErrorToModelState(int boreholeIndex, string errorMessa private sealed class CsvImportBoreholeMap : ClassMap { - private readonly CultureInfo swissCulture = new("de-CH"); - public CsvImportBoreholeMap() { AutoMap(CsvConfigHelper.CsvReadConfig); From 6850af38155ce7c586acee82a6d9257f9d4bf0c7 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 14:21:37 +0100 Subject: [PATCH 10/13] Add missing imports --- src/client/cypress/e2e/mainPage/export.cy.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/cypress/e2e/mainPage/export.cy.js b/src/client/cypress/e2e/mainPage/export.cy.js index a57849b77..0657eb676 100644 --- a/src/client/cypress/e2e/mainPage/export.cy.js +++ b/src/client/cypress/e2e/mainPage/export.cy.js @@ -1,4 +1,11 @@ -import { deleteItem, exportCSVItem, exportItem, exportJsonItem } from "../helpers/buttonHelpers"; +import { + addItem, + deleteItem, + exportCSVItem, + exportItem, + exportJsonItem, + saveWithSaveBar, +} from "../helpers/buttonHelpers"; import { checkAllVisibleRows, checkRowWithText, showTableAndWaitForData } from "../helpers/dataGridHelpers.js"; import { evaluateInput, setInput, setSelect } from "../helpers/formHelpers"; import { From e295724a2d6a19c37e24102b08d58af84340472a Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 15:20:08 +0100 Subject: [PATCH 11/13] Add export statements to test --- src/client/cypress/e2e/mainPage/export.cy.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/cypress/e2e/mainPage/export.cy.js b/src/client/cypress/e2e/mainPage/export.cy.js index 0657eb676..f8d014b83 100644 --- a/src/client/cypress/e2e/mainPage/export.cy.js +++ b/src/client/cypress/e2e/mainPage/export.cy.js @@ -108,6 +108,7 @@ describe("Test for exporting boreholes.", () => { saveWithSaveBar(); stopBoreholeEditing(); + exportItem(); exportCSVItem(); verifyTVDContentInCSVFile(fileName, "700", "800", "900"); @@ -143,6 +144,7 @@ describe("Test for exporting boreholes.", () => { setInput("originalName", secondBoreholeName); // change name to avoid potential CSV filename conflict saveWithSaveBar(); stopBoreholeEditing(); + exportItem(); exportCSVItem(); verifyTVDContentInCSVFile(secondFileName, "674.8678208299723", "762.6098263945338", "846.9637100889873"); startBoreholeEditing(); @@ -172,6 +174,7 @@ describe("Test for exporting boreholes.", () => { showTableAndWaitForData(); checkRowWithText(firstBoreholeName); checkRowWithText(secondBoreholeName); + exportItem(); exportCSVItem(); cy.readFile(prepareDownloadPath(csvFileName)).then(fileContent => { const { lines, rows } = splitFileContent(fileContent); @@ -206,6 +209,7 @@ describe("Test for exporting boreholes.", () => { handlePrompt(moreThan100SelectedPrompt, "Cancel"); exportItem(); handlePrompt(moreThan100SelectedPrompt, "Export 100 boreholes"); + exportItem(); exportCSVItem(); cy.wait("@borehole_export_csv").its("response.statusCode").should("eq", 200); readDownloadedFile(csvFileName); From 5efda6fbb289c1fc2f21157b79e3b3287d486712 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 16:09:59 +0100 Subject: [PATCH 12/13] Improve ExportController --- src/api/Controllers/ExportController.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/api/Controllers/ExportController.cs b/src/api/Controllers/ExportController.cs index 34588804e..9d70f90cf 100644 --- a/src/api/Controllers/ExportController.cs +++ b/src/api/Controllers/ExportController.cs @@ -36,8 +36,7 @@ public async Task ExportCsvAsync([FromQuery][MaxLength(MaxPageSiz if (idList.Count < 1) return BadRequest("The list of IDs must not be empty."); var boreholes = await context.Boreholes - .Include(b => b.BoreholeCodelists) - .ThenInclude(bc => bc.Codelist) + .Include(b => b.BoreholeCodelists).ThenInclude(bc => bc.Codelist) .Where(borehole => idList.Contains(borehole.Id)) .OrderBy(b => idList.IndexOf(b.Id)) .ToListAsync() @@ -85,7 +84,7 @@ public async Task ExportCsvAsync([FromQuery][MaxLength(MaxPageSiz // Write dynamic headers for each distinct custom Id var customIdHeaders = boreholes - .SelectMany(b => b.BoreholeCodelists ?? Enumerable.Empty()) + .SelectMany(b => GetBoreholeCodelists(b)) .Select(bc => new { bc.CodelistId, bc.Codelist?.En }) .Distinct() .OrderBy(x => x.CodelistId) @@ -149,7 +148,7 @@ public async Task ExportCsvAsync([FromQuery][MaxLength(MaxPageSiz // Write dynamic fields for custom Ids foreach (var header in customIdHeaders) { - var codelistValue = (b.BoreholeCodelists ?? Enumerable.Empty()).FirstOrDefault(bc => bc.CodelistId == header.CodelistId)?.Value; + var codelistValue = GetBoreholeCodelists(b).FirstOrDefault(bc => bc.CodelistId == header.CodelistId)?.Value; csvWriter.WriteField(codelistValue ?? ""); } @@ -160,4 +159,9 @@ public async Task ExportCsvAsync([FromQuery][MaxLength(MaxPageSiz await csvWriter.FlushAsync().ConfigureAwait(false); return File(Encoding.UTF8.GetBytes(stringWriter.ToString()), "text/csv", "boreholes_export.csv"); } + + private IEnumerable GetBoreholeCodelists(Borehole borehole) + { + return borehole.BoreholeCodelists ?? Enumerable.Empty(); + } } From ff824f27268f545507077887dc2282a104f7170f Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 17 Dec 2024 16:34:31 +0100 Subject: [PATCH 13/13] Make GetBoreholeCodelists static --- src/api/Controllers/ExportController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/Controllers/ExportController.cs b/src/api/Controllers/ExportController.cs index 9d70f90cf..386eeb19a 100644 --- a/src/api/Controllers/ExportController.cs +++ b/src/api/Controllers/ExportController.cs @@ -160,7 +160,7 @@ public async Task ExportCsvAsync([FromQuery][MaxLength(MaxPageSiz return File(Encoding.UTF8.GetBytes(stringWriter.ToString()), "text/csv", "boreholes_export.csv"); } - private IEnumerable GetBoreholeCodelists(Borehole borehole) + private static IEnumerable GetBoreholeCodelists(Borehole borehole) { return borehole.BoreholeCodelists ?? Enumerable.Empty(); }