Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add csv export #1725

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
475764f
Add CSV Export
MiraGeowerkstatt Dec 5, 2024
f97a02d
Add alert for more than 100 export
MiraGeowerkstatt Dec 5, 2024
c4078b1
Add button translations
MiraGeowerkstatt Dec 5, 2024
d58a433
Fix prompt condition
MiraGeowerkstatt Dec 5, 2024
89c75b4
Add cypress test
MiraGeowerkstatt Dec 5, 2024
d822b76
Fix merge error
MiraGeowerkstatt Dec 5, 2024
ec3b10d
Rename properties for csv Export
MiraGeowerkstatt Dec 5, 2024
25d02e8
Fix alternate Name on client
MiraGeowerkstatt Dec 5, 2024
08e7ced
Fix name import
MiraGeowerkstatt Dec 5, 2024
21b81ce
Rename props on client
MiraGeowerkstatt Dec 5, 2024
a1f4540
Fix test expectation
MiraGeowerkstatt Dec 5, 2024
c029b26
Fix typo
MiraGeowerkstatt Dec 5, 2024
56493a7
remove duplicated code
MiraGeowerkstatt Dec 5, 2024
6120cbc
write csv async
MiraGeowerkstatt Dec 5, 2024
60a7027
Fix function signature
MiraGeowerkstatt Dec 5, 2024
2d52126
Remove log
MiraGeowerkstatt Dec 5, 2024
82cbc5c
Return bad request on one line
MiraGeowerkstatt Dec 10, 2024
2b3da9f
Remove whitespace
MiraGeowerkstatt Dec 10, 2024
9bbea08
Rename method
MiraGeowerkstatt Dec 10, 2024
11ee187
Fix indentation
MiraGeowerkstatt Dec 10, 2024
e893b2b
Add test cases
MiraGeowerkstatt Dec 10, 2024
93e1ea9
Fix whitespace error
MiraGeowerkstatt Dec 10, 2024
1025a89
Use max page size
MiraGeowerkstatt Dec 10, 2024
880e6c5
Merge branch 'main' into add-csv-export
MiraGeowerkstatt Dec 10, 2024
009151d
Fix spaces in doc
MiraGeowerkstatt Dec 10, 2024
e2f683b
Use maxPage Size in function doc
MiraGeowerkstatt Dec 10, 2024
bd5de35
Use functions to get filesnames
MiraGeowerkstatt Dec 10, 2024
6d8c0ba
Use String writer
MiraGeowerkstatt Dec 10, 2024
4fa1a79
Update changelog
MiraGeowerkstatt Dec 10, 2024
b6c18be
Merge branch 'main' into add-csv-export
MiraGeowerkstatt Dec 10, 2024
216b7c1
Merge branch 'main' into add-csv-export
MiraGeowerkstatt Dec 10, 2024
26fddc4
Fix spelling
MiraGeowerkstatt Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/api/BdmsContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasForeignKey(bf => bf.BoreholeId),
j => j.HasKey(bf => new { bf.BoreholeId, bf.CodelistId }));

modelBuilder.Entity<Borehole>().HasOne(l => l.Chronostratigraphy).WithMany().HasForeignKey(l => l.ChronostratigraphyId);
modelBuilder.Entity<Borehole>().HasOne(l => l.ChronostratigraphyTopBedrock).WithMany().HasForeignKey(l => l.ChronostratigraphyTopBedrockId);
modelBuilder.Entity<Borehole>().HasOne(l => l.Type).WithMany().HasForeignKey(l => l.TypeId);
modelBuilder.Entity<Borehole>().HasOne(l => l.Hrs).WithMany().HasForeignKey(l => l.HrsId);
modelBuilder.Entity<Borehole>().HasOne(l => l.LithologyTopBedrock).WithMany().HasForeignKey(l => l.LithologyTopBedrockId);
modelBuilder.Entity<Borehole>().HasOne(l => l.Lithostratigraphy).WithMany().HasForeignKey(l => l.LithostratigraphyId);
modelBuilder.Entity<Borehole>().HasOne(l => l.LithostratigraphyTopBedrock).WithMany().HasForeignKey(l => l.LithostratigraphyTopBedrockId);
modelBuilder.Entity<Borehole>().HasOne(l => l.Purpose).WithMany().HasForeignKey(l => l.PurposeId);
modelBuilder.Entity<Borehole>().HasOne(l => l.QtDepth).WithMany().HasForeignKey(l => l.QtDepthId);
modelBuilder.Entity<Borehole>().HasOne(l => l.DepthPrecision).WithMany().HasForeignKey(l => l.DepthPrecisionId);
modelBuilder.Entity<Borehole>().HasOne(l => l.ElevationPrecision).WithMany().HasForeignKey(l => l.ElevationPrecisionId);
modelBuilder.Entity<Borehole>().HasOne(l => l.LocationPrecision).WithMany().HasForeignKey(l => l.LocationPrecisionId);
modelBuilder.Entity<Borehole>().HasOne(l => l.QtReferenceElevation).WithMany().HasForeignKey(l => l.QtReferenceElevationId);
modelBuilder.Entity<Borehole>().HasOne(l => l.ReferenceElevationPrecision).WithMany().HasForeignKey(l => l.ReferenceElevationPrecisionId);
modelBuilder.Entity<Borehole>().HasOne(l => l.ReferenceElevationType).WithMany().HasForeignKey(l => l.ReferenceElevationTypeId);
modelBuilder.Entity<Borehole>().HasOne(l => l.Restriction).WithMany().HasForeignKey(l => l.RestrictionId);
modelBuilder.Entity<Borehole>().HasOne(l => l.Status).WithMany().HasForeignKey(l => l.StatusId);
Expand Down
20 changes: 10 additions & 10 deletions src/api/BdmsContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public static void SeedData(this BdmsContext context)
.RuleFor(o => o.Restriction, _ => default!)
.RuleFor(o => o.RestrictionUntil, f => f.Date.Future().ToUniversalTime().OrNull(f, .9f))
.RuleFor(o => o.OriginalName, f => f.Name.FullName())
.RuleFor(o => o.AlternateName, f => "")
.RuleFor(o => o.Name, f => "")
.RuleFor(o => o.LocationPrecisionId, f => f.PickRandom(locationPrecisionIds).OrNull(f, .1f))
.RuleFor(o => o.LocationPrecision, _ => default!)
.RuleFor(o => o.ElevationPrecisionId, f => f.PickRandom(elevationPrecisionIds).OrNull(f, .1f))
Expand All @@ -168,21 +168,21 @@ public static void SeedData(this BdmsContext context)
.RuleFor(o => o.Purpose, _ => default!)
.RuleFor(o => o.StatusId, f => f.PickRandom(statusIds).OrNull(f, .05f))
.RuleFor(o => o.Status, _ => default!)
.RuleFor(o => o.QtDepthId, f => f.PickRandom(qtDepthIds).OrNull(f, .05f))
.RuleFor(o => o.QtDepth, _ => default!)
.RuleFor(o => o.DepthPrecisionId, f => f.PickRandom(qtDepthIds).OrNull(f, .05f))
.RuleFor(o => o.DepthPrecision, _ => default!)
.RuleFor(o => o.TopBedrockFreshMd, f => f.Random.Double(0, 1000).OrNull(f, .05f))
.RuleFor(o => o.TopBedrockWeatheredMd, f => f.Random.Double(0, 2).OrNull(f, .05f))
.RuleFor(o => o.HasGroundwater, f => f.Random.Bool().OrNull(f, .2f))
.RuleFor(o => o.Remarks, f => f.Rant.Review().OrNull(f, .05f))
.RuleFor(o => o.LithologyTopBedrockId, f => f.PickRandom(lithologyTopBedrockIds).OrNull(f, .05f))
.RuleFor(o => o.LithologyTopBedrock, _ => default!)
.RuleFor(o => o.LithostratigraphyId, f => f.PickRandom(lithostratigraphyTopBedrockIds).OrNull(f, .05f))
.RuleFor(o => o.Lithostratigraphy, _ => default!)
.RuleFor(o => o.ChronostratigraphyId, f => f.PickRandom(chronostratigraphyTopBedrockIds).OrNull(f, .05f))
.RuleFor(o => o.Chronostratigraphy, _ => default!)
.RuleFor(o => o.LithostratigraphyTopBedrockId, f => f.PickRandom(lithostratigraphyTopBedrockIds).OrNull(f, .05f))
.RuleFor(o => o.LithostratigraphyTopBedrock, _ => default!)
.RuleFor(o => o.ChronostratigraphyTopBedrockId, f => f.PickRandom(chronostratigraphyTopBedrockIds).OrNull(f, .05f))
.RuleFor(o => o.ChronostratigraphyTopBedrock, _ => default!)
.RuleFor(o => o.ReferenceElevation, f => f.Random.Double(0, 4500).OrNull(f, .05f))
.RuleFor(o => o.QtReferenceElevationId, f => f.PickRandom(elevationPrecisionIds).OrNull(f, .05f))
.RuleFor(o => o.QtReferenceElevation, _ => default!)
.RuleFor(o => o.ReferenceElevationPrecisionId, f => f.PickRandom(elevationPrecisionIds).OrNull(f, .05f))
.RuleFor(o => o.ReferenceElevationPrecision, _ => default!)
.RuleFor(o => o.ReferenceElevationTypeId, f => f.PickRandom(referenceElevationTypeIds).OrNull(f, .05f))
.RuleFor(o => o.ReferenceElevationType, _ => default!)
.RuleFor(o => o.BoreholeCodelists, _ => new Collection<BoreholeCodelist>())
Expand All @@ -198,7 +198,7 @@ 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)))
.FinishWith((f, o) => { o.AlternateName = o.OriginalName; });
.FinishWith((f, o) => { o.Name = o.OriginalName; });

Borehole SeededBoreholes(int seed) => fakeBoreholes.UseSeed(seed).Generate();
context.BulkInsert(boreholeRange.Select(SeededBoreholes).ToList(), bulkConfig);
Expand Down
81 changes: 74 additions & 7 deletions src/api/Controllers/BoreholeController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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;

namespace BDMS.Controllers;

Expand Down Expand Up @@ -77,7 +79,7 @@
/// <param name="pageSize">The page size for pagination.</param>
[HttpGet]
[Authorize(Policy = PolicyNames.Viewer)]
public async Task<ActionResult<PaginatedBoreholeResponse>> GetAllAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable<int>? ids = null, [FromQuery][Range(1, int.MaxValue)] int pageNumber = 1, [FromQuery] [Range(1, MaxPageSize)] int pageSize = 100)
public async Task<ActionResult<PaginatedBoreholeResponse>> GetAllAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable<int>? ids = null, [FromQuery][Range(1, int.MaxValue)] int pageNumber = 1, [FromQuery][Range(1, MaxPageSize)] int pageSize = 100)
{
pageSize = Math.Min(MaxPageSize, Math.Max(1, pageSize));

Expand Down Expand Up @@ -142,6 +144,71 @@
.Include(b => b.UpdatedBy);
}

/// <summary>
/// Exports the details of up to 100 boreholes as a CSV file. Filters the boreholes based on the provided list of IDs.
MiraGeowerkstatt marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="ids">The list of IDs for the boreholes to be exported.</param>
/// <returns>A CSV file containing the details of up to 100 specified boreholes.</returns>
[HttpGet("export-csv")]
[Authorize(Policy = PolicyNames.Viewer)]
public async Task<IActionResult> DownloadCsv([FromQuery] IEnumerable<int> ids)
MiraGeowerkstatt marked this conversation as resolved.
Show resolved Hide resolved
MiraGeowerkstatt marked this conversation as resolved.
Show resolved Hide resolved
{
if (!ids.Any())
{
return BadRequest("The list of IDs must not be empty.");
}
MiraGeowerkstatt marked this conversation as resolved.
Show resolved Hide resolved

Logger.LogInformation("Export borehole(s) with ids <{Ids}>.", string.Join(", ", ids));
Fixed Show fixed Hide fixed

var boreholes = await Context.Boreholes
.Where(borehole => ids.Contains(borehole.Id))
.Take(100)
MiraGeowerkstatt marked this conversation as resolved.
Show resolved Hide resolved
MiraGeowerkstatt marked this conversation as resolved.
Show resolved Hide resolved
.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);

var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, leaveOpen: true))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQ: Hier kann die StringWriter-Klasse verwendet werden. So muss man sich nicht mit dem MemoryStream herumschlagen.
Beispiel:

using var stringWriter = new StringWriter();
using var csvWriter = new CsvWriter(stringWriter , CultureInfo.InvariantCulture)return File(stringWriter.ToString(),

using (var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
await csvWriter.WriteRecordsAsync(boreholes).ConfigureAwait(false);
}

stream.Position = 0;

return File(stream.ToArray(), "text/csv", "boreholes_export.csv");
}

/// <summary>
/// Asynchronously copies a <see cref="Borehole"/>.
/// </summary>
Expand Down Expand Up @@ -179,7 +246,7 @@
{
// Include FieldMeasurementResults and HydrotestResults separately since Entity Framework does not support casting in an Include statement
var fieldMeasurements = borehole.Observations.OfType<FieldMeasurement>().ToList();
#pragma warning disable CS8603
#pragma warning disable CS8603
// Cannot include null test for fieldMeasurementResults and hydrotestResults since they are not yet loaded
// if there are no fieldMeasurementResults of hydrotestResults the LoadAsync method will be called but have no effect
foreach (var fieldMeasurement in fieldMeasurements)
Expand All @@ -193,12 +260,12 @@
var hydrotests = borehole.Observations.OfType<Hydrotest>().ToList();
foreach (var hydrotest in hydrotests)
{
await Context.Entry(hydrotest)
.Collection(h => h.HydrotestResults)
.LoadAsync()
.ConfigureAwait(false);
await Context.Entry(hydrotest)
.Collection(h => h.HydrotestResults)
.LoadAsync()
.ConfigureAwait(false);
}
#pragma warning restore CS8603
#pragma warning restore CS8603
}

// Set ids of copied entities to zero. Entities with an id of zero are added as new entities to the DB.
Expand Down
2 changes: 1 addition & 1 deletion src/api/Controllers/StratigraphyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ public async Task<ActionResult<int>> AddBedrockLayerAsync([Required] int id)
StratigraphyId = stratigraphy.Id,
FromDepth = borehole.TopBedrockFreshMd.Value,
LithologyTopBedrockId = borehole.LithologyTopBedrockId,
LithostratigraphyId = borehole.LithostratigraphyId,
LithostratigraphyId = borehole.LithostratigraphyTopBedrockId,
IsLast = false,
};

Expand Down
10 changes: 5 additions & 5 deletions src/api/Controllers/UploadController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -520,22 +520,22 @@ public CsvImportBoreholeMap()
Map(m => m.RestrictionId).Optional();
Map(m => m.RestrictionUntil).Optional();
Map(m => m.NationalInterest).Optional();
Map(m => m.AlternateName).Optional();
Map(m => m.Name).Optional();
Map(m => m.LocationPrecisionId).Optional();
Map(m => m.ElevationPrecisionId).Optional();
Map(m => m.ProjectName).Optional();
Map(m => m.PurposeId).Optional();
Map(m => m.StatusId).Optional();
Map(m => m.QtDepthId).Optional();
Map(m => m.DepthPrecisionId).Optional();
Map(m => m.TopBedrockFreshMd).Optional();
Map(m => m.TopBedrockWeatheredMd).Optional();
Map(m => m.HasGroundwater).Optional();
Map(m => m.Remarks).Optional();
Map(m => m.LithologyTopBedrockId).Optional();
Map(m => m.LithostratigraphyId).Optional();
Map(m => m.ChronostratigraphyId).Optional();
Map(m => m.LithostratigraphyTopBedrockId).Optional();
Map(m => m.ChronostratigraphyTopBedrockId).Optional();
Map(m => m.ReferenceElevation).Optional();
Map(m => m.QtReferenceElevationId).Optional();
Map(m => m.ReferenceElevationPrecisionId).Optional();
Map(m => m.ReferenceElevationTypeId).Optional();
Map(m => m.LocationX).Optional();
Map(m => m.LocationY).Optional();
Expand Down
34 changes: 17 additions & 17 deletions src/api/Models/Borehole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public class Borehole : IChangeTracking, IIdentifyable
/// Gets or sets the <see cref="Borehole"/>'s alternate name.
/// </summary>
[Column("alternate_name_bho")]
public string? AlternateName { get; set; }
public string? Name { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s location precision.
Expand Down Expand Up @@ -253,15 +253,15 @@ public class Borehole : IChangeTracking, IIdentifyable
public Codelist? Status { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s QtDepth id.
/// Gets or sets the <see cref="Borehole"/>'s Depth presicion id.
/// </summary>
[Column("qt_depth_id_cli")]
public int? QtDepthId { get; set; }
public int? DepthPrecisionId { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s QtDepth.
/// Gets or sets the <see cref="Borehole"/>'s Depth presicion.
/// </summary>
public Codelist? QtDepth { get; set; }
public Codelist? DepthPrecision { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s top bedrock.
Expand Down Expand Up @@ -306,26 +306,26 @@ public class Borehole : IChangeTracking, IIdentifyable
public Codelist? LithologyTopBedrock { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s lithostratigraphy id.
/// Gets or sets the <see cref="Borehole"/>'s lithostratigraphy top bedrock id.
/// </summary>
[Column("lithostrat_id_cli")]
public int? LithostratigraphyId { get; set; }
public int? LithostratigraphyTopBedrockId { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s lithostratigraphy.
/// Gets or sets the <see cref="Borehole"/>'s lithostratigraphy top bedrock.
/// </summary>
public Codelist? Lithostratigraphy { get; set; }
public Codelist? LithostratigraphyTopBedrock { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s chronostratigraphy id.
/// Gets or sets the <see cref="Borehole"/>'s chronostratigraphy top bedrock id.
/// </summary>
[Column("chronostrat_id_cli")]
public int? ChronostratigraphyId { get; set; }
public int? ChronostratigraphyTopBedrockId { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s chronostratigraphy.
/// Gets or sets the <see cref="Borehole"/>'s chronostratigraphy top bedrock.
/// </summary>
public Codelist? Chronostratigraphy { get; set; }
public Codelist? ChronostratigraphyTopBedrock { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s reference elevation.
Expand All @@ -334,15 +334,15 @@ public class Borehole : IChangeTracking, IIdentifyable
public double? ReferenceElevation { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s Qt reference elevation id.
/// Gets or sets the <see cref="Borehole"/>'s reference elevation precision id.
/// </summary>
[Column("qt_reference_elevation_id_cli")]
public int? QtReferenceElevationId { get; set; }
public int? ReferenceElevationPrecisionId { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s Qt reference elevation.
/// Gets or sets the <see cref="Borehole"/>'s reference elevation precision.
/// </summary>
public Codelist? QtReferenceElevation { get; set; }
public Codelist? ReferenceElevationPrecision { get; set; }

/// <summary>
/// Gets or sets the <see cref="Borehole"/>'s reference elevation type id.
MiraGeowerkstatt marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Loading
Loading