diff --git a/CHANGELOG.md b/CHANGELOG.md
index 700f6625c..bf9c055b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
## [Unreleased]
+### Changed
+- Removed attachments from csv import.
+- Updated recommended csv headers for borehole import to camel case e.g. `OriginalName` (snake case e.g. `original_name` is still supported for all properties except for custom identifiers).
+- Changed order of `Top Bedrock (fresh)` and `Top Bedrock (weathered)` fields in borehole form.
+
+### Fixed
+
+- Observations were not included in exported borehole JSON file.
+
## v2.1.993 - 2024-12-13
### Added
@@ -47,7 +56,6 @@
- When copying a borehole, the nested collections of observations were not copied.
- There was a bug when changing the order, transparency or visibility of custom WMS user layers.
- The borehole status was not translated everywhere in the workflow panel.
-- Observations were not included in exported borehole JSON files.
## v2.1.870 - 2024-09-27
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/BoreholeExtensions.cs b/src/api/BoreholeExtensions.cs
new file mode 100644
index 000000000..9858bb418
--- /dev/null
+++ b/src/api/BoreholeExtensions.cs
@@ -0,0 +1,36 @@
+using BDMS.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace BDMS;
+
+public static class BoreholeExtensions
+{
+ public static IQueryable GetAllWithIncludes(this DbSet boreholes)
+ {
+ return boreholes.Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerColorCodes)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerDebrisCodes)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerGrainAngularityCodes)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerGrainShapeCodes)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerOrganicComponentCodes)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerUscs3Codes)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.LithologicalDescriptions)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.FaciesDescriptions)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.ChronostratigraphyLayers)
+ .Include(b => b.Stratigraphies).ThenInclude(s => s.LithostratigraphyLayers)
+ .Include(b => b.Completions).ThenInclude(c => c.Casings).ThenInclude(c => c.CasingElements)
+ .Include(b => b.Completions).ThenInclude(c => c.Instrumentations)
+ .Include(b => b.Completions).ThenInclude(c => c.Backfills)
+ .Include(b => b.Sections).ThenInclude(s => s.SectionElements)
+ .Include(b => b.Observations).ThenInclude(o => (o as FieldMeasurement)!.FieldMeasurementResults)
+ .Include(b => b.Observations).ThenInclude(o => (o as Hydrotest)!.HydrotestResults)
+ .Include(b => b.Observations).ThenInclude(o => (o as Hydrotest)!.HydrotestEvaluationMethodCodes)
+ .Include(b => b.Observations).ThenInclude(o => (o as Hydrotest)!.HydrotestFlowDirectionCodes)
+ .Include(b => b.Observations).ThenInclude(o => (o as Hydrotest)!.HydrotestKindCodes)
+ .Include(b => b.BoreholeCodelists)
+ .Include(b => b.Workflows)
+ .Include(b => b.BoreholeFiles)
+ .Include(b => b.BoreholeGeometry)
+ .Include(b => b.Workgroup)
+ .Include(b => b.UpdatedBy);
+ }
+}
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 8acbfc819..dc04e4db5 100644
--- a/src/api/Controllers/BoreholeController.cs
+++ b/src/api/Controllers/BoreholeController.cs
@@ -1,17 +1,10 @@
using BDMS.Authentication;
using BDMS.Models;
-using CsvHelper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NetTopologySuite.Geometries;
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using OSGeo.OGR;
-using MaxRev.Gdal.Core;
namespace BDMS.Controllers;
@@ -89,7 +82,7 @@ public async Task> GetAllAsync([FromQuer
pageSize = Math.Min(MaxPageSize, Math.Max(1, pageSize));
var skip = (pageNumber - 1) * pageSize;
- var query = GetBoreholesWithIncludes().AsNoTracking();
+ var query = Context.Boreholes.GetAllWithIncludes().AsNoTracking();
if (ids != null && ids.Any())
{
@@ -111,7 +104,7 @@ public async Task> GetAllAsync([FromQuer
[Authorize(Policy = PolicyNames.Viewer)]
public async Task> GetByIdAsync(int id)
{
- var borehole = await GetBoreholesWithIncludes()
+ var borehole = await Context.Boreholes.GetAllWithIncludes()
.AsNoTracking()
.SingleOrDefaultAsync(l => l.Id == id)
.ConfigureAwait(false);
@@ -124,171 +117,6 @@ public async Task> GetByIdAsync(int id)
return Ok(borehole);
}
- ///
- /// Asynchronously gets all records filtered by ids. Additional data is included in the response.
- ///
- /// The required list of borehole ids to filter by.
- [HttpGet("json")]
- [Authorize(Policy = PolicyNames.Viewer)]
- public async Task ExportJsonAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable ids)
- {
- if (ids == null || !ids.Any())
- {
- return BadRequest("The list of IDs must not be empty.");
- }
-
- var boreholes = await GetBoreholesWithIncludes().AsNoTracking().Where(borehole => ids.Contains(borehole.Id)).ToListAsync().ConfigureAwait(false);
-
- // Create a new JsonSerializerOptions for this specific endpoint
- var options = new JsonSerializerOptions()
- {
- ReferenceHandler = ReferenceHandler.IgnoreCycles,
- WriteIndented = true,
- };
-
- // Add the default converters from the global configuration
- options.Converters.Add(new DateOnlyJsonConverter());
- options.Converters.Add(new LTreeJsonConverter());
-
- // Add special converter for the 'Observations' collection
- options.Converters.Add(new ObservationConverter());
-
- try
- {
- var features = boreholes.Select(borehole =>
- {
-
- var feature = new
- {
- type = "Feature",
- geometry = new
- {
- type = "Point",
- crs = new
- {
- type = "name",
- properties = new
- {
- name = "EPSG:2056",
- },
- },
- coordinates = new[]
- {
- borehole.LocationX,
- borehole.LocationY,
- },
- },
- properties = borehole,
- };
- return feature;
- }).ToList();
-
- var geojson = new
- {
- type = "FeatureCollection",
- crs = new
- {
- type = "name",
- properties = new
- {
- name = "EPSG:2056",
- },
- },
- features,
- };
-
- var geojsonFile = "bulkexport_test.geojson";
- await System.IO.File.WriteAllTextAsync(geojsonFile, JsonSerializer.Serialize(geojson, options)).ConfigureAwait(false);
-
- GdalBase.ConfigureAll();
- Ogr.RegisterAll();
- Ogr.UseExceptions();
-
- var geojsonDataSource = Ogr.Open(geojsonFile, 1);
- if (geojsonDataSource == null)
- {
- throw new InvalidOperationException("Could not open input datasource.");
- }
-
- var gpkgFilePath = geojsonFile.Replace(".geojson", ".gpkg", StringComparison.InvariantCulture);
- if (Directory.Exists(gpkgFilePath))
- {
- Directory.Delete(gpkgFilePath, true);
- }
-
- var openFileGdbDriver = Ogr.GetDriverByName("GPKG");
- var gpkgDataSource = openFileGdbDriver.CreateDataSource(gpkgFilePath, null);
-
- gpkgDataSource.CopyLayer(geojsonDataSource.GetLayerByIndex(0), "boreholes", null);
-
- geojsonDataSource.Dispose();
- gpkgDataSource.Dispose();
- }
- catch (Exception e)
- {
- Logger.LogError(e.Message);
- }
-
- return new JsonResult(boreholes, options);
- }
-
- ///
- /// 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 .
///
@@ -312,7 +140,7 @@ public async Task> CopyAsync([Required] int id, [Required] int
return Unauthorized();
}
- var borehole = await GetBoreholesWithIncludes()
+ var borehole = await Context.Boreholes.GetAllWithIncludes()
.AsNoTracking()
.SingleOrDefaultAsync(b => b.Id == id)
.ConfigureAwait(false);
@@ -424,33 +252,4 @@ public async Task> CopyAsync([Required] int id, [Required] int
if (entity == null) return default;
return await Task.FromResult(entity.Id).ConfigureAwait(false);
}
-
- private IQueryable GetBoreholesWithIncludes()
- {
- return Context.Boreholes.Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerColorCodes)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerDebrisCodes)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerGrainAngularityCodes)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerGrainShapeCodes)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerOrganicComponentCodes)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.Layers).ThenInclude(l => l.LayerUscs3Codes)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.LithologicalDescriptions)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.FaciesDescriptions)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.ChronostratigraphyLayers)
- .Include(b => b.Stratigraphies).ThenInclude(s => s.LithostratigraphyLayers)
- .Include(b => b.Completions).ThenInclude(c => c.Casings).ThenInclude(c => c.CasingElements)
- .Include(b => b.Completions).ThenInclude(c => c.Instrumentations)
- .Include(b => b.Completions).ThenInclude(c => c.Backfills)
- .Include(b => b.Sections).ThenInclude(s => s.SectionElements)
- .Include(b => b.Observations).ThenInclude(o => (o as FieldMeasurement)!.FieldMeasurementResults)
- .Include(b => b.Observations).ThenInclude(o => (o as Hydrotest)!.HydrotestResults)
- .Include(b => b.Observations).ThenInclude(o => (o as Hydrotest)!.HydrotestEvaluationMethodCodes)
- .Include(b => b.Observations).ThenInclude(o => (o as Hydrotest)!.HydrotestFlowDirectionCodes)
- .Include(b => b.Observations).ThenInclude(o => (o as Hydrotest)!.HydrotestKindCodes)
- .Include(b => b.BoreholeCodelists)
- .Include(b => b.Workflows)
- .Include(b => b.BoreholeFiles)
- .Include(b => b.BoreholeGeometry)
- .Include(b => b.Workgroup)
- .Include(b => b.UpdatedBy);
- }
}
diff --git a/src/api/Controllers/ExportController.cs b/src/api/Controllers/ExportController.cs
new file mode 100644
index 000000000..4cd6cb775
--- /dev/null
+++ b/src/api/Controllers/ExportController.cs
@@ -0,0 +1,279 @@
+using BDMS.Authentication;
+using BDMS.Models;
+using CsvHelper;
+using MaxRev.Gdal.Core;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using OSGeo.OGR;
+using System.ComponentModel.DataAnnotations;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+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 static readonly JsonSerializerOptions jsonExportOptions = new()
+ {
+ WriteIndented = true,
+ ReferenceHandler = ReferenceHandler.IgnoreCycles,
+ Converters = { new DateOnlyJsonConverter(), new LTreeJsonConverter(), new ObservationConverter() },
+ };
+
+ public ExportController(BdmsContext context)
+ {
+ this.context = context;
+ }
+
+ ///
+ /// Asynchronously gets all records filtered by ids. Additional data is included in the response.
+ ///
+ /// The required list of borehole ids to filter by.
+ [HttpGet("json")]
+ [Authorize(Policy = PolicyNames.Viewer)]
+ public async Task ExportJsonAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable ids)
+ {
+ if (ids == null || !ids.Any()) return BadRequest("The list of IDs must not be empty.");
+
+ var boreholes = await context.Boreholes.GetAllWithIncludes().AsNoTracking().Where(borehole => ids.Contains(borehole.Id)).ToListAsync().ConfigureAwait(false);
+
+ return new JsonResult(boreholes, jsonExportOptions);
+ }
+
+ ///
+ /// Asynchronously gets all records filtered by ids. Additional data is included in the response.
+ ///
+ /// The required list of borehole ids to filter by.
+ [HttpGet("geojson")]
+ [Authorize(Policy = PolicyNames.Viewer)]
+ public async Task ExportGeoJsonAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable ids)
+ {
+ if (ids == null || !ids.Any())
+ {
+ return BadRequest("The list of IDs must not be empty.");
+ }
+
+ var boreholes = await context.Boreholes.GetAllWithIncludes().AsNoTracking().Where(borehole => ids.Contains(borehole.Id)).ToListAsync().ConfigureAwait(false);
+ var features = boreholes.Select(borehole =>
+ {
+
+ var feature = new
+ {
+ type = "Feature",
+ geometry = new
+ {
+ type = "Point",
+ crs = new
+ {
+ type = "name",
+ properties = new
+ {
+ name = "EPSG:2056",
+ },
+ },
+ coordinates = new[]
+ {
+ borehole.LocationX,
+ borehole.LocationY,
+ },
+ },
+ properties = borehole,
+ };
+ return feature;
+ }).ToList();
+
+ var geojson = new
+ {
+ type = "FeatureCollection",
+ crs = new
+ {
+ type = "name",
+ properties = new
+ {
+ name = "EPSG:2056",
+ },
+ },
+ features,
+ };
+
+ var geojsonFile = $"C:\\Users\\rtschuemperlin\\Desktop\\bulkexport_test_{DateTime.Now:yyyyMMddHHmm}.geojson";
+ await System.IO.File.WriteAllTextAsync(geojsonFile, JsonSerializer.Serialize(geojson, jsonExportOptions)).ConfigureAwait(false);
+
+ GdalBase.ConfigureAll();
+ Ogr.RegisterAll();
+ Ogr.UseExceptions();
+
+ var geojsonDataSource = Ogr.Open(geojsonFile, 1);
+ if (geojsonDataSource == null)
+ {
+ throw new InvalidOperationException("Could not open input datasource.");
+ }
+
+ var gpkgFilePath = geojsonFile.Replace(".geojson", ".gpkg", StringComparison.InvariantCulture);
+ if (Directory.Exists(gpkgFilePath))
+ {
+ Directory.Delete(gpkgFilePath, true);
+ }
+
+ var openFileGdbDriver = Ogr.GetDriverByName("GPKG");
+ var gpkgDataSource = openFileGdbDriver.CreateDataSource(gpkgFilePath, null);
+
+ gpkgDataSource.CopyLayer(geojsonDataSource.GetLayerByIndex(0), "boreholes", null);
+
+ geojsonDataSource.Dispose();
+ gpkgDataSource.Dispose();
+
+ return new JsonResult(geojson, jsonExportOptions);
+ }
+
+ ///
+ /// 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("csv")]
+ [Authorize(Policy = PolicyNames.Viewer)]
+ 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.");
+
+ 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.LocationXLV03));
+ csvWriter.WriteField(nameof(Borehole.LocationYLV03));
+ 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 => GetBoreholeCodelists(b))
+ .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.LocationXLV03);
+ csvWriter.WriteField(b.LocationYLV03);
+ 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 = GetBoreholeCodelists(b).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");
+ }
+
+ private static IEnumerable GetBoreholeCodelists(Borehole borehole)
+ {
+ return borehole.BoreholeCodelists ?? Enumerable.Empty();
+ }
+}
diff --git a/src/api/Controllers/ImportController.cs b/src/api/Controllers/ImportController.cs
index 3df0a8b9a..5537347b0 100644
--- a/src/api/Controllers/ImportController.cs
+++ b/src/api/Controllers/ImportController.cs
@@ -26,13 +26,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()
{
@@ -134,13 +127,12 @@ public async Task> UploadJsonFileAsync(int workgroupId, IFormF
///
/// The of the new (s).
/// The containing the borehole csv records that were uploaded.
- /// The list of containing the borehole attachments referred in .
/// The number of the newly created s.
[HttpPost]
[Authorize(Policy = PolicyNames.Viewer)]
[RequestSizeLimit(int.MaxValue)]
[RequestFormLimits(MultipartBodyLengthLimit = MaxFileSize)]
- public async Task> UploadFileAsync(int workgroupId, IFormFile boreholesFile, IList? attachments = null)
+ public async Task> UploadFileAsync(int workgroupId, IFormFile boreholesFile)
{
// Increase max allowed errors to be able to return more validation errors at once.
ModelState.MaxAllowedErrors = 1000;
@@ -154,14 +146,8 @@ public async Task> UploadFileAsync(int workgroupId, IFormFile
// Checks if the provided boreholes file is a CSV file.
if (!FileTypeChecker.IsCsv(boreholesFile)) return BadRequest("Invalid file type for borehole csv.");
- // Checks if any of the provided attachments has a whitespace in its file name.
- if (attachments?.Any(a => a.FileName.Any(char.IsWhiteSpace)) == true) return BadRequest("One or more file name(s) contain a whitespace.");
-
- // Checks if any of the provided attachments exceeds the maximum file size.
- if (attachments?.Any(a => a.Length > MaxFileSize) == true) return BadRequest($"One or more attachment exceed maximum file size of {MaxFileSize / 1024 / 1024} Mb.");
-
var boreholeImports = ReadBoreholesFromCsv(boreholesFile);
- ValidateBoreholeImports(workgroupId, boreholeImports, false, attachments);
+ ValidateBoreholeImports(workgroupId, boreholeImports, false);
// If any validation error occured, return a bad request.
if (!ModelState.IsValid) return ValidationProblem(statusCode: (int)HttpStatusCode.BadRequest);
@@ -233,21 +219,6 @@ public async Task> UploadFileAsync(int workgroupId, IFormFile
await context.Boreholes.AddRangeAsync(boreholes).ConfigureAwait(false);
var result = await SaveChangesAsync(() => Ok(boreholes.Count)).ConfigureAwait(false);
- // Add attachments to borehole.
- if (attachments != null)
- {
- var boreholeImportsWithAttachments = boreholeImports.Where(x => x.Attachments?.Length > 0).ToList();
- foreach (var boreholeImport in boreholeImportsWithAttachments)
- {
- var attachmentFileNames = boreholeImport.Attachments?.Split(",").Select(s => s.Replace(" ", "", StringComparison.InvariantCulture)).ToList();
- var attachmentFiles = attachments.Where(x => attachmentFileNames != null && attachmentFileNames.Contains(x.FileName.Replace(" ", "", StringComparison.InvariantCulture))).ToList();
- foreach (var attachmentFile in attachmentFiles)
- {
- await boreholeFileCloudService.UploadFileAndLinkToBorehole(attachmentFile, boreholeImport.Id).ConfigureAwait(false);
- }
- }
- }
-
await transaction.CommitAsync().ConfigureAwait(false);
return result;
}
@@ -276,20 +247,19 @@ internal static int GetPrecision(IReaderRow row, string fieldName)
return 0;
}
- private void ValidateBoreholeImports(int workgroupId, List boreholesFromFile, bool isJsonFile, IList? attachments = null)
+ private void ValidateBoreholeImports(int workgroupId, List boreholesFromFile, bool isJsonFile)
{
foreach (var borehole in boreholesFromFile.Select((value, index) => (value, index)))
{
- ValidateBorehole(borehole.value, boreholesFromFile, workgroupId, borehole.index, isJsonFile, attachments);
+ ValidateBorehole(borehole.value, boreholesFromFile, workgroupId, borehole.index, isJsonFile);
}
}
- private void ValidateBorehole(BoreholeImport borehole, List boreholesFromFile, int workgroupId, int boreholeIndex, bool isJsonFile, IList? attachments)
+ private void ValidateBorehole(BoreholeImport borehole, List boreholesFromFile, int workgroupId, int boreholeIndex, bool isJsonFile)
{
ValidateRequiredFields(borehole, boreholeIndex, isJsonFile);
ValidateDuplicateInFile(borehole, boreholesFromFile, boreholeIndex, isJsonFile);
ValidateDuplicateInDb(borehole, workgroupId, boreholeIndex, isJsonFile);
- ValidateAttachments(borehole, attachments, boreholeIndex, isJsonFile);
}
private void ValidateRequiredFields(BoreholeImport borehole, int processingIndex, bool isJsonFile)
@@ -329,26 +299,6 @@ private void ValidateDuplicateInDb(BoreholeImport borehole, int workgroupId, int
}
}
- private void ValidateAttachments(BoreholeImport borehole, IList? attachments, int processingIndex, bool isJsonFile)
- {
- if (attachments == null || string.IsNullOrEmpty(borehole.Attachments)) return;
-
- var boreholeFileNames = borehole.Attachments
- .Split(",")
- .Select(s => s.Trim())
- .Where(s => !string.IsNullOrEmpty(s))
- .ToList();
-
- foreach (var boreholeFileName in boreholeFileNames)
- {
- // Check if the name of any attached file matches the name of the borehole file
- if (!attachments.Any(a => a.FileName.Equals(boreholeFileName, StringComparison.OrdinalIgnoreCase)))
- {
- AddValidationErrorToModelState(processingIndex, $"Attachment file '{boreholeFileName}' not found.", isJsonFile);
- }
- }
- }
-
internal static bool CompareValuesWithTolerance(double? firstValue, double? secondValue, double tolerance)
{
if (firstValue == null && secondValue == null) return true;
@@ -357,10 +307,10 @@ 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, csvConfig);
+ using var csv = new CsvReader(reader, CsvConfigHelper.CsvReadConfig);
csv.Context.RegisterClassMap(new CsvImportBoreholeMap());
@@ -398,17 +348,9 @@ private void AddValidationErrorToModelState(int boreholeIndex, string errorMessa
private sealed class CsvImportBoreholeMap : ClassMap
{
- private readonly CultureInfo swissCulture = new("de-CH");
-
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();
@@ -455,6 +397,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 =>
@@ -462,16 +407,16 @@ public CsvImportBoreholeMap()
var boreholeCodeLists = new List();
new List<(string Name, int CodeListId)>
{
- ("id_geodin_shortname", 100000000),
- ("id_info_geol", 100000003),
- ("id_original", 100000004),
- ("id_canton", 100000005),
- ("id_geo_quat", 100000006),
- ("id_geo_mol", 100000007),
- ("id_geo_therm", 100000008),
- ("id_top_fels", 100000009),
- ("id_geodin", 100000010),
- ("id_kernlager", 100000011),
+ ("IDGeODin-Shortname", 100000000),
+ ("IDInfoGeol", 100000003),
+ ("IDOriginal", 100000004),
+ ("IDCanton", 100000005),
+ ("IDGeoQuat", 100000006),
+ ("IDGeoMol", 100000007),
+ ("IDGeoTherm", 100000008),
+ ("IDTopFels", 100000009),
+ ("IDGeODin", 100000010),
+ ("IDKernlager", 100000011),
}.ForEach(id =>
{
if (args.Row.HeaderRecord != null && args.Row.HeaderRecord.Any(h => h == id.Name))
diff --git a/src/api/Controllers/ObservationConverter.cs b/src/api/Controllers/ObservationConverter.cs
index 817ad1774..0f9a900d4 100644
--- a/src/api/Controllers/ObservationConverter.cs
+++ b/src/api/Controllers/ObservationConverter.cs
@@ -4,6 +4,9 @@
namespace BDMS.Controllers;
+///
+/// Serializes and deserializes objects based on their ObservationType.
+///
public class ObservationConverter : JsonConverter
{
private static readonly JsonSerializerOptions observationDefaultOptions = new JsonSerializerOptions
@@ -12,6 +15,7 @@ public class ObservationConverter : JsonConverter
ReferenceHandler = ReferenceHandler.IgnoreCycles,
};
+ ///
public override Observation? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using JsonDocument doc = JsonDocument.ParseValue(ref reader);
@@ -30,6 +34,7 @@ public class ObservationConverter : JsonConverter
};
}
+ ///
public override void Write(Utf8JsonWriter writer, Observation value, JsonSerializerOptions options)
{
switch (value)
@@ -53,7 +58,7 @@ public override void Write(Utf8JsonWriter writer, Observation value, JsonSeriali
JsonSerializer.Serialize(writer, observation, options);
break;
default:
- throw new NotSupportedException($"Observation type is not supported");
+ throw new NotSupportedException("Observation type is not supported");
}
}
}
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/detailPage/boreholeform.cy.js b/src/client/cypress/e2e/detailPage/boreholeform.cy.js
index 3bb360f74..708d59996 100644
--- a/src/client/cypress/e2e/detailPage/boreholeform.cy.js
+++ b/src/client/cypress/e2e/detailPage/boreholeform.cy.js
@@ -1,13 +1,11 @@
-import { exportCSVItem, exportItem, exportJsonItem, saveWithSaveBar } from "../helpers/buttonHelpers";
+import { saveWithSaveBar } from "../helpers/buttonHelpers";
import { clickOnRowWithText, showTableAndWaitForData, sortBy } from "../helpers/dataGridHelpers";
import { evaluateInput, evaluateSelect, isDisabled, setInput, setSelect } from "../helpers/formHelpers";
import {
createBorehole,
- deleteDownloadedFile,
goToRouteAndAcceptTerms,
handlePrompt,
newEditableBorehole,
- readDownloadedFile,
returnToOverview,
startBoreholeEditing,
} from "../helpers/testHelpers";
@@ -234,27 +232,4 @@ describe("Test for the borehole form.", () => {
ensureEditingEnabled();
});
});
-
- it("Exports a borehole as csv and json", () => {
- const boreholeName = "AAA_HIPPOPOTHAMUS";
- createBorehole({
- "extended.original_name": boreholeName,
- "custom.alternate_name": boreholeName,
- }).as("borehole_id");
-
- deleteDownloadedFile(`${boreholeName}.json`);
- deleteDownloadedFile(`${boreholeName}.csv`);
-
- cy.get("@borehole_id").then(id => {
- goToRouteAndAcceptTerms(`/${id}`);
- ensureEditingDisabled();
- exportItem();
- exportJsonItem();
- exportItem();
- exportCSVItem();
- });
-
- readDownloadedFile(`${boreholeName}.json`);
- readDownloadedFile(`${boreholeName}.csv`);
- });
});
diff --git a/src/client/cypress/e2e/detailPage/coordinates.cy.js b/src/client/cypress/e2e/detailPage/coordinates.cy.js
index dfc949d1a..0eb51e565 100644
--- a/src/client/cypress/e2e/detailPage/coordinates.cy.js
+++ b/src/client/cypress/e2e/detailPage/coordinates.cy.js
@@ -196,4 +196,44 @@ describe("Tests for editing coordinates of a borehole.", () => {
checkDecimalPlaces("@LV03X-input", 5);
checkDecimalPlaces("@LV03Y-input", 5);
});
+
+ it("updates canton and municipality when changing coordinates", () => {
+ // Type coordinates for Samaden in LV95
+ cy.get("@LV95X-input").type("2789000");
+ cy.get("@LV95Y-input").type("1155000");
+ cy.wait("@location");
+ cy.wait(4000);
+
+ cy.get("@country").should("have.value", "Schweiz");
+ cy.get("@canton").should("have.value", "Graubünden");
+ cy.get("@municipality").should("have.value", "Samaden");
+
+ // Type coordinates for Unterentfelden in LV95
+ cy.get("@LV95X-input").clear().type("2646000");
+ cy.get("@LV95Y-input").clear().type("1247000");
+
+ cy.get("@country").should("have.value", "Schweiz");
+ cy.get("@canton").should("have.value", "Aargau");
+ cy.get("@municipality").should("have.value", "Unterentfelden");
+
+ // switch reference system to LV03
+ setSelect("originalReferenceSystem", 1);
+ handlePrompt("Changing the coordinate system will reset the coordinates. Do you want to continue?", "Confirm");
+
+ // Type coordinates for Samaden in LV03
+ cy.get("@LV03X-input").clear().type("789000");
+ cy.get("@LV03Y-input").clear().type("155000");
+
+ cy.get("@country").should("have.value", "Schweiz");
+ cy.get("@canton").should("have.value", "Graubünden");
+ cy.get("@municipality").should("have.value", "Samaden");
+
+ // Type coordinates for Unterentfelden in LV03
+ cy.get("@LV03X-input").clear().type("646000");
+ cy.get("@LV03Y-input").clear().type("247000");
+
+ cy.get("@country").should("have.value", "Schweiz");
+ cy.get("@canton").should("have.value", "Aargau");
+ cy.get("@municipality").should("have.value", "Unterentfelden");
+ });
});
diff --git a/src/client/cypress/e2e/helpers/testHelpers.js b/src/client/cypress/e2e/helpers/testHelpers.js
index 8b125d7f1..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");
@@ -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 c40c94344..8721fe943 100644
--- a/src/client/cypress/e2e/mainPage/export.cy.js
+++ b/src/client/cypress/e2e/mainPage/export.cy.js
@@ -1,31 +1,74 @@
-import { exportCSVItem, exportItem, exportJsonItem } from "../helpers/buttonHelpers";
-import { checkAllVisibleRows, checkRowWithText, showTableAndWaitForData } from "../helpers/dataGridHelpers.js";
+import {
+ addItem,
+ deleteItem,
+ exportCSVItem,
+ exportItem,
+ exportJsonItem,
+ saveWithSaveBar,
+} from "../helpers/buttonHelpers";
+import {
+ checkAllVisibleRows,
+ checkRowWithText,
+ clickOnRowWithText,
+ 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][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);
+ });
+};
+
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);
@@ -35,10 +78,132 @@ describe("Test for exporting boreholes.", () => {
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();
+ exportItem();
+ 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();
+ exportItem();
+ 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);
+ exportItem();
+ 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][33]).to.equal("IDInfoGeol");
+ expect(rows[1][33]).to.equal("");
+ expect(rows[2][33]).to.equal("14");
+
+ 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");
});
it("downloads a maximum of 100 boreholes", () => {
- loginAsAdmin();
+ deleteDownloadedFile(csvFileName);
+ deleteDownloadedFile(jsonFileName);
showTableAndWaitForData();
checkAllVisibleRows();
deleteDownloadedFile(csvFileName);
@@ -49,6 +214,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);
@@ -61,4 +227,96 @@ describe("Test for exporting boreholes.", () => {
deleteDownloadedFile(jsonFileName);
});
+
+ it("exports a single borehole as csv and json", () => {
+ const boreholeName = "AAA_HIPPOPOTHAMUS";
+ createBorehole({
+ "extended.original_name": boreholeName,
+ "custom.alternate_name": boreholeName,
+ }).as("borehole_id");
+
+ deleteDownloadedFile(`${boreholeName}.json`);
+ deleteDownloadedFile(`${boreholeName}.csv`);
+
+ cy.get("@borehole_id").then(id => {
+ goToRouteAndAcceptTerms(`/${id}`);
+ getElementByDataCy("edit-button").should("exist");
+ getElementByDataCy("editingstop-button").should("not.exist");
+ exportItem();
+ exportJsonItem();
+ exportItem();
+ exportCSVItem();
+ });
+
+ readDownloadedFile(`${boreholeName}.json`);
+ readDownloadedFile(`${boreholeName}.csv`);
+ });
+
+ it("exports and reimports a borehole using csv", () => {
+ const boreholeName = "AAA_WALRUS";
+ createBorehole({
+ "extended.original_name": boreholeName,
+ "custom.alternate_name": boreholeName,
+ }).as("borehole_id");
+
+ cy.get("@borehole_id").then(id => {
+ goToRouteAndAcceptTerms(`/${id}`);
+ });
+ startBoreholeEditing();
+
+ // set two custom identifiers
+ addItem("addIdentifier");
+ setSelect("boreholeCodelists.0.codelistId", 1);
+ setInput("boreholeCodelists.0.value", "w1");
+
+ addItem("addIdentifier");
+ setSelect("boreholeCodelists.1.codelistId", 2);
+ setInput("boreholeCodelists.1.value", "w2");
+
+ // add coordinates
+ cy.get('[data-cy="locationX-formCoordinate"] input').type("2646000 ");
+ cy.get('[data-cy="locationY-formCoordinate"] input').type("1247000 ");
+ cy.wait("@location");
+ cy.wait(4000);
+ saveWithSaveBar();
+
+ exportItem();
+ exportCSVItem();
+
+ const downloadedFilePath = prepareDownloadPath(`${boreholeName}.csv`);
+ cy.readFile(downloadedFilePath).should("exist");
+
+ returnToOverview();
+ showTableAndWaitForData();
+ checkRowWithText(boreholeName);
+ deleteItem();
+ handlePrompt("Do you really want to delete this borehole? This cannot be undone.", "Delete");
+ getElementByDataCy("import-borehole-button").click();
+ cy.contains(boreholeName).should("not.exist");
+
+ cy.readFile(downloadedFilePath, "utf-8").then(fileContent => {
+ // Create a DataTransfer and a File from the downloaded content
+ const boreholeFile = new DataTransfer();
+ const file = new File([fileContent], `${boreholeName}.csv`, {
+ type: "text/csv",
+ });
+ boreholeFile.items.add(file);
+
+ cy.get('[data-cy="import-boreholeFile-input"]').within(() => {
+ cy.get("input[type=file]", { force: true }).then(input => {
+ input[0].files = boreholeFile.files; // Attach the file
+ input[0].dispatchEvent(new Event("change", { bubbles: true }));
+ });
+ });
+ cy.get('[data-cy="import-button"]').click();
+ cy.wait("@borehole-upload");
+ });
+
+ clickOnRowWithText(boreholeName);
+ evaluateInput("name", boreholeName);
+ evaluateInput("boreholeCodelists.1.value", "w1");
+ evaluateInput("boreholeCodelists.0.value", "w2");
+ cy.get('[data-cy="locationX-formCoordinate"] input').should("have.value", `2'646'000`);
+ cy.get('[data-cy="locationY-formCoordinate"] input').should("have.value", `1'247'000`);
+ });
});
diff --git a/src/client/cypress/e2e/mainPage/import.cy.js b/src/client/cypress/e2e/mainPage/import.cy.js
index b7a9e4920..2551abf5b 100644
--- a/src/client/cypress/e2e/mainPage/import.cy.js
+++ b/src/client/cypress/e2e/mainPage/import.cy.js
@@ -21,28 +21,7 @@ describe("Test for importing boreholes.", () => {
});
});
- // Select borehole attachments
- let attachmentFileList = new DataTransfer();
- getImportFileFromFixtures("borehole_attachment_1.pdf", "utf-8").then(fileContent => {
- const file = new File([fileContent], "borehole_attachment_1.pdf", {
- type: "application/pdf",
- });
- attachmentFileList.items.add(file);
- });
- getImportFileFromFixtures("borehole_attachment_2.zip", "utf-8").then(fileContent => {
- const file = new File([fileContent], "borehole_attachment_2.zip", {
- type: "application/zip",
- });
- attachmentFileList.items.add(file);
- });
- cy.get('[data-cy="import-boreholeFile-attachments-input"]').within(() => {
- cy.get("input[type=file]", { force: true }).then(input => {
- input[0].files = attachmentFileList.files;
- input[0].dispatchEvent(new Event("change", { bubbles: true }));
- });
- });
-
- // Import boreholes and attachments
+ // Import boreholes
cy.get('[data-cy="import-button"]').click();
cy.wait("@borehole-upload");
diff --git a/src/client/cypress/fixtures/import/boreholes-multiple-valid.csv b/src/client/cypress/fixtures/import/boreholes-multiple-valid.csv
index 7e73e3f42..2f955f650 100644
--- a/src/client/cypress/fixtures/import/boreholes-multiple-valid.csv
+++ b/src/client/cypress/fixtures/import/boreholes-multiple-valid.csv
@@ -1,5 +1,5 @@
-name;original_name;location_x;location_y;attachments;
-BH-1001;Wellington 1;2156784;1154321;borehole_attachment_1.pdf,borehole_attachment_2.zip;
+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;
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
deleted file mode 100644
index cc578434c..000000000
--- a/src/client/cypress/fixtures/import/data-sets/invalid-lithology/borehole-valid.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-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/docs/import.md b/src/client/docs/import.md
index 5db6da5bc..6e9e92140 100644
--- a/src/client/docs/import.md
+++ b/src/client/docs/import.md
@@ -18,11 +18,7 @@ Zunächst sollte die CSV-Datei den Anforderungen und dem Format entsprechen, wie
1. Schaltfläche _Datei auswählen_ anklicken und die vorbereitete CSV-Datei auswählen.
2. Unter _Arbeitsgruppe_ die Arbeitsgruppe auswählen, in welche die Bohrdaten importiert werden sollen (neue Arbeitsgruppen können nur als "Admin-User" erstellt werden).
-### Schritt 4: Bohrlochanhänge selektieren (optional)
-
-1. Schaltfläche _Dateien hier ablegen oder klicken, um sie hochzuladen_ anklicken und die vorbereitete Datei(en) auswählen.
-
-### Schritt 5: Dateien hochladen
+### Schritt 4: Dateien hochladen
1. Import-Prozess mit einem Klick auf _Importieren_ starten.
2. Warten, bis der Upload abgeschlossen ist und die Daten in der Anwendung verfügbar sind.
@@ -37,57 +33,54 @@ Die CSV-Datei muss den folgenden Anforderungen und dem Format entsprechen, damit
- Die Spaltenüberschriften müssen den vorgegebenen Feldnamen aus dem Import-Dialog entsprechen.
- Die Werte in den Spalten müssen den erwarteten Datentypen entsprechen (z.B. numerisch für Tiefe, Text für Namen, etc.).
-## Format und Anforderungen an die Dateien der Bohrlochanhänge
-
-Die Anhangsdatei muss den folgenden Anforderungen entsprechen, damit sie erfolgreich in die Webapplikation importiert werden kann:
-
-- Die Datei darf maximal 200 MB gross sein.
-- Der Dateiname darf keine Leerzeichen enthalten.
## Bohrloch Datei Format
Die zu importierenden Daten müssen gemäss obigen Anforderungen im CSV-Format vorliegen. Die erste Zeile wird als Spaltentitel/Spaltenname interpretiert, die restlichen Zeilen als Daten.
-| Feldname | Datentyp | Pflichtfeld | Beschreibung |
-| --------------------------- | -------------- | ----------- | ------------------------------------------------------------------------------------- |
-| id_geodin_shortname | Zahl | Nein | ID GeODin-Shortname |
-| id_info_geol | Zahl | Nein | ID InfoGeol |
-| id_original | Zahl | Nein | ID Original |
-| id_canton | Zahl | Nein | ID Kanton |
-| id_geo_quat | Zahl | Nein | ID GeoQuat |
-| id_geo_mol | Zahl | Nein | ID GeoMol |
-| id_geo_therm | Zahl | Nein | ID GeoTherm |
-| id_top_fels | Zahl | Nein | ID TopFels |
-| id_geodin | Zahl | Nein | ID GeODin |
-| id_kernlager | Zahl | Nein | ID Kernlager |
-| original_name | Text | Ja | Originalname |
-| project_name | Text | Nein | Projektname |
-| name | Text | Nein | Name |
-| restriction_id | ID (Codeliste) | Nein | Beschränkung |
-| restriction_until | Datum | Nein | Ablaufdatum der Beschränkung |
-| national_interest | True/False | Nein | Nationales Interesse |
-| location_x | Dezimalzahl | Ja | Koordinate Ost LV95 |
-| location_y | Dezimalzahl | Ja | Koordinate Nord LV95 |
-| location_precision_id | ID (Codeliste) | Nein | +/- Koordinaten [m] |
-| elevation_z | Dezimalzahl | Nein | Terrainhöhe [m ü.M.] |
-| elevation_precision_id | ID (Codeliste) | Nein | +/- Terrainhöhe [m] |
-| reference_elevation | Dezimalzahl | Nein | Referenz Ansatzhöhe [m ü.M.] |
-| reference_elevation_type_id | ID (Codeliste) | Nein | Typ der Referenz Ansatzhöhe |
-| qt_reference_elevation_id | ID (Codeliste) | Nein | +/- Referenz Ansatzhöhe [m] |
-| hrs_id | ID (Codeliste) | Nein | Höhenreferenzsystem |
-| type_id | ID (Codeliste) | Nein | Bohrtyp |
-| purpose_id | ID (Codeliste) | Nein | Bohrzweck |
-| status_id | ID (Codeliste) | Nein | Bohrungsstatus |
-| remarks | Text | Nein | Bemerkungen |
-| total_depth | Dezimalzahl | Nein | Bohrlochlänge [m MD] |
-| qt_depth_id | ID (Codeliste) | Nein | +/- Bohrlochlänge [m MD] |
-| top_bedrock_fresh_md | Dezimalzahl | Nein | Top Fels (frisch) [m MD] |
-| top_bedrock_weathered_md | Dezimalzahl | Nein | Top Fels (verwittert) [m MD] |
-| has_groundwater | True/False | Nein | Grundwasser |
-| lithology_top_bedrock_id | ID (Codeliste) | Nein | Lithologie Top Fels |
-| chronostratigraphy_id | ID (Codeliste) | Nein | Chronostratigraphie Top Fels |
-| lithostratigraphy_id | ID (Codeliste) | Nein | Lithostratigraphie Top Fels |
-| attachments | Text | Nein | Kommaseparierte Dateinamen der Anhänge mit Dateiendung z.B. anhang_1.pdf,anhang_2.zip |
+| Feldname | Datentyp | Pflichtfeld | Beschreibung |
+| --------------------------- | -------------- | ----------- | ------------------------------------------------------------------------------------- |
+| IDGeODin-Shortname | Zahl | Nein | ID GeODin-Shortname |
+| IDInfoGeol | Zahl | Nein | ID InfoGeol |
+| IDOriginal | Zahl | Nein | ID Original |
+| IDCanton | Zahl | Nein | ID Kanton |
+| IDGeoQuat | Zahl | Nein | ID GeoQuat |
+| IDGeoMol | Zahl | Nein | ID GeoMol |
+| IDGeoTherm | Zahl | Nein | ID GeoTherm |
+| IDTopFels | Zahl | Nein | ID TopFels |
+| IDGeODin | Zahl | Nein | ID GeODin |
+| IDKernlager | Zahl | Nein | ID Kernlager |
+| OriginalName | Text | Ja | Originalname |
+| ProjectName | Text | Nein | Projektname |
+| Name | Text | Nein | Name |
+| RestrictionId | ID (Codeliste) | Nein | Beschränkung |
+| RestrictionUntil | Datum | Nein | Ablaufdatum der Beschränkung |
+| NationalInterest | True/False | Nein | Nationales Interesse |
+| LocationX | Dezimalzahl | Ja | Koordinate Ost in LV95 oder LV03 |
+| LocationY | Dezimalzahl | Ja | Koordinate Nord in LV95 oder LV03 |
+| LocationPrecisionId | ID (Codeliste) | Nein | +/- Koordinaten [m] |
+| ElevationZ | Dezimalzahl | Nein | Terrainhöhe [m ü.M.] |
+| ElevationPrecisionId | ID (Codeliste) | Nein | +/- Terrainhöhe [m] |
+| ReferenceElevation | Dezimalzahl | Nein | Referenz Ansatzhöhe [m ü.M.] |
+| ReferenceElevationTypeId | ID (Codeliste) | Nein | Typ der Referenz Ansatzhöhe |
+| ReferenceElevationPrecisionId | ID (Codeliste) | Nein | +/- Referenz Ansatzhöhe [m] |
+| HrsId | ID (Codeliste) | Nein | Höhenreferenzsystem |
+| TypeId | ID (Codeliste) | Nein | Bohrtyp |
+| PurposeId | ID (Codeliste) | Nein | Bohrzweck |
+| StatusId | ID (Codeliste) | Nein | Bohrungsstatus |
+| Remarks | Text | Nein | Bemerkungen |
+| TotalDepth | Dezimalzahl | Nein | Bohrlochlänge [m MD] |
+| DepthPrecisionId | ID (Codeliste) | Nein | +/- Bohrlochlänge [m MD] |
+| TopBedrockFreshMd | Dezimalzahl | Nein | Top Fels (frisch) [m MD] |
+| TopBedrockWeatheredMd | Dezimalzahl | Nein | Top Fels (verwittert) [m MD] |
+| HasGroundwater | True/False | Nein | Grundwasser |
+| LithologyTopBedrockId | ID (Codeliste) | Nein | Lithologie Top Fels |
+| ChronostratigraphyTopBedrockId| ID (Codeliste) | Nein | Chronostratigraphie Top Fels |
+| LithostratigraphyTopBedrockId | ID (Codeliste) | Nein | Lithostratigraphie Top Fels |
+
+### Koordinaten
+
+Koordinaten können in LV95 oder LV03 importiert werden, das räumliche Bezugssystem wird aus den Koordinaten erkannt und abgespeichert.
## Validierung
@@ -100,9 +93,6 @@ Für jeden bereitgestellten Header CSV-Datei muss für jede Zeile ein entspreche
Beim Importprozess der Bohrdaten wird eine Duplikatsvalidierung durchgeführt, um sicherzustellen, dass kein Bohrloch mehrmals in der Datei vorhanden ist oder bereits in der Datenbank existiert.
Duplikate werden nur innerhalb einer Arbeitsgruppe erkannt. Die Duplikaterkennung erfolgt anhand der Koordinaten mit einer Toleranz von +/- 2 Metern und der Gesamttiefe des Bohrlochs.
-### Bohrlochanhänge
-
-Überprüft wird, ob jeder Dateiname in der kommaseparierten Liste in dem _attachments_-Feld in der Liste der Anhänge vorhanden ist.
## Generelles
diff --git a/src/client/src/api/borehole.ts b/src/client/src/api/borehole.ts
index af19fa413..85e918955 100644
--- a/src/client/src/api/borehole.ts
+++ b/src/client/src/api/borehole.ts
@@ -69,7 +69,7 @@ export const getBoreholeById = async (id: number) => await fetchApiV2(`borehole/
export const exportJsonBoreholes = async (ids: number[] | GridRowSelectionModel) => {
const idsQuery = ids.map(id => `ids=${id}`).join("&");
- return await fetchApiV2(`borehole/json?${idsQuery}`, "GET");
+ return await fetchApiV2(`export/geojson?${idsQuery}`, "GET");
};
export const updateBorehole = async (borehole: BoreholeV2) => {
@@ -92,5 +92,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/src/client/src/pages/detail/form/borehole/boreholeForm.tsx b/src/client/src/pages/detail/form/borehole/boreholeForm.tsx
index 1455abf55..de0add108 100644
--- a/src/client/src/pages/detail/form/borehole/boreholeForm.tsx
+++ b/src/client/src/pages/detail/form/borehole/boreholeForm.tsx
@@ -140,29 +140,29 @@ export const BoreholeForm = forwardRef(({ borehole, editingEnabled, onSubmit }:
diff --git a/src/client/src/pages/detail/form/location/locationSegment.tsx b/src/client/src/pages/detail/form/location/locationSegment.tsx
index f9a83cea5..d3895c239 100644
--- a/src/client/src/pages/detail/form/location/locationSegment.tsx
+++ b/src/client/src/pages/detail/form/location/locationSegment.tsx
@@ -1,4 +1,4 @@
-import { useCallback } from "react";
+import { useCallback, useState } from "react";
import { UseFormReturn } from "react-hook-form";
import { Card, Grid, Stack } from "@mui/material";
import { fetchApiV2 } from "../../../../api/fetchApiV2";
@@ -17,6 +17,8 @@ interface LocationSegmentProps extends LocationBaseProps {
}
const LocationSegment = ({ borehole, editingEnabled, labelingPanelOpen, formMethods }: LocationSegmentProps) => {
+ const [currentLV95X, setCurrentLV95X] = useState(borehole.locationX ? Number(borehole.locationX) : null);
+ const [currentLV95Y, setCurrentLV95Y] = useState(borehole.locationY ? Number(borehole.locationY) : null);
const transformCoordinates = useCallback(async (referenceSystem: string, x: number, y: number) => {
let apiUrl;
if (referenceSystem === referenceSystems.LV95.name) {
@@ -70,12 +72,18 @@ const LocationSegment = ({ borehole, editingEnabled, labelingPanelOpen, formMeth
if (!response) return; // Ensure response is valid
const maxPrecision = Math.max(XPrecision, YPrecision);
- const transformedX = parseFloat(response.easting).toFixed(maxPrecision);
- const transformedY = parseFloat(response.northing).toFixed(maxPrecision);
+ const transformedX = parseFloat(response.easting);
+ const transformedY = parseFloat(response.northing);
- const location = await fetchApiV2(`location/identify?east=${X}&north=${Y}`, "GET");
+ const XLV95 = sourceSystem === ReferenceSystemKey.LV95 ? X : transformedX;
+ const YLV95 = sourceSystem === ReferenceSystemKey.LV95 ? Y : transformedY;
+
+ setCurrentLV95X(XLV95);
+ setCurrentLV95Y(YLV95);
+
+ const location = await fetchApiV2(`location/identify?east=${XLV95}&north=${YLV95}`, "GET");
setValuesForCountryCantonMunicipality(location);
- setValuesForReferenceSystem(targetSystem, transformedX, transformedY);
+ setValuesForReferenceSystem(targetSystem, transformedX.toFixed(maxPrecision), transformedY.toFixed(maxPrecision));
},
[setValuesForCountryCantonMunicipality, setValuesForReferenceSystem, transformCoordinates],
);
@@ -130,8 +138,8 @@ const LocationSegment = ({ borehole, editingEnabled, labelingPanelOpen, formMeth
}}
id={borehole.id}
isEditable={editingEnabled}
- x={borehole.locationX ? Number(borehole.locationX) : null}
- y={borehole.locationY ? Number(borehole.locationY) : null}
+ x={currentLV95X}
+ y={currentLV95Y}
/>
diff --git a/src/client/src/pages/overview/layout/mainSideNav.tsx b/src/client/src/pages/overview/layout/mainSideNav.tsx
index c76d43f85..b616998c2 100644
--- a/src/client/src/pages/overview/layout/mainSideNav.tsx
+++ b/src/client/src/pages/overview/layout/mainSideNav.tsx
@@ -45,7 +45,6 @@ const MainSideNav = ({
const [upload, setUpload] = useState(false);
const [validationErrorModal, setValidationErrorModal] = useState(false);
const [selectedFile, setSelectedFile] = useState(null);
- const [selectedBoreholeAttachments, setSelectedBoreholeAttachments] = useState(null);
const [errorsResponse, setErrorsResponse] = useState(null);
const filterContext = useContext(FilterContext);
@@ -177,10 +176,8 @@ const MainSideNav = ({
setSelectedFile={setSelectedFile}
setWorkgroup={setWorkgroupId}
enabledWorkgroups={enabledWorkgroups}
- setSelectedBoreholeAttachments={setSelectedBoreholeAttachments}
workgroup={workgroupId}
selectedFile={selectedFile}
- selectedBoreholeAttachments={selectedBoreholeAttachments}
modal={modal}
upload={upload}
/>
diff --git a/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts b/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts
index 128731516..98d55d4c8 100644
--- a/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts
+++ b/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts
@@ -21,15 +21,12 @@ export interface NewBoreholeProps extends WorkgroupSelectProps {
}
export interface ImportContentProps {
- setSelectedBoreholeAttachments: React.Dispatch>;
- selectedFile: Blob[] | null;
setSelectedFile: React.Dispatch>;
}
export interface ImportModalProps extends ImportContentProps {
modal: boolean;
creating: boolean;
- selectedBoreholeAttachments: Blob[] | null;
selectedFile: Blob[] | null;
upload: boolean;
workgroup: string;
diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx
index db2f49592..47e45a57a 100644
--- a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx
+++ b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx
@@ -16,8 +16,6 @@ const ImportModal = ({
setErrorsResponse,
setValidationErrorModal,
selectedFile,
- selectedBoreholeAttachments,
- setSelectedBoreholeAttachments,
setSelectedFile,
modal,
creating,
@@ -35,12 +33,6 @@ const ImportModal = ({
selectedFile.forEach((boreholeFile: string | Blob) => {
combinedFormData.append("boreholesFile", boreholeFile);
});
-
- if (selectedBoreholeAttachments !== null) {
- selectedBoreholeAttachments.forEach((attachment: string | Blob) => {
- combinedFormData.append("attachments", attachment);
- });
- }
}
importBoreholes(workgroup, combinedFormData).then(response => {
setCreating(false);
@@ -97,11 +89,7 @@ const ImportModal = ({
-
+
{capitalizeFirstLetter(t("workgroup"))}
diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx
index fd71fcb37..98430ad7e 100644
--- a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx
+++ b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx
@@ -8,18 +8,6 @@ import Downloadlink from "../../../detail/attachments/downloadlink.jsx";
import { FileDropzone } from "../../../detail/attachments/fileDropzone.jsx";
import { ImportContentProps } from "../commons/actionsInterfaces.js";
-const SeparatorLine = () => {
- return (
-
- );
-};
-
const ExampleHeadings = (headings: string) => {
return (
{
);
};
-const ImportModalContent = ({ setSelectedBoreholeAttachments, setSelectedFile, selectedFile }: ImportContentProps) => {
+const ImportModalContent = ({ setSelectedFile }: ImportContentProps) => {
const { t } = useTranslation();
- const handleBoreholeAttachmentChange = useCallback(
- (attachmentsFromDropzone: Blob[]) => {
- setSelectedBoreholeAttachments(attachmentsFromDropzone);
- },
- [setSelectedBoreholeAttachments],
- );
-
const handleBoreholeFileChange = useCallback(
(boreholeFileFromDropzone: Blob[]) => {
setSelectedFile(boreholeFileFromDropzone);
@@ -60,23 +41,22 @@ const ImportModalContent = ({ setSelectedBoreholeAttachments, setSelectedFile, s
- {SeparatorLine()}
{capitalizeFirstLetter(t("boreholes"))}
{t("csvFormatExplanation")}
{ExampleHeadings(
- "id_geodin_shortname;id_info_geol;id_original;" +
- "id_canton;id_geo_quat;id_geo_mol;id_geo_therm;id_top_fels;" +
- "id_geodin;id_kernlager;original_name;project_name;name;" +
- "restriction_id;restriction_until;national_interest;location_x;location_y;" +
- "location_precision;elevation_z;elevation_precision_id;" +
- "reference_elevation;reference_elevation_type_id;" +
- "qt_reference_elevation_id;hrs_id;type_id;purpose_id;" +
- "status_id;remarks;total_depth;qt_depth_id;top_bedrock_fresh_md;" +
- "top_bedrock_weathered_md;" +
- "has_groundwater;lithology_top_bedrock_id;" +
- "chronostratigraphy_id;lithostratigraphy_id;attachments;",
+ "IdOriginal;" +
+ "IdCanton;IdGeoQuat;IdGeoMol;IdGeoTherm;IdTopFels;" +
+ "IdGeodin;IdKernlager;OriginalName;ProjectName;Name;" +
+ "RestrictionId;RestrictionUntil;NationalInterest;LocationX;LocationY;" +
+ "LocationPrecision;ElevationZ;ElevationPrecisionId;" +
+ "ReferenceElevation;ReferenceElevationTypeId;" +
+ "QtReferenceElevationId;HrsId;TypeId;PurposeId;" +
+ "StatusId;Remarks;TotalDepth;QtDepthId;TopBedrockFreshMd;" +
+ "TopBedrockWeatheredMd;" +
+ "HasGroundwater;LithologyTopBedrockId;" +
+ "ChronostratigraphyId;LithostratigraphyId;",
)}
- {capitalizeFirstLetter(t("attachments"))}
-
- {t("importBoreholeAttachment")}
-
-
- {SeparatorLine()}
>
);
};
diff --git a/tests/api/Controllers/BoreholeControllerTest.cs b/tests/api/Controllers/BoreholeControllerTest.cs
index b15bb7c5e..22c17b6a5 100644
--- a/tests/api/Controllers/BoreholeControllerTest.cs
+++ b/tests/api/Controllers/BoreholeControllerTest.cs
@@ -390,91 +390,6 @@ public async Task CopyBoreholeWithHydrotests()
Assert.AreEqual(waterIngress.ConditionsId, copiedWaterIngress.ConditionsId);
}
- [TestMethod]
- public async Task ExportJson()
- {
- var newBorehole = GetBoreholeToAdd();
-
- var fieldMeasurementResult = new FieldMeasurementResult
- {
- ParameterId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.FieldMeasurementParameterSchema).FirstAsync().ConfigureAwait(false)).Id,
- SampleTypeId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.FieldMeasurementSampleTypeSchema).FirstAsync().ConfigureAwait(false)).Id,
- Value = 10.0,
- };
-
- var fieldMeasurement = new FieldMeasurement
- {
- Borehole = newBorehole,
- StartTime = new DateTime(2021, 01, 01, 01, 01, 01, DateTimeKind.Utc),
- EndTime = new DateTime(2021, 01, 01, 13, 01, 01, DateTimeKind.Utc),
- Type = ObservationType.FieldMeasurement,
- Comment = "Field measurement observation for testing",
- FieldMeasurementResults = new List { fieldMeasurementResult },
- };
-
- var groundwaterLevelMeasurement = new GroundwaterLevelMeasurement
- {
- Borehole = newBorehole,
- StartTime = new DateTime(2021, 01, 01, 01, 01, 01, DateTimeKind.Utc),
- EndTime = new DateTime(2021, 01, 01, 13, 01, 01, DateTimeKind.Utc),
- Type = ObservationType.GroundwaterLevelMeasurement,
- Comment = "Groundwater level measurement observation for testing",
- LevelM = 10.0,
- LevelMasl = 11.0,
- KindId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.GroundwaterLevelMeasurementKindSchema).FirstAsync().ConfigureAwait(false)).Id,
- };
-
- var waterIngress = new WaterIngress
- {
- Borehole = newBorehole,
- IsOpenBorehole = true,
- Type = ObservationType.WaterIngress,
- Comment = "Water ingress observation for testing",
- QuantityId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.WateringressQualitySchema).FirstAsync().ConfigureAwait(false)).Id,
- ConditionsId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.WateringressConditionsSchema).FirstAsync().ConfigureAwait(false)).Id,
- };
-
- var hydroTestResult = new HydrotestResult
- {
- ParameterId = 15203191,
- Value = 10.0,
- MaxValue = 15.0,
- MinValue = 5.0,
- };
-
- var kindCodelistIds = await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.HydrotestKindSchema).Take(2).Select(c => c.Id).ToListAsync().ConfigureAwait(false);
- var flowDirectionCodelistIds = await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.FlowdirectionSchema).Take(2).Select(c => c.Id).ToListAsync().ConfigureAwait(false);
- var evaluationMethodCodelistIds = await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.EvaluationMethodSchema).Take(2).Select(c => c.Id).ToListAsync().ConfigureAwait(false);
-
- var kindCodelists = await GetCodelists(context, kindCodelistIds).ConfigureAwait(false);
- var flowDirectionCodelists = await GetCodelists(context, flowDirectionCodelistIds).ConfigureAwait(false);
- var evaluationMethodCodelists = await GetCodelists(context, evaluationMethodCodelistIds).ConfigureAwait(false);
-
- var hydroTest = new Hydrotest
- {
- Borehole = newBorehole,
- StartTime = new DateTime(2021, 01, 01, 01, 01, 01, DateTimeKind.Utc),
- EndTime = new DateTime(2021, 01, 01, 13, 01, 01, DateTimeKind.Utc),
- Type = ObservationType.Hydrotest,
- Comment = "Hydrotest observation for testing",
- HydrotestResults = new List() { hydroTestResult },
- HydrotestFlowDirectionCodes = new List { new() { CodelistId = flowDirectionCodelists[0].Id }, new() { CodelistId = flowDirectionCodelists[1].Id } },
- HydrotestKindCodes = new List { new() { CodelistId = kindCodelists[0].Id }, new() { CodelistId = kindCodelists[1].Id } },
- HydrotestEvaluationMethodCodes = new List { new() { CodelistId = evaluationMethodCodelists[0].Id }, new() { CodelistId = evaluationMethodCodelists[1].Id } },
- };
-
- newBorehole.Observations = new List { hydroTest, fieldMeasurement, groundwaterLevelMeasurement, waterIngress };
-
- context.Add(newBorehole);
- await context.SaveChangesAsync().ConfigureAwait(false);
-
- var response = await controller.ExportJsonAsync(new List() { newBorehole.Id }).ConfigureAwait(false);
- JsonResult jsonResult = (JsonResult)response!;
- Assert.IsNotNull(jsonResult.Value);
- List boreholes = (List)jsonResult.Value;
- Assert.AreEqual(1, boreholes.Count);
- }
-
[TestMethod]
public async Task Copy()
{
@@ -772,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..0fd43e998
--- /dev/null
+++ b/tests/api/Controllers/ExportControllerTest.cs
@@ -0,0 +1,363 @@
+using BDMS.Models;
+using CsvHelper;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Text;
+using static BDMS.Helpers;
+
+namespace BDMS.Controllers;
+
+[DeploymentItem("TestData")]
+[TestClass]
+public class ExportControllerTest
+{
+ private const string TestCsvString = "text/csv";
+ private const string ExportFileName = "boreholes_export.csv";
+ private BdmsContext context;
+ private ExportController controller;
+ private static int testBoreholeId = 1000068;
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ context = ContextFactory.GetTestContext();
+ controller = new ExportController(context) { ControllerContext = GetControllerContextAdmin() };
+ }
+
+ [TestMethod]
+ public async Task ExportJson()
+ {
+ var newBorehole = GetBoreholeToAdd();
+
+ var fieldMeasurementResult = new FieldMeasurementResult
+ {
+ ParameterId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.FieldMeasurementParameterSchema).FirstAsync().ConfigureAwait(false)).Id,
+ SampleTypeId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.FieldMeasurementSampleTypeSchema).FirstAsync().ConfigureAwait(false)).Id,
+ Value = 10.0,
+ };
+
+ var fieldMeasurement = new FieldMeasurement
+ {
+ Borehole = newBorehole,
+ StartTime = new DateTime(2021, 01, 01, 01, 01, 01, DateTimeKind.Utc),
+ EndTime = new DateTime(2021, 01, 01, 13, 01, 01, DateTimeKind.Utc),
+ Type = ObservationType.FieldMeasurement,
+ Comment = "Field measurement observation for testing",
+ FieldMeasurementResults = new List { fieldMeasurementResult },
+ };
+
+ var groundwaterLevelMeasurement = new GroundwaterLevelMeasurement
+ {
+ Borehole = newBorehole,
+ StartTime = new DateTime(2021, 01, 01, 01, 01, 01, DateTimeKind.Utc),
+ EndTime = new DateTime(2021, 01, 01, 13, 01, 01, DateTimeKind.Utc),
+ Type = ObservationType.GroundwaterLevelMeasurement,
+ Comment = "Groundwater level measurement observation for testing",
+ LevelM = 10.0,
+ LevelMasl = 11.0,
+ KindId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.GroundwaterLevelMeasurementKindSchema).FirstAsync().ConfigureAwait(false)).Id,
+ };
+
+ var waterIngress = new WaterIngress
+ {
+ Borehole = newBorehole,
+ IsOpenBorehole = true,
+ Type = ObservationType.WaterIngress,
+ Comment = "Water ingress observation for testing",
+ QuantityId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.WateringressQualitySchema).FirstAsync().ConfigureAwait(false)).Id,
+ ConditionsId = (await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.WateringressConditionsSchema).FirstAsync().ConfigureAwait(false)).Id,
+ };
+
+ var hydroTestResult = new HydrotestResult
+ {
+ ParameterId = 15203191,
+ Value = 10.0,
+ MaxValue = 15.0,
+ MinValue = 5.0,
+ };
+
+ var kindCodelistIds = await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.HydrotestKindSchema).Take(2).Select(c => c.Id).ToListAsync().ConfigureAwait(false);
+ var flowDirectionCodelistIds = await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.FlowdirectionSchema).Take(2).Select(c => c.Id).ToListAsync().ConfigureAwait(false);
+ var evaluationMethodCodelistIds = await context.Codelists.Where(c => c.Schema == HydrogeologySchemas.EvaluationMethodSchema).Take(2).Select(c => c.Id).ToListAsync().ConfigureAwait(false);
+
+ var kindCodelists = await GetCodelists(context, kindCodelistIds).ConfigureAwait(false);
+ var flowDirectionCodelists = await GetCodelists(context, flowDirectionCodelistIds).ConfigureAwait(false);
+ var evaluationMethodCodelists = await GetCodelists(context, evaluationMethodCodelistIds).ConfigureAwait(false);
+
+ var hydroTest = new Hydrotest
+ {
+ Borehole = newBorehole,
+ StartTime = new DateTime(2021, 01, 01, 01, 01, 01, DateTimeKind.Utc),
+ EndTime = new DateTime(2021, 01, 01, 13, 01, 01, DateTimeKind.Utc),
+ Type = ObservationType.Hydrotest,
+ Comment = "Hydrotest observation for testing",
+ HydrotestResults = new List() { hydroTestResult },
+ HydrotestFlowDirectionCodes = new List { new() { CodelistId = flowDirectionCodelists[0].Id }, new() { CodelistId = flowDirectionCodelists[1].Id } },
+ HydrotestKindCodes = new List { new() { CodelistId = kindCodelists[0].Id }, new() { CodelistId = kindCodelists[1].Id } },
+ HydrotestEvaluationMethodCodes = new List { new() { CodelistId = evaluationMethodCodelists[0].Id }, new() { CodelistId = evaluationMethodCodelists[1].Id } },
+ };
+
+ newBorehole.Observations = new List { hydroTest, fieldMeasurement, groundwaterLevelMeasurement, waterIngress };
+
+ context.Add(newBorehole);
+ await context.SaveChangesAsync().ConfigureAwait(false);
+
+ var response = await controller.ExportJsonAsync(new List() { newBorehole.Id }).ConfigureAwait(false);
+ JsonResult jsonResult = (JsonResult)response!;
+ Assert.IsNotNull(jsonResult.Value);
+ List boreholes = (List)jsonResult.Value;
+ Assert.AreEqual(1, boreholes.Count);
+ }
+
+ [TestMethod]
+ public async Task DownloadCsvWithValidIdsReturnsFileResultWithMax100Boreholes()
+ {
+ var ids = Enumerable.Range(testBoreholeId, 120).ToList();
+
+ var result = await controller.ExportCsvAsync(ids) as FileContentResult;
+
+ Assert.IsNotNull(result);
+ 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
+ Assert.AreEqual(100, recordCount);
+ }
+
+ [TestMethod]
+ public async Task DownloadCsvReturnsTVD()
+ {
+ var boreholeQuery = context.Boreholes
+ .Include(b => b.BoreholeGeometry);
+
+ var boreholeIdsWithoutGeometry = await boreholeQuery
+ .Where(b => b.BoreholeGeometry.Count < 2)
+ .Take(3).Select(b => b.Id).ToListAsync();
+
+ var boreholeIdsWithGeometry = await boreholeQuery
+ .Where(b => b.BoreholeGeometry.Count > 1)
+ .Take(3).Select(b => b.Id).ToListAsync();
+
+ var boreholeIds = boreholeIdsWithoutGeometry.Concat(boreholeIdsWithGeometry);
+ var result = await controller.ExportCsvAsync(boreholeIds) as FileContentResult;
+
+ Assert.IsNotNull(result);
+ Assert.AreEqual(TestCsvString, result.ContentType);
+ Assert.AreEqual(ExportFileName, 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.Contains(int.Parse(record.Id)))
+ {
+ Assert.AreEqual(totalDepthMd, totalDepthTvd);
+ Assert.AreEqual(topBedrockFreshMd, topBedrockFreshTvd);
+ Assert.AreEqual(topBedrockWeatheredMd, topBedrockWeatheredTvd);
+ }
+
+ if (boreholeIdsWithGeometry.Contains(int.Parse(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()
+ {
+ string idGeoDinValue = "ID GeoDIN value";
+ string idTopFelsValue = "ID TopFels value";
+ string idKernlagerValue = "ID Kernlager value";
+ var firstBoreholeId = 1_009_068;
+ var boreholeWithCustomIds = new Borehole
+ {
+ Id = firstBoreholeId,
+ BoreholeCodelists = new List
+ {
+ new BoreholeCodelist
+ {
+ BoreholeId = firstBoreholeId,
+ CodelistId = 100000010,
+ Value = idGeoDinValue,
+ },
+ new BoreholeCodelist
+ {
+ BoreholeId = firstBoreholeId,
+ CodelistId = 100000011,
+ Value = idKernlagerValue,
+ },
+ },
+ };
+
+ var secondBoreholeId = 1_009_069;
+ var boreholeWithOtherCustomIds = new Borehole
+ {
+ Id = secondBoreholeId,
+ BoreholeCodelists = new List
+ {
+ new BoreholeCodelist
+ {
+ BoreholeId = secondBoreholeId,
+ CodelistId = 100000010,
+ Value = idGeoDinValue,
+ },
+ new BoreholeCodelist
+ {
+ BoreholeId = secondBoreholeId,
+ CodelistId = 100000009,
+ Value = idTopFelsValue,
+ },
+ },
+ };
+
+ context.Boreholes.AddRange(boreholeWithCustomIds, boreholeWithOtherCustomIds);
+ await context.SaveChangesAsync();
+
+ var ids = new List { firstBoreholeId, secondBoreholeId };
+
+ var result = await controller.ExportCsvAsync(ids) as FileContentResult;
+ Assert.IsNotNull(result);
+ 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(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(idGeoDinValue, secondBorehole.IDGeODin);
+ Assert.AreEqual("", secondBorehole.IDKernlager);
+ Assert.AreEqual(idTopFelsValue, secondBorehole.IDTopFels);
+ }
+
+ [TestMethod]
+ public async Task DownloadCsvWithInvalidIdsReturnsNotFound()
+ {
+ var ids = new List { 8, 2, 11, 87 };
+
+ var result = await controller.ExportCsvAsync(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.ExportCsvAsync(ids) as FileContentResult;
+
+ Assert.IsNotNull(result);
+ Assert.IsNotNull(result);
+ 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;
+ Assert.AreEqual(recordCount, 1);
+ }
+
+ [TestMethod]
+ public async Task DownloadCsvEmptyIdsReturnsBadRequest()
+ {
+ var ids = new List();
+
+ var result = await controller.ExportCsvAsync(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();
+ }
+
+ private Borehole GetBoreholeToAdd()
+ {
+ return new Borehole
+ {
+ CreatedById = 4,
+ UpdatedById = 4,
+ Locked = null,
+ LockedById = null,
+ WorkgroupId = 1,
+ IsPublic = true,
+ TypeId = 20101003,
+ LocationX = 2600000.0,
+ PrecisionLocationX = 5,
+ LocationY = 1200000.0,
+ PrecisionLocationY = 5,
+ LocationXLV03 = 600000.0,
+ PrecisionLocationXLV03 = 5,
+ LocationYLV03 = 200000.0,
+ PrecisionLocationYLV03 = 5,
+ OriginalReferenceSystem = ReferenceSystem.LV95,
+ ElevationZ = 450.5,
+ HrsId = 20106001,
+ TotalDepth = 100.0,
+ RestrictionId = 20111003,
+ RestrictionUntil = DateTime.UtcNow.AddYears(1),
+ NationalInterest = false,
+ OriginalName = "BH-257",
+ Name = "Borehole 257",
+ LocationPrecisionId = 20113002,
+ ElevationPrecisionId = null,
+ ProjectName = "Project Alpha",
+ Country = "CH",
+ Canton = "ZH",
+ Municipality = "Zurich",
+ PurposeId = 22103002,
+ StatusId = 22104001,
+ DepthPrecisionId = 22108005,
+ TopBedrockFreshMd = 10.5,
+ TopBedrockWeatheredMd = 8.0,
+ HasGroundwater = true,
+ Geometry = null,
+ Remarks = "Test borehole for project",
+ LithologyTopBedrockId = 15104934,
+ LithostratigraphyTopBedrockId = 15300259,
+ ChronostratigraphyTopBedrockId = 15001141,
+ ReferenceElevation = 500.0,
+ ReferenceElevationPrecisionId = 20114002,
+ ReferenceElevationTypeId = 20117003,
+ };
+ }
+}
diff --git a/tests/api/Controllers/ImportControllerTest.cs b/tests/api/Controllers/ImportControllerTest.cs
index bb8afbb1f..a1345d473 100644
--- a/tests/api/Controllers/ImportControllerTest.cs
+++ b/tests/api/Controllers/ImportControllerTest.cs
@@ -518,7 +518,7 @@ public async Task UploadShouldSaveDataToDatabaseAsync()
var boreholeCsvFile = GetFormFileByExistingFile("testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
@@ -565,7 +565,7 @@ public async Task UploadShouldSaveMinimalDatasetAsync()
var boreholeCsvFile = GetFormFileByExistingFile("minimal_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, attachments: null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
@@ -604,7 +604,7 @@ public async Task UploadShouldSavePrecisionDatasetAsync()
var boreholeCsvFile = GetFormFileByExistingFile("precision_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, attachments: null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
@@ -639,74 +639,6 @@ public async Task UploadShouldSavePrecisionDatasetAsync()
Assert.AreEqual(4, boreholeWithZeros.PrecisionLocationY);
}
- [TestMethod]
- public async Task UploadShouldSaveBoreholeWithAttachmentsAsync()
- {
- httpClientFactoryMock
- .Setup(cf => cf.CreateClient(It.IsAny()))
- .Returns(() => new HttpClient())
- .Verifiable();
-
- var boreholeCsvFormFile = GetFormFileByExistingFile("borehole_with_attachments.csv");
- var firstAttachmentFile = GetRandomPDFFile("attachment_1.pdf");
- var secondAttachmentFile = GetRandomFile("attachment_2.txt");
- var thirdAttachmentFile = GetRandomFile("attachment_3.zip");
- var fourthAttachmentFile = GetRandomFile("attachment_4.jpg");
- var fifthAttachmentFile = GetRandomFile("attachment_5.csv");
- var sixthAttachmentFile = GetFormFileByExistingFile("borehole_attachment_1.pdf");
- var seventhAttachmentFile = GetFormFileByExistingFile("borehole_attachment_2.pdf");
- var eighthAttachmentFile = GetFormFileByExistingFile("borehole_attachment_3.csv");
- var ninthAttachmentFile = GetFormFileByExistingFile("borehole_attachment_4.zip");
- var tenthAttachmentFile = GetFormFileByExistingFile("borehole_attachment_5.png");
-
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFormFile, new List() { firstAttachmentFile, secondAttachmentFile, thirdAttachmentFile, fourthAttachmentFile, fifthAttachmentFile, sixthAttachmentFile, seventhAttachmentFile, eighthAttachmentFile, ninthAttachmentFile, tenthAttachmentFile });
-
- ActionResultAssert.IsOk(response.Result);
- OkObjectResult okResult = (OkObjectResult)response.Result!;
- Assert.AreEqual(1, okResult.Value);
-
- var borehole = GetBoreholesWithIncludes(context.Boreholes).Single(b => b.OriginalName == "ACORNFLEA");
- Assert.AreEqual(10, borehole.BoreholeFiles.Count);
- }
-
- [TestMethod]
- public async Task UploadShouldSaveBoreholesWithNotAllHaveAttachmentsAsync()
- {
- httpClientFactoryMock
- .Setup(cf => cf.CreateClient(It.IsAny()))
- .Returns(() => new HttpClient())
- .Verifiable();
-
- var boreholeCsvFormFile = GetFormFileByExistingFile("boreholes_not_all_have_attachments.csv");
- var firstAttachmentFile = GetFormFileByExistingFile("borehole_attachment_1.pdf");
- var secondAttachmentFile = GetFormFileByExistingFile("borehole_attachment_2.pdf");
-
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFormFile, new List() { firstAttachmentFile, secondAttachmentFile });
-
- ActionResultAssert.IsOk(response.Result);
- OkObjectResult okResult = (OkObjectResult)response.Result!;
- Assert.AreEqual(3, okResult.Value);
- }
-
- [TestMethod]
- public async Task UploadShouldSaveBoreholeWithAttachmentFileNamesMixedCaseAsync()
- {
- httpClientFactoryMock
- .Setup(cf => cf.CreateClient(It.IsAny()))
- .Returns(() => new HttpClient())
- .Verifiable();
-
- var boreholeCsvFormFile = GetFormFileByExistingFile("borehole_with_mixed_case_in_attachments_filenames.csv");
- var firstPdfFormFile = GetFormFileByExistingFile("borehole_attachment_1.pdf");
- var secondPdfFormFile = GetFormFileByExistingFile("borehole_attachment_2.pdf");
-
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFormFile, new List() { firstPdfFormFile, secondPdfFormFile });
-
- ActionResultAssert.IsOk(response.Result);
- OkObjectResult okResult = (OkObjectResult)response.Result!;
- Assert.AreEqual(1, okResult.Value);
- }
-
[TestMethod]
public async Task UploadShouldSaveSpecialCharsDatasetAsync()
{
@@ -717,7 +649,7 @@ public async Task UploadShouldSaveSpecialCharsDatasetAsync()
var boreholeCsvFile = GetFormFileByExistingFile("special_chars_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
@@ -736,7 +668,7 @@ public async Task UploadWithMissingCoordinatesAsync()
{
var boreholeCsvFile = GetFormFileByExistingFile("no_coordinates_provided_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
Assert.IsInstanceOfType(response.Result, typeof(ObjectResult));
ObjectResult result = (ObjectResult)response.Result!;
@@ -758,7 +690,7 @@ public async Task UploadBoreholeWithLV95CoordinatesAsync()
var boreholeCsvFile = GetFormFileByExistingFile("lv95_coordinates_provided_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
@@ -787,7 +719,7 @@ public async Task UploadBoreholeWithLV03CoordinatesAsync()
var boreholeCsvFile = GetFormFileByExistingFile("lv03_coordinates_provided_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
@@ -816,7 +748,7 @@ public async Task UploadBoreholeWithLV03OutOfRangeCoordinatesAsync()
var boreholeCsvFile = GetFormFileByExistingFile("lv03_out_of_range_coordinates_provided_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
@@ -840,7 +772,7 @@ public async Task UploadEmptyFileShouldReturnError()
{
var boreholeCsvFile = new FormFile(null, 0, 0, null, "non_existent_file.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsBadRequest(response.Result);
BadRequestObjectResult badRequestResult = (BadRequestObjectResult)response.Result!;
@@ -852,101 +784,13 @@ public async Task UploadInvalidFileTypeBoreholeCsvShouldReturnError()
{
var invalidFileTypeBoreholeFile = GetFormFileByContent(fileContent: "This is the content of the file.", fileName: "invalid_file_type.txt");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, invalidFileTypeBoreholeFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, invalidFileTypeBoreholeFile);
ActionResultAssert.IsBadRequest(response.Result);
BadRequestObjectResult badRequestResult = (BadRequestObjectResult)response.Result!;
Assert.AreEqual("Invalid file type for borehole csv.", badRequestResult.Value);
}
- [TestMethod]
- public async Task UploadBoreholeCsvFileWithoutAttachmentsButWithProvidedFilesShouldCreateBorehole()
- {
- httpClientFactoryMock
- .Setup(cf => cf.CreateClient(It.IsAny()))
- .Returns(() => new HttpClient())
- .Verifiable();
-
- var boreholeCsvFile = GetFormFileByContent(fileContent: "original_name;location_x;location_y\r\nFrank Place;2000000;1000000", fileName: "boreholes.csv");
- var firstPdfFormFile = GetFormFileByExistingFile("borehole_attachment_1.pdf");
- var secondPdfFormFile = GetFormFileByExistingFile("borehole_attachment_2.pdf");
-
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, new List() { firstPdfFormFile, secondPdfFormFile });
-
- ActionResultAssert.IsOk(response.Result);
- OkObjectResult okResult = (OkObjectResult)response.Result!;
- Assert.AreEqual(1, okResult.Value);
- }
-
- [TestMethod]
- public async Task UploadBoreholeCsvFileWithAttachmentsLinkedPdfsShouldCreateBorehole()
- {
- httpClientFactoryMock
- .Setup(cf => cf.CreateClient(It.IsAny()))
- .Returns(() => new HttpClient())
- .Verifiable();
-
- var firstAttachmentFileName = "borehole_attachment_1.pdf";
- var secondAttachmentFileName = "borehole_attachment_2.pdf";
-
- var pdfContent = @"original_name;location_x;location_y;attachments
-Frank Place;2000000;1000000;borehole_attachment_1.pdf,borehole_attachment_2.pdf";
- var boreholeCsvFile = GetFormFileByContent(fileContent: pdfContent, fileName: "boreholes.csv");
- var firstPdfFormFile = GetFormFileByExistingFile(firstAttachmentFileName);
- var secondPdfFormFile = GetFormFileByExistingFile(secondAttachmentFileName);
-
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, new List() { firstPdfFormFile, secondPdfFormFile });
-
- ActionResultAssert.IsOk(response.Result);
- OkObjectResult okResult = (OkObjectResult)response.Result!;
- Assert.AreEqual(1, okResult.Value);
-
- // Get latest borehole Ids
- var latestBoreholeId = context.Boreholes.OrderByDescending(b => b.Id).First().Id;
-
- var borehole = GetBoreholesWithIncludes(context.Boreholes)
- .Single(b => b.Id == latestBoreholeId);
-
- Assert.AreEqual(borehole.BoreholeFiles.First().File.Name, firstAttachmentFileName);
- Assert.AreEqual(borehole.BoreholeFiles.Last().File.Name, secondAttachmentFileName);
- Assert.AreEqual(borehole.BoreholeFiles.Count, 2);
- }
-
- [TestMethod]
- public async Task UploadBoreholeCsvFileWithNotPresentAttachmentsShouldReturnError()
- {
- var boreholeCsvFile = GetFormFileByExistingFile("borehole_with_not_present_attachments.csv");
- var firstPdfFormFile = GetFormFileByExistingFile("borehole_attachment_1.pdf");
- var secondPdfFormFile = GetFormFileByExistingFile("borehole_attachment_2.pdf");
-
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, new List() { firstPdfFormFile, secondPdfFormFile });
-
- Assert.IsInstanceOfType(response.Result, typeof(ObjectResult));
- ObjectResult result = (ObjectResult)response.Result!;
- ActionResultAssert.IsBadRequest(result);
-
- ValidationProblemDetails problemDetails = (ValidationProblemDetails)result.Value!;
- Assert.AreEqual(1, problemDetails.Errors.Count);
-
- CollectionAssert.AreEquivalent(
- new[] { "Attachment file 'is_not_present_in_upload_files.pdf' not found." },
- problemDetails.Errors["Row1"]);
- }
-
- [TestMethod]
- public async Task UploadBoreholeCsvFileWithWhiteSpaceInAttachmentFileNameShouldReturnError()
- {
- var boreholeCsvFile = GetFormFileByExistingFile("borehole_with_not_present_attachments.csv");
- var firstPdfFormFile = GetFormFileByExistingFile("borehole_attachment_1.pdf");
- var whiteSpacePdf = GetFormFileByExistingFile("white space.pdf");
-
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, new List() { firstPdfFormFile, whiteSpacePdf });
-
- ActionResultAssert.IsBadRequest(response.Result);
- BadRequestObjectResult badRequestResult = (BadRequestObjectResult)response.Result!;
- Assert.AreEqual("One or more file name(s) contain a whitespace.", badRequestResult.Value);
- }
-
[TestMethod]
public void IsCorrectFileType()
{
@@ -963,7 +807,7 @@ public void IsCorrectFileType()
[TestMethod]
public async Task UploadNoFileShouldReturnError()
{
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, null, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, null);
ActionResultAssert.IsBadRequest(response.Result);
BadRequestObjectResult badRequestResult = (BadRequestObjectResult)response.Result!;
@@ -975,7 +819,7 @@ public async Task UploadNoDataButRequiredHeadersSetShouldUploadNoBorehole()
{
var boreholeCsvFile = GetFormFileByExistingFile("no_data_but_required_headers.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
@@ -987,7 +831,7 @@ public async Task UploadMultipleRowsMissingRequiredFieldsShouldReturnError()
{
var boreholeCsvFile = GetFormFileByExistingFile("multiple_rows_missing_required_attributes_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
Assert.IsInstanceOfType(response.Result, typeof(ObjectResult));
ObjectResult result = (ObjectResult)response.Result!;
@@ -1012,7 +856,7 @@ public async Task UploadRequiredHeadersMissingShouldReturnError()
{
var boreholeCsvFile = GetFormFileByExistingFile("missing_required_headers_testdata.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
Assert.IsInstanceOfType(response.Result, typeof(ObjectResult));
var result = (ObjectResult)response.Result!;
@@ -1129,25 +973,6 @@ public async Task UploadDuplicateBoreholesInDbButDifferentWorkgroupShouldUploadB
Assert.AreEqual(2, okResult.Value);
}
- [TestMethod]
- public async Task UploadWithAttachmentToLargeShouldThrowError()
- {
- var minBoreholeId = context.Boreholes.Min(b => b.Id);
- var boreholeCsvFile = GetRandomFile("borehoel.csv");
-
- long targetSizeInBytes = 201 * 1024 * 1024; // 201MB
- byte[] content = new byte[targetSizeInBytes];
- var stream = new MemoryStream(content);
-
- var attachment = new FormFile(stream, 0, stream.Length, "file", "dummy.txt");
-
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, new[] { attachment });
-
- ActionResultAssert.IsBadRequest(response.Result);
- BadRequestObjectResult badRequestResult = (BadRequestObjectResult)response.Result!;
- Assert.AreEqual($"One or more attachment exceed maximum file size of 200 Mb.", badRequestResult.Value);
- }
-
[TestMethod]
public async Task UploadWithMaxValidationErrorsExceededShouldReturnError()
{
@@ -1185,7 +1010,7 @@ public async Task UploadShouldIgnoreLocationFields()
var boreholeCsvFile = GetFormFileByExistingFile("borehole_and_location_data.csv");
- ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile, null);
+ ActionResult response = await controller.UploadFileAsync(workgroupId: 1, boreholeCsvFile);
ActionResultAssert.IsOk(response.Result);
OkObjectResult okResult = (OkObjectResult)response.Result!;
diff --git a/tests/api/TestData/borehole_attachment_1.pdf b/tests/api/TestData/borehole_attachment_1.pdf
deleted file mode 100644
index ca3c36c8d..000000000
Binary files a/tests/api/TestData/borehole_attachment_1.pdf and /dev/null differ
diff --git a/tests/api/TestData/borehole_attachment_2.pdf b/tests/api/TestData/borehole_attachment_2.pdf
deleted file mode 100644
index 5cecb0fa6..000000000
Binary files a/tests/api/TestData/borehole_attachment_2.pdf and /dev/null differ
diff --git a/tests/api/TestData/borehole_attachment_3.csv b/tests/api/TestData/borehole_attachment_3.csv
deleted file mode 100644
index 133272495..000000000
--- a/tests/api/TestData/borehole_attachment_3.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-id;name;
-1;ACORNFLEA;
diff --git a/tests/api/TestData/borehole_attachment_4.zip b/tests/api/TestData/borehole_attachment_4.zip
deleted file mode 100644
index 29849af84..000000000
Binary files a/tests/api/TestData/borehole_attachment_4.zip and /dev/null differ
diff --git a/tests/api/TestData/borehole_attachment_5.png b/tests/api/TestData/borehole_attachment_5.png
deleted file mode 100644
index a971b2355..000000000
Binary files a/tests/api/TestData/borehole_attachment_5.png and /dev/null differ
diff --git a/tests/api/TestData/borehole_attachment_with_wrong_extension.txt b/tests/api/TestData/borehole_attachment_with_wrong_extension.txt
deleted file mode 100644
index f2c43b62e..000000000
Binary files a/tests/api/TestData/borehole_attachment_with_wrong_extension.txt and /dev/null differ
diff --git a/tests/api/TestData/borehole_with_attachments.csv b/tests/api/TestData/borehole_with_attachments.csv
deleted file mode 100644
index 75b26ca6f..000000000
--- a/tests/api/TestData/borehole_with_attachments.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-original_name;location_x;location_y;attachments
-ACORNFLEA;0;0;attachment_1.pdf,attachment_2.txt,attachment_3.zip,attachment_4.jpg,attachment_5.csv,borehole_attachment_1.pdf,borehole_attachment_2.pdf,borehole_attachment_3.csv,borehole_attachment_4.zip,borehole_attachment_5.png
diff --git a/tests/api/TestData/borehole_with_mixed_case_in_attachments_filenames.csv b/tests/api/TestData/borehole_with_mixed_case_in_attachments_filenames.csv
deleted file mode 100644
index ab830ee9f..000000000
--- a/tests/api/TestData/borehole_with_mixed_case_in_attachments_filenames.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-original_name;location_x;location_y;attachments
-ACORNFLEA;0;0;Borehole_Attachment_1.pdf,borehole_attachment_2.pdf
diff --git a/tests/api/TestData/borehole_with_not_present_attachments.csv b/tests/api/TestData/borehole_with_not_present_attachments.csv
deleted file mode 100644
index 65fd39745..000000000
--- a/tests/api/TestData/borehole_with_not_present_attachments.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-original_name;location_x;location_y;attachments
-ACORNFLEA;0;0;borehole_attachment_1.pdf,is_not_present_in_upload_files.pdf
diff --git a/tests/api/TestData/boreholes_not_all_have_attachments.csv b/tests/api/TestData/boreholes_not_all_have_attachments.csv
deleted file mode 100644
index fbd3fb704..000000000
--- a/tests/api/TestData/boreholes_not_all_have_attachments.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-original_name;location_x;location_y;attachments
-ACORNFLEA;2000000;1000000;borehole_attachment_1.pdf,borehole_attachment_2.pdf
-BERRYSNAIL;2000010;1000010;
-BLUEBIRDY;2000020;1000020;;
diff --git a/tests/api/TestData/testdata.csv b/tests/api/TestData/testdata.csv
index b3a6918d3..c4b601e71 100644
--- a/tests/api/TestData/testdata.csv
+++ b/tests/api/TestData/testdata.csv
@@ -1,4 +1,4 @@
-id_geodin_shortname;id_info_geol;id_original;id_canton;id_geo_quat;id_geo_mol;id_geo_therm;id_top_fels;id_geodin;id_kernlager;original_name;project_name;name;date;restriction_id;restriction_until;original_reference_system;location_x;location_y;location_x_lv_03;location_y_lv_03;location_precision_id;elevation_z;elevation_precision_id;reference_elevation;reference_elevation_type_id;reference_elevation_precision_id;hrs_id;type_id;purpose_id;status_id;remarks;total_depth;depth_precision_id;top_bedrock_fresh_md;top_bedrock_weathered_md;has_groundwater;lithology_top_bedrock_id;chronostratigraphy_top_bedrock_id;lithostratigraphy_top_bedrock_id
+IDGeODin-Shortname;IDInfoGeol;IDOriginal;IDCanton;IDGeoQuat;IDGeoMol;IDGeoTherm;IDTopFels;IDGeODin;IDKernlager;original_name;project_name;name;date;restriction_id;restriction_until;original_reference_system;location_x;location_y;location_x_lv_03;location_y_lv_03;location_precision_id;elevation_z;elevation_precision_id;reference_elevation;reference_elevation_type_id;reference_elevation_precision_id;hrs_id;type_id;purpose_id;status_id;remarks;total_depth;depth_precision_id;top_bedrock_fresh_md;top_bedrock_weathered_md;has_groundwater;lithology_top_bedrock_id;chronostratigraphy_top_bedrock_id;lithostratigraphy_top_bedrock_id
Id_1;Id_2;;;;;Id_3;;;kernlager AETHERMAGIC;Unit_Test_1;Projekt 1 ;Unit_Test_1_a;2021-08-06 00:36:21.991827+00;20111002;;20104001;2618962;1144995;;;20113005;640.7726659;20114001;317.9010264;20117002;20114004;20106001;20101001;22103001;22104003;this product is top-notch.;4232.711946;22108003;398.8529283;656.2476436;TRUE;15104669;15001073;15300261
Id_4;;Id_5;Id_6;;;;;;;Unit_Test_2;Projekt 2;Unit_Test_2_a;2021-03-31 12:20:10.341393+00;;;20104001;2631690;1170516;;;20113002;3430.769638;20114005;2016.314814;20117005;20114004;20106001;20101001;22103001;22104008;This product works certainly well. It perfectly improves my tennis by a lot.;794.1547194;22108005;958.2378855;549.9801019;;15104670;15001009;15302009
;;Id_7;Id_8;;;;Id_9;;;Unit_Test_3;Projekt 3;Unit_Test_3_a;;20111002;01.12.2023;20104001;2614834;1178661;;;20113005;1720.766609;20114003;1829.812475;20117005;20114002;20106001;20101001;;22104002;This is a really good product.;2429.747725;22108002;759.7574008;827.8441205;TRUE;15104671;15001007;15302339