Skip to content

Commit

Permalink
PSP-9452: Split map search for PID PIN (#4480)
Browse files Browse the repository at this point in the history
* Lint fixes, remove dead code

* Split map search for PID PIN

* Test updates

* Update backend filter models

* Update EF queries

* Test updates

* Support adding DB views to PIMS testing DB context

* Test updates

* Code cleanup

* PR feedback

* Add proxy microservice to "make infra" command
  • Loading branch information
asanchezr authored Nov 19, 2024
1 parent c083c47 commit 32ee93a
Show file tree
Hide file tree
Showing 24 changed files with 964 additions and 277 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ refresh: | down build up ## Recreates local docker environment
.PHONY: infra
infra: ## Starts infrastructure containers (e.g. database, geoserver). Useful for local debugging
@echo "$(P) Starting up infrastructure containers..."
@"$(MAKE)" start n="database geoserver grafana prometheus"
@"$(MAKE)" start n="database geoserver grafana prometheus proxy"

start: ## Starts the local containers (n=service name)
@echo "$(P) Starting client and server containers..."
Expand Down
20 changes: 14 additions & 6 deletions source/backend/api/Areas/Property/Models/PropertyFilterModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ public class PropertyFilterModel : PageFilter
public string Address { get; set; }

/// <summary>
/// get/set - The pin or pid property identifier.
/// get/set - The pid property identifier.
/// </summary>
public string PinOrPid { get; set; }
public string Pid { get; set; }

/// <summary>
/// get/set - The pin property identifier.
/// </summary>
public string Pin { get; set; }

/// <summary>
/// get/set - The property plan number.
Expand Down Expand Up @@ -87,7 +92,8 @@ public PropertyFilterModel(Dictionary<string, Microsoft.Extensions.Primitives.St
}
Sort = tempSort.ToArray();

PinOrPid = filter.GetStringValue(nameof(PinOrPid));
Pid = filter.GetStringValue(nameof(Pid));
Pin = filter.GetStringValue(nameof(Pin));
Address = filter.GetStringValue(nameof(Address));
PlanNumber = filter.GetStringValue(nameof(PlanNumber));
Historical = filter.GetStringValue(nameof(Historical));
Expand All @@ -98,7 +104,7 @@ public PropertyFilterModel(Dictionary<string, Microsoft.Extensions.Primitives.St
#region Methods

/// <summary>
/// Convert to a ParcelFilter.
/// Convert to a PropertyFilter.
/// </summary>
/// <param name="model"></param>
public static explicit operator PropertyFilter(PropertyFilterModel model)
Expand All @@ -109,7 +115,8 @@ public static explicit operator PropertyFilter(PropertyFilterModel model)
Quantity = model.Quantity,
Sort = model.Sort,

PinOrPid = model.PinOrPid,
Pid = model.Pid,
Pin = model.Pin,
Address = model.Address,
PlanNumber = model.PlanNumber,
Historical = model.Historical,
Expand All @@ -126,7 +133,8 @@ public static explicit operator PropertyFilter(PropertyFilterModel model)
public override bool IsValid()
{
return base.IsValid()
|| !string.IsNullOrWhiteSpace(PinOrPid)
|| !string.IsNullOrWhiteSpace(Pid)
|| !string.IsNullOrWhiteSpace(Pin)
|| !string.IsNullOrWhiteSpace(Historical)
|| !string.IsNullOrWhiteSpace(Address);
}
Expand Down
31 changes: 15 additions & 16 deletions source/backend/dal/Helpers/Extensions/PropertyViewExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using System;
using System.Linq;
using System.Security.Claims;
Expand All @@ -7,8 +6,6 @@
using Microsoft.EntityFrameworkCore;
using Pims.Core.Extensions;
using Pims.Dal.Entities;
using Pims.Core.Security;
using Entity = Pims.Dal.Entities;

namespace Pims.Dal.Helpers.Extensions
{
Expand All @@ -25,18 +22,18 @@ public static class PropertyViewExtensions
/// <param name="user"></param>
/// <param name="filter"></param>
/// <returns></returns>
public static IQueryable<Entity.PimsPropertyVw> GeneratePropertyQuery(this PimsContext context, ClaimsPrincipal user, Entity.Models.PropertyFilter filter)
public static IQueryable<PimsPropertyVw> GeneratePropertyQuery(this PimsContext context, ClaimsPrincipal user, Entities.Models.PropertyFilter filter)
{
filter.ThrowIfNull(nameof(filter));
filter.ThrowIfNull(nameof(user));
ArgumentNullException.ThrowIfNull(filter, nameof(filter));
ArgumentNullException.ThrowIfNull(user, nameof(user));

var query = context.PimsPropertyVws
.AsNoTracking();

var predicate = GenerateCommonPropertyQuery(context, user, filter);
query = query.Where(predicate);

if (filter.Sort?.Any() == true)
if (filter.Sort is not null && filter.Sort.Length > 0)
{
query = query.OrderByProperty(true, filter.Sort);
}
Expand All @@ -54,22 +51,24 @@ public static class PropertyViewExtensions
/// <param name="user"></param>
/// <param name="filter"></param>
/// <returns></returns>
private static ExpressionStarter<PimsPropertyVw> GenerateCommonPropertyQuery(PimsContext context, ClaimsPrincipal user, Entity.Models.PropertyFilter filter)
private static ExpressionStarter<PimsPropertyVw> GenerateCommonPropertyQuery(PimsContext context, ClaimsPrincipal user, Entities.Models.PropertyFilter filter)
{
filter.ThrowIfNull(nameof(filter));
filter.ThrowIfNull(nameof(user));

// Check if user has the ability to view sensitive properties.
var viewSensitive = user.HasPermission(Permissions.SensitiveView);
ArgumentNullException.ThrowIfNull(filter, nameof(filter));
ArgumentNullException.ThrowIfNull(user, nameof(user));

var predicateBuilder = PredicateBuilder.New<PimsPropertyVw>(p => true);

if (!string.IsNullOrWhiteSpace(filter.PinOrPid))
if (!string.IsNullOrWhiteSpace(filter.Pid))
{
// note - 2 part search required. all matches found by removing leading 0's, then matches filtered in subsequent step. This is because EF core does not support an lpad method.
Regex nonInteger = new Regex("[^\\d]");
var formattedPidPin = Convert.ToInt32(nonInteger.Replace(filter.PinOrPid, string.Empty)).ToString();
predicateBuilder = predicateBuilder.And(p => EF.Functions.Like(p.Pid.ToString(), $"%{formattedPidPin}%") || EF.Functions.Like(p.Pin.ToString(), $"%{formattedPidPin}%"));
var formattedPid = Convert.ToInt32(nonInteger.Replace(filter.Pid, string.Empty)).ToString();
predicateBuilder = predicateBuilder.And(p => EF.Functions.Like(p.Pid.ToString(), $"%{formattedPid}%"));
}

if (!string.IsNullOrWhiteSpace(filter.Pin))
{
predicateBuilder = predicateBuilder.And(p => EF.Functions.Like(p.Pin.ToString(), $"%{filter.Pin}%"));
}
if (!string.IsNullOrWhiteSpace(filter.Address))
{
Expand Down
23 changes: 15 additions & 8 deletions source/backend/dal/Models/PropertyFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ public class PropertyFilter : PageFilter
#region Properties

/// <summary>
/// get/set - The pin or pid property.
/// get/set - The pid property identifier.
/// </summary>
public string PinOrPid { get; set; }
public string Pid { get; set; }

/// <summary>
/// get/set - The pin property identifier.
/// </summary>
public string Pin { get; set; }

/// <summary>
/// get/set - The property address.
Expand Down Expand Up @@ -58,10 +63,11 @@ public PropertyFilter(Dictionary<string, Microsoft.Extensions.Primitives.StringV
// We want case-insensitive query parameter properties.
var filter = new Dictionary<string, Microsoft.Extensions.Primitives.StringValues>(query, StringComparer.OrdinalIgnoreCase);

this.Address = filter.GetStringValue(nameof(this.Address));
this.PinOrPid = filter.GetStringValue(nameof(this.PinOrPid));
this.PlanNumber = filter.GetStringValue(nameof(this.PlanNumber));
this.Ownership = filter.GetStringArrayValue(nameof(this.Ownership));
Address = filter.GetStringValue(nameof(Address));
Pid = filter.GetStringValue(nameof(Pid));
Pin = filter.GetStringValue(nameof(Pin));
PlanNumber = filter.GetStringValue(nameof(PlanNumber));
Ownership = filter.GetStringArrayValue(nameof(Ownership));
}
#endregion

Expand All @@ -74,8 +80,9 @@ public PropertyFilter(Dictionary<string, Microsoft.Extensions.Primitives.StringV
public override bool IsValid()
{
return base.IsValid()
|| !string.IsNullOrWhiteSpace(this.PinOrPid)
|| !string.IsNullOrWhiteSpace(this.Address);
|| !string.IsNullOrWhiteSpace(Pid)
|| !string.IsNullOrWhiteSpace(Pin)
|| !string.IsNullOrWhiteSpace(Address);
}
#endregion
}
Expand Down
12 changes: 6 additions & 6 deletions source/backend/dal/Repositories/PropertyRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
using Pims.Core.Exceptions;
using Pims.Core.Extensions;
using Pims.Core.Helpers;
using Pims.Core.Security;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
using Pims.Dal.Helpers.Extensions;
using Pims.Core.Security;

namespace Pims.Dal.Repositories
{
Expand Down Expand Up @@ -56,25 +56,25 @@ public int Count()
/// <returns></returns>
public Paged<PimsPropertyVw> GetPage(PropertyFilter filter)
{
this.User.ThrowIfNotAuthorized(Permissions.PropertyView);
User.ThrowIfNotAuthorized(Permissions.PropertyView);
filter.ThrowIfNull(nameof(filter));
if (!filter.IsValid())
{
throw new ArgumentException("Argument must have a valid filter", nameof(filter));
}

var skip = (filter.Page - 1) * filter.Quantity;
var query = Context.GeneratePropertyQuery(this.User, filter);
var query = Context.GeneratePropertyQuery(User, filter);
var items = query
.Skip(skip)
.Take(filter.Quantity)
.ToArray();

if (!string.IsNullOrWhiteSpace(filter.PinOrPid))
if (!string.IsNullOrWhiteSpace(filter.Pid))
{
Regex nonInteger = new Regex("[^\\d]");
var formattedPidPin = nonInteger.Replace(filter.PinOrPid, string.Empty);
items = items.Where(i => i.Pid.ToString().PadLeft(9, '0').Contains(formattedPidPin) || i.Pin.ToString().Contains(formattedPidPin)).ToArray();
var formattedPid = nonInteger.Replace(filter.Pid, string.Empty);
items = items.Where(i => i.Pid.ToString().PadLeft(9, '0').Contains(formattedPid)).ToArray();
}

return new Paged<PimsPropertyVw>(items, filter.Page, filter.Quantity, query.Count());
Expand Down
2 changes: 1 addition & 1 deletion source/backend/tests/core/DatabaseHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public static PimsContext CreatePimsContext(this TestHelper helper, string dbNam
var serializerOptions = new Mock<IOptions<JsonSerializerOptions>>();
helper.AddSingleton(serializerOptions);

var context = new PimsContext(options, contextAccessor.Object, serializerOptions.Object);
PimsContext context = new PimsTestContext(options, contextAccessor.Object, serializerOptions.Object);

if (ensureDeleted)
{
Expand Down
11 changes: 11 additions & 0 deletions source/backend/tests/core/Entities/PropertyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public static PimsPropertyVw CreatePropertyView(int pid, int? pin = null, PimsPr
var property = new PimsPropertyVw()
{
PropertyId = pid,
Pid = pid,
Pin = pin,
IsRetired = false,
PropertyTypeCode = type.PropertyTypeCode,
Expand Down Expand Up @@ -126,9 +127,19 @@ public static PimsProperty CreateProperty(this PimsContext context, int pid, int
public static PimsPropertyVw CreatePropertyView(this PimsContext context, int pid, int? pin = null, PimsPropertyType type = null, PimsAddress address = null, PimsPropertyTenureType tenure = null, PimsAreaUnitType areaUnit = null, PimsDataSourceType dataSource = null, PimsPropertyStatusType status = null, Geometry location = null, bool isRetired = false)
{
type ??= context.PimsPropertyTypes.FirstOrDefault() ?? throw new InvalidOperationException("Unable to find a property type.");
tenure ??= context.PimsPropertyTenureTypes.FirstOrDefault() ?? throw new InvalidOperationException("Unable to find a property tenure type.");
status ??= context.PimsPropertyStatusTypes.FirstOrDefault() ?? throw new InvalidOperationException("Unable to find a property status type.");
dataSource ??= context.PimsDataSourceTypes.FirstOrDefault() ?? throw new InvalidOperationException("Unable to find a property data source type.");
areaUnit ??= context.PimsAreaUnitTypes.FirstOrDefault() ?? throw new InvalidOperationException("Unable to find a property area unit type.");
address ??= context.CreateAddress(pid, "12342 Test Street");
var property = CreatePropertyView(pid, pin, type, address);
property.Location = location;
property.IsRetired = isRetired;
property.PropertyStatusTypeCode = status.PropertyStatusTypeCode;
property.PropertyDataSourceTypeCode = dataSource.DataSourceTypeCode;
property.PropertyTenureTypeCode = tenure.PropertyTenureTypeCode;
property.PropertyAreaUnitTypeCode = areaUnit.AreaUnitTypeCode;

context.PimsPropertyVws.Add(property);
return property;
}
Expand Down
45 changes: 45 additions & 0 deletions source/backend/tests/core/PimsTestContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Pims.Dal;
using Pims.Dal.Entities;

namespace Pims.Core.Test
{
/// <summary>
/// PimsTestContext class, provides a data context to manage the datasource for the PIMS application (specific to UNIT TESTS).
/// </summary>
public class PimsTestContext : PimsContext
{
/// <summary>
/// Initializes a new instance of the <see cref="PimsTestContext"/> class.
/// </summary>
public PimsTestContext()
: base()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="PimsTestContext"/> class.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="httpContextAccessor">Provides access to the current IHttpContextAccessor.HttpContext, if one is available.</param>
/// <param name="serializerOptions">The serializer options.</param>
public PimsTestContext(DbContextOptions<PimsContext> options, IHttpContextAccessor httpContextAccessor = null, IOptions<JsonSerializerOptions> serializerOptions = null)
: base(options, httpContextAccessor, serializerOptions)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

// This is needed so unit tests for DB Views work with in-memory DB
modelBuilder.Entity<PimsPropertyVw>(entity =>
{
entity.HasKey(x => x.PropertyId);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class SearchControllerTest
{
new object [] { new PropertyFilterModel() },
new object [] { new PropertyFilterModel() { Address = "Address" } },
new object [] { new PropertyFilterModel() { PinOrPid = "999999" } },
new object [] { new PropertyFilterModel() { Pid = "999999" } },
};

public readonly static IEnumerable<object[]> PropertyQueryFilters = new List<object[]>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class PropertyControllerTest
{
new object [] { new PropertyFilterModel() },
new object [] { new PropertyFilterModel() { Address = "Address" } },
new object [] { new PropertyFilterModel() { PinOrPid = "999999" } },
new object [] { new PropertyFilterModel() { Pid = "999999" } },
};

public static IEnumerable<object[]> PropertyQueryFilters = new List<object[]>()
Expand Down
9 changes: 5 additions & 4 deletions source/backend/tests/unit/dal/Entities/PropertyFilterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,23 @@ public void PropertyFilter_Default_Constructor()
// Assert
filter.Page.Should().Be(1);
filter.Quantity.Should().Be(10);
filter.PinOrPid.Should().BeNull();
filter.Pid.Should().BeNull();
filter.Pin.Should().BeNull();
}

[Fact]
public void PropertyFilter_Constructor_03()
{
// Arrange
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery("?page=2&quantity=3&pinOrPid=323423&sort=one,two");
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery("?page=2&quantity=3&pid=323423&sort=one,two");

// Act
var filter = new PropertyFilter(query);

// Assert
filter.Page.Should().Be(2);
filter.Quantity.Should().Be(3);
filter.PinOrPid.Should().Be("323423");
filter.Pid.Should().Be("323423");
filter.Sort.Should().BeEquivalentTo(new[] { "one", "two" });
}

Expand All @@ -47,7 +48,7 @@ public void PropertyFilter_Constructor_03()
public void PropertyFilter_IsValid()
{
// Arrange
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery("?pinOrPid=323423");
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery("?pid=323423");
var filter = new PropertyFilter(query);

// Act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,6 @@ public class PropertyActivityRepositoryTest
{
private TestHelper _helper;

#region Data
public static IEnumerable<object[]> AllPropertyFilters =>
new List<object[]>
{
new object[] { new PropertyFilter() { PinOrPid = "111-111-111" }, 1 },
new object[] { new PropertyFilter() { PinOrPid = "111" }, 2 },
new object[] { new PropertyFilter() { Address = "12342 Test Street" }, 5 },
new object[] { new PropertyFilter() { Page = 1, Quantity = 10 }, 6 },
new object[] { new PropertyFilter(), 6 },
};
#endregion

public PropertyActivityRepositoryTest()
{
_helper = new TestHelper();
Expand Down
Loading

0 comments on commit 32ee93a

Please sign in to comment.