Skip to content

Commit

Permalink
Add csv export (#1725)
Browse files Browse the repository at this point in the history
  • Loading branch information
MiraGeowerkstatt authored Dec 16, 2024
2 parents 3dd6111 + 432e220 commit e423e08
Show file tree
Hide file tree
Showing 43 changed files with 475 additions and 238 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
- Added data extraction API.
- Added support to extract coordinates from a borehole attachment.
- Show ChangedAt and ChangedBy information in borehole detail header.
- Add JSON export for single and multiple boreholes.
- Add JSON and CSV export for single and multiple boreholes.
- The workgroup name is now displayed in the borehole location tab.
- Added new API endpoint to retrieve all boreholes.
- Add JSON import for boreholes.
Expand Down
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 @@ -159,7 +159,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 @@ -172,21 +172,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 @@ -203,7 +203,7 @@ public static void SeedData(this BdmsContext context)
.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.Observations, _ => new Collection<Observation>())
.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
64 changes: 62 additions & 2 deletions src/api/Controllers/BoreholeController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
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;

namespace BDMS.Controllers;

Expand Down Expand Up @@ -77,7 +80,7 @@ public async override Task<ActionResult<Borehole>> EditAsync(Borehole entity)
/// <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 @@ -117,6 +120,63 @@ public async Task<ActionResult<Borehole>> GetByIdAsync(int id)
return Ok(borehole);
}

/// <summary>
/// Exports the details of up to <see cref="MaxPageSize"></see> boreholes as a CSV file. Filters the boreholes based on the provided list of IDs.
/// </summary>
/// <param name="ids">The list of IDs for the boreholes to be exported.</param>
/// <returns>A CSV file containing the details specified boreholes.</returns>
[HttpGet("export-csv")]
[Authorize(Policy = PolicyNames.Viewer)]
public async Task<IActionResult> DownloadCsvAsync([FromQuery][MaxLength(MaxPageSize)] IEnumerable<int> 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");
}

/// <summary>
/// Asynchronously copies a <see cref="Borehole"/>.
/// </summary>
Expand Down Expand Up @@ -238,7 +298,7 @@ public async Task<ActionResult<int>> CopyAsync([Required] int id, [Required] int
borehole.Workflows.Add(new Workflow { Borehole = borehole, Role = Role.Editor, UserId = user.Id });

borehole.OriginalName += " (Copy)";
borehole.AlternateName += " (Copy)";
borehole.Name += " (Copy)";

var entityEntry = await Context.AddAsync(borehole).ConfigureAwait(false);
await Context.SaveChangesAsync().ConfigureAwait(false);
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 @@ -416,22 +416,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.
Expand Down
Loading

0 comments on commit e423e08

Please sign in to comment.