diff --git a/src/api/BoreholeGeometry/AzIncFormat.cs b/src/api/BoreholeGeometry/AzIncFormat.cs index 4c02ed664..3f6e3d754 100644 --- a/src/api/BoreholeGeometry/AzIncFormat.cs +++ b/src/api/BoreholeGeometry/AzIncFormat.cs @@ -14,14 +14,14 @@ internal sealed class AzIncFormat : IBoreholeGeometryFormat public string Name => "Azimuth Inclination"; - private Lazy expectedCsvHeader = new(Helper.GetCSVHeader); + private Lazy expectedCsvHeader = new(CsvConfigHelper.GetCsvHeader); public string CsvHeader => expectedCsvHeader.Value; public IList ReadCsv(IFormFile file, int boreholeId) { using var reader = new StreamReader(file.OpenReadStream()); - using var csv = new CsvReader(reader, Helper.CsvConfig); + using var csv = new CsvReader(reader, CsvConfigHelper.CsvConfig); var data = csv.GetRecords().ToList(); diff --git a/src/api/BoreholeGeometry/CsvConfigHelper.cs b/src/api/BoreholeGeometry/CsvConfigHelper.cs new file mode 100644 index 000000000..211f2a71d --- /dev/null +++ b/src/api/BoreholeGeometry/CsvConfigHelper.cs @@ -0,0 +1,38 @@ +using BDMS.Models; +using CsvHelper; +using CsvHelper.Configuration; +using Humanizer; +using NetTopologySuite.Mathematics; +using NetTopologySuite.Utilities; +using System.Globalization; + +namespace BDMS.BoreholeGeometry; + +public static class CsvConfigHelper +{ + internal static readonly CsvConfiguration CsvConfig = new(new CultureInfo("de-CH")) + { + Delimiter = ";", + IgnoreReferences = true, + PrepareHeaderForMatch = args => args.Header.Humanize(LetterCasing.Title), + MissingFieldFound = null, + }; + + /// + /// Get the CSV header for a class of type . + /// 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 map = context.AutoMap(); + return string.Join("; ", map.MemberMaps + .Select(m => + { + var name = m.Data.Names.FirstOrDefault(m.Data.Member.Name); + return m.Data.IsOptional ? $"[{name}]" : name; + })); + } +} diff --git a/src/api/BoreholeGeometry/PitchRollFormat.cs b/src/api/BoreholeGeometry/PitchRollFormat.cs index 4e8049741..05054b975 100644 --- a/src/api/BoreholeGeometry/PitchRollFormat.cs +++ b/src/api/BoreholeGeometry/PitchRollFormat.cs @@ -14,14 +14,14 @@ internal sealed class PitchRollFormat : IBoreholeGeometryFormat public string Name => "Pitch Roll"; - private Lazy expectedCsvHeader = new(Helper.GetCSVHeader); + private Lazy expectedCsvHeader = new(CsvConfigHelper.GetCsvHeader); public string CsvHeader => expectedCsvHeader.Value; public IList ReadCsv(IFormFile file, int boreholeId) { using var reader = new StreamReader(file.OpenReadStream()); - using var csv = new CsvReader(reader, Helper.CsvConfig); + using var csv = new CsvReader(reader, CsvConfigHelper.CsvConfig); var data = csv.GetRecords().ToList(); diff --git a/src/api/BoreholeGeometry/XYZFormat.cs b/src/api/BoreholeGeometry/XYZFormat.cs index a44d26887..f7787b395 100644 --- a/src/api/BoreholeGeometry/XYZFormat.cs +++ b/src/api/BoreholeGeometry/XYZFormat.cs @@ -13,14 +13,14 @@ internal sealed class XYZFormat : IBoreholeGeometryFormat public string Name => "X Y Z"; - private Lazy expectedCsvHeader = new(Helper.GetCSVHeader); + private Lazy expectedCsvHeader = new(CsvConfigHelper.GetCsvHeader); public string CsvHeader => expectedCsvHeader.Value; public IList ReadCsv(IFormFile file, int boreholeId) { using var reader = new StreamReader(file.OpenReadStream()); - using var csv = new CsvReader(reader, Helper.CsvConfig); + using var csv = new CsvReader(reader, CsvConfigHelper.CsvConfig); var data = csv.GetRecords(); return ToBoreholeGeometry(data, boreholeId); diff --git a/src/api/BoreholeGeometry/Helper.cs b/src/api/BoreholeGeometryExtensions.cs similarity index 77% rename from src/api/BoreholeGeometry/Helper.cs rename to src/api/BoreholeGeometryExtensions.cs index 171188e96..64d2bb170 100644 --- a/src/api/BoreholeGeometry/Helper.cs +++ b/src/api/BoreholeGeometryExtensions.cs @@ -1,42 +1,41 @@ using BDMS.Models; -using CsvHelper; -using CsvHelper.Configuration; -using Humanizer; using NetTopologySuite.Mathematics; using NetTopologySuite.Utilities; -using System.Globalization; -namespace BDMS.BoreholeGeometry; +namespace BDMS; -public static class Helper +public static class BoreholeGeometryExtensions { - internal static readonly CsvConfiguration CsvConfig = new(new CultureInfo("de-CH")) - { - Delimiter = ";", - IgnoreReferences = true, - PrepareHeaderForMatch = args => args.Header.Humanize(LetterCasing.Title), - MissingFieldFound = null, - }; - /// - /// Get the CSV header expects to read a class . - /// Uses the map generated by . - /// If a property has multiple possible column names only the first is considered. + /// Get the TVD of according to the if the geometry exists. /// - /// The class to get the header for. - internal static string GetCSVHeader() + /// The list of representing the borehole's path geometry. + /// The measured depth (MD) at which to calculate the TVD. + /// The TVD at if the geometry exists; otherwise . + internal static double? GetTVDIfGeometryExists(this List geometry, double? depthMD) { - var context = new CsvContext(CsvConfig); - var map = context.AutoMap(); - return string.Join("; ", map.MemberMaps - .Select(m => + if (geometry == null || geometry.Count < 2) + { + if (depthMD != null && depthMD >= 0) { - var name = m.Data.Names.FirstOrDefault(m.Data.Member.Name); - return m.Data.IsOptional ? $"[{name}]" : name; - })); - } + // Return the depthMD unchanged as if the borehole is perfectly vertical and infinitely long. + return depthMD; + } + } + else if (depthMD != null) + { + try + { + return geometry.GetDepthTVD(depthMD.Value); + } + catch (ArgumentOutOfRangeException) + { + // Exception is ignored so that the method returns null in case the input was invalid. + } + } - private static readonly IComparer geometryMDComparer = Comparer.Create((a, b) => a.MD.CompareTo(b.MD)); + return null; + } /// /// Get the TVD of according to the . @@ -130,6 +129,8 @@ private static double InterpolateDepthTVD(List geometry } } + private static readonly IComparer geometryMDComparer = Comparer.Create((a, b) => a.MD.CompareTo(b.MD)); + internal static Vector3D ToVector3D(this BoreholeGeometryElement element) => new Vector3D(element.X, element.Y, element.Z); diff --git a/src/api/Controllers/BoreholeGeometryController.cs b/src/api/Controllers/BoreholeGeometryController.cs index a6b179039..3690e9200 100644 --- a/src/api/Controllers/BoreholeGeometryController.cs +++ b/src/api/Controllers/BoreholeGeometryController.cs @@ -118,28 +118,13 @@ public async Task GetDepthTVD([FromQuery] int boreholeId, [FromQu { var geometry = await GetBoreholeGeometry(boreholeId).ConfigureAwait(false); - if (geometry.Count < 2) + var tvd = geometry.GetTVDIfGeometryExists(depthMD); + if (tvd == null) { - if (depthMD >= 0) - { - // Return the depthMD unchanged as if the borehole is perfectly vertical and infinitely long. - return Ok(depthMD); - } - } - else - { - try - { - return Ok(geometry.GetDepthTVD(depthMD)); - } - catch (ArgumentOutOfRangeException) - { - // Exception is ignored so that the action returns an empty response in case the input was invalid. - } + logger?.LogInformation($"Invalid input, could not calculate true vertical depth from measured depth of {depthMD}"); } - logger?.LogInformation($"Invalid input, could not calculate true vertical depth from measured depth of {depthMD}"); - return Ok(); + return Ok(tvd); } private async Task> GetBoreholeGeometry(int boreholeId) diff --git a/src/api/Controllers/UploadController.cs b/src/api/Controllers/ImportController.cs similarity index 99% rename from src/api/Controllers/UploadController.cs rename to src/api/Controllers/ImportController.cs index 70501d2e0..091e85cf0 100644 --- a/src/api/Controllers/UploadController.cs +++ b/src/api/Controllers/ImportController.cs @@ -14,7 +14,7 @@ namespace BDMS.Controllers; [ApiController] [Route("api/v{version:apiVersion}/[controller]")] -public class UploadController : ControllerBase +public class ImportController : ControllerBase { private const int MaxFileSize = 210_000_000; // 1024 x 1024 x 200 = 209715200 bytes private readonly BdmsContext context; @@ -35,7 +35,7 @@ public class UploadController : ControllerBase private static readonly JsonSerializerOptions jsonImportOptions = new() { PropertyNameCaseInsensitive = true }; - public UploadController(BdmsContext context, ILogger logger, LocationService locationService, CoordinateService coordinateService, BoreholeFileCloudService boreholeFileCloudService) + public ImportController(BdmsContext context, ILogger logger, LocationService locationService, CoordinateService coordinateService, BoreholeFileCloudService boreholeFileCloudService) { this.context = context; this.logger = logger; diff --git a/src/client/cypress/e2e/helpers/testHelpers.js b/src/client/cypress/e2e/helpers/testHelpers.js index d0a8c34dd..8b125d7f1 100644 --- a/src/client/cypress/e2e/helpers/testHelpers.js +++ b/src/client/cypress/e2e/helpers/testHelpers.js @@ -109,6 +109,8 @@ export const interceptApiCalls = () => { cy.intercept("dataextraction/api/V1/extract_data").as("extract-data"); cy.intercept("https://api3.geo.admin.ch/rest/services/height*").as("height"); + + cy.intercept("/api/v2/import*").as("borehole-upload"); }; /** diff --git a/src/client/cypress/e2e/mainPage/import.cy.js b/src/client/cypress/e2e/mainPage/import.cy.js index d4c3f904a..b7a9e4920 100644 --- a/src/client/cypress/e2e/mainPage/import.cy.js +++ b/src/client/cypress/e2e/mainPage/import.cy.js @@ -42,9 +42,6 @@ describe("Test for importing boreholes.", () => { }); }); - // Intercept upload request - cy.intercept("/api/v2/upload?workgroupId=1").as("borehole-upload"); - // Import boreholes and attachments cy.get('[data-cy="import-button"]').click(); cy.wait("@borehole-upload"); @@ -75,9 +72,6 @@ describe("Test for importing boreholes.", () => { }); }); }); - - cy.intercept("/api/v2/upload?workgroupId=1").as("borehole-upload"); - cy.get('[data-cy="import-button"]').click(); cy.wait("@borehole-upload"); diff --git a/src/client/cypress/fixtures/import/boreholes-missing-fields-and-duplicates.csv b/src/client/cypress/fixtures/import/boreholes-missing-fields-and-duplicates.csv index adc714180..2db71587f 100644 --- a/src/client/cypress/fixtures/import/boreholes-missing-fields-and-duplicates.csv +++ b/src/client/cypress/fixtures/import/boreholes-missing-fields-and-duplicates.csv @@ -1,4 +1,4 @@ -alternate_name;original_name;location_x;location_y +name;original_name;location_x;location_y BH-1001;Wellington 1; BH-1002;Wellington 2; BH-1003;Wellington 3;2189456;1334567 diff --git a/src/client/cypress/fixtures/import/boreholes-multiple-valid.csv b/src/client/cypress/fixtures/import/boreholes-multiple-valid.csv index f9b993915..7e73e3f42 100644 --- a/src/client/cypress/fixtures/import/boreholes-multiple-valid.csv +++ b/src/client/cypress/fixtures/import/boreholes-multiple-valid.csv @@ -1,4 +1,4 @@ -alternate_name;original_name;location_x;location_y;attachments; +name;original_name;location_x;location_y;attachments; BH-1001;Wellington 1;2156784;1154321;borehole_attachment_1.pdf,borehole_attachment_2.zip; BH-1002;Wellington 2;2367999;1276543; BH-1003;Wellington 3;2189456;1334567; diff --git a/src/client/cypress/fixtures/import/data-sets/invalid-lithology/borehole-valid.csv b/src/client/cypress/fixtures/import/data-sets/invalid-lithology/borehole-valid.csv index 4267b0f70..cc578434c 100644 --- a/src/client/cypress/fixtures/import/data-sets/invalid-lithology/borehole-valid.csv +++ b/src/client/cypress/fixtures/import/data-sets/invalid-lithology/borehole-valid.csv @@ -1,5 +1,5 @@ -import_id;alternate_name;original_name;location_x;location_y; -344;BH-1001;Wellington 1;2156784;1154321; -33;BH-1002;Wellington 2;2367999;1276543; -2;BH-1003;Wellington 3;2189456;1334567; -55;BH-1004;Wellington 4;2312345;1200987; \ No newline at end of file +name;original_name;location_x;location_y; +BH-1001;Wellington 1;2156784;1154321; +BH-1002;Wellington 2;2367999;1276543; +BH-1003;Wellington 3;2189456;1334567; +BH-1004;Wellington 4;2312345;1200987; \ No newline at end of file diff --git a/src/client/src/api/borehole.ts b/src/client/src/api/borehole.ts index 56109a80e..2f4ff93dc 100644 --- a/src/client/src/api/borehole.ts +++ b/src/client/src/api/borehole.ts @@ -73,7 +73,7 @@ export const updateBorehole = async (borehole: BoreholeV2) => { /* eslint-disable @typescript-eslint/no-explicit-any */ export const importBoreholes = async (workgroupId: string, combinedFormData: any) => { - return await upload(`upload?workgroupId=${workgroupId}`, "POST", combinedFormData); + return await upload(`import?workgroupId=${workgroupId}`, "POST", combinedFormData); }; export const copyBorehole = async (boreholeId: GridRowSelectionModel, workgroupId: string | null) => { diff --git a/tests/api/Controllers/BoreholeControllerTest.cs b/tests/api/Controllers/BoreholeControllerTest.cs index ebe4887f3..68708fe28 100644 --- a/tests/api/Controllers/BoreholeControllerTest.cs +++ b/tests/api/Controllers/BoreholeControllerTest.cs @@ -643,7 +643,7 @@ public async Task CopyInvalidBoreholeId() [TestMethod] public async Task CopyInvalidWorkgroupId() { - boreholeId = GetBoreholeIdToCopy(); + boreholeId = testBoreholeId; var result = await controller.CopyAsync(boreholeId, workgroupId: 0).ConfigureAwait(false); ActionResultAssert.IsUnauthorized(result.Result); } @@ -651,7 +651,7 @@ public async Task CopyInvalidWorkgroupId() [TestMethod] public async Task CopyMissingWorkgroupPermission() { - boreholeId = GetBoreholeIdToCopy(); + boreholeId = testBoreholeId; var result = await controller.CopyAsync(boreholeId, workgroupId: 2).ConfigureAwait(false); ActionResultAssert.IsUnauthorized(result.Result); } @@ -659,7 +659,7 @@ public async Task CopyMissingWorkgroupPermission() [TestMethod] public async Task CopyWithUnknownUser() { - boreholeId = GetBoreholeIdToCopy(); + boreholeId = testBoreholeId; controller.HttpContext.SetClaimsPrincipal("NON-EXISTENT-NAME", PolicyNames.Admin); var result = await controller.CopyAsync(boreholeId, workgroupId: DefaultWorkgroupId).ConfigureAwait(false); ActionResultAssert.IsUnauthorized(result.Result); @@ -668,7 +668,7 @@ public async Task CopyWithUnknownUser() [TestMethod] public async Task CopyWithUserNotSet() { - boreholeId = GetBoreholeIdToCopy(); + boreholeId = testBoreholeId; controller.ControllerContext.HttpContext.User = null; await Assert.ThrowsExceptionAsync(async () => { @@ -679,7 +679,7 @@ await Assert.ThrowsExceptionAsync(async () => [TestMethod] public async Task CopyWithNonAdminUser() { - boreholeId = GetBoreholeIdToCopy(); + boreholeId = testBoreholeId; controller.HttpContext.SetClaimsPrincipal("sub_editor", PolicyNames.Viewer); var result = await controller.CopyAsync(boreholeId, workgroupId: DefaultWorkgroupId).ConfigureAwait(false); ActionResultAssert.IsOk(result.Result); @@ -701,7 +701,7 @@ public async Task DownloadCsvWithValidIdsReturnsFileResultWithMax100Boreholes() var csvData = Encoding.UTF8.GetString(result.FileContents); var fileLength = csvData.Split('\n').Length; var recordCount = fileLength - 2; // Remove header and last line break - Assert.IsTrue(recordCount <= 100); + Assert.AreEqual(100, recordCount); } [TestMethod] diff --git a/tests/api/Controllers/UploadControllerTest.cs b/tests/api/Controllers/ImportControllerTest.cs similarity index 99% rename from tests/api/Controllers/UploadControllerTest.cs rename to tests/api/Controllers/ImportControllerTest.cs index f90e4c57b..9e4c93396 100644 --- a/tests/api/Controllers/UploadControllerTest.cs +++ b/tests/api/Controllers/ImportControllerTest.cs @@ -16,16 +16,16 @@ namespace BDMS.Controllers; [DeploymentItem("TestData")] [TestClass] -public class UploadControllerTest +public class ImportControllerTest { private const int MaxBoreholeSeedId = 1002999; private const int MaxStratigraphySeedId = 6002999; private const int MaxLayerSeedId = 7029999; private BdmsContext context; - private UploadController controller; + private ImportController controller; private Mock httpClientFactoryMock; - private Mock> loggerMock; + private Mock> loggerMock; private Mock> loggerLocationServiceMock; private Mock> loggerCoordinateServiceMock; @@ -36,7 +36,7 @@ public void TestInitialize() context = ContextFactory.CreateContext(); httpClientFactoryMock = new Mock(MockBehavior.Strict); - loggerMock = new Mock>(); + loggerMock = new Mock>(); loggerLocationServiceMock = new Mock>(MockBehavior.Strict); var locationService = new LocationService(loggerLocationServiceMock.Object, httpClientFactoryMock.Object); @@ -56,7 +56,7 @@ public void TestInitialize() contextAccessorMock.Object.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, context.Users.FirstOrDefault().SubjectId) })); var boreholeFileCloudService = new BoreholeFileCloudService(context, configuration, loggerBoreholeFileCloudService.Object, contextAccessorMock.Object, s3ClientMock); - controller = new UploadController(context, loggerMock.Object, locationService, coordinateService, boreholeFileCloudService) { ControllerContext = GetControllerContextAdmin() }; + controller = new ImportController(context, loggerMock.Object, locationService, coordinateService, boreholeFileCloudService) { ControllerContext = GetControllerContextAdmin() }; } [TestCleanup] @@ -1137,13 +1137,13 @@ public async Task UploadWithMaxValidationErrorsExceededShouldReturnError() [TestMethod] public void CompareValueWithTolerance() { - Assert.AreEqual(true, UploadController.CompareValuesWithTolerance(null, null, 0)); - Assert.AreEqual(true, UploadController.CompareValuesWithTolerance(2100000, 2099998, 2)); - Assert.AreEqual(false, UploadController.CompareValuesWithTolerance(2100000, 2000098, 1.99)); - Assert.AreEqual(false, UploadController.CompareValuesWithTolerance(2100002, 2000000, 1.99)); - Assert.AreEqual(false, UploadController.CompareValuesWithTolerance(21000020, 2000000, 20)); - Assert.AreEqual(false, UploadController.CompareValuesWithTolerance(null, 2000000, 0)); - Assert.AreEqual(false, UploadController.CompareValuesWithTolerance(2000000, null, 2)); + Assert.AreEqual(true, ImportController.CompareValuesWithTolerance(null, null, 0)); + Assert.AreEqual(true, ImportController.CompareValuesWithTolerance(2100000, 2099998, 2)); + Assert.AreEqual(false, ImportController.CompareValuesWithTolerance(2100000, 2000098, 1.99)); + Assert.AreEqual(false, ImportController.CompareValuesWithTolerance(2100002, 2000000, 1.99)); + Assert.AreEqual(false, ImportController.CompareValuesWithTolerance(21000020, 2000000, 20)); + Assert.AreEqual(false, ImportController.CompareValuesWithTolerance(null, 2000000, 0)); + Assert.AreEqual(false, ImportController.CompareValuesWithTolerance(2000000, null, 2)); } [TestMethod]