Skip to content

Commit

Permalink
VCST-2460: Refactor and enhance CategoryPropertyNameValidator (#762)
Browse files Browse the repository at this point in the history
feat: Refactored CategoryPropertyNameValidator to validate property name on whole Virto Commerce, instead of child categories only.  (#762)
feat: Added validation for a legacy property name that can contain special symbols.  (#762)
  • Loading branch information
OlegoO committed Dec 23, 2024
1 parent 6945bd9 commit 88809bb
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ public class CategoryPropertyValidationRequest
{
public string PropertyName { get; set; }
public string PropertyType { get; set; }
public PropertyValueType PropertyValueType { get; set; }

public string CategoryId { get; set; }
public string CatalogId { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,9 @@ public string[] CatalogIds

public IList<string> PropertyNames { get; set; }
public IList<string> PropertyTypes { get; set; }

public IList<PropertyValueType> PropertyValueTypes { get; set; }

public IList<PropertyValueType> ExcludedPropertyValueTypes { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ protected virtual IQueryable<PropertyEntity> BuildQuery(ICatalogRepository repos
: query.Where(x => criteria.PropertyTypes.Contains(x.TargetType));
}

if (!criteria.PropertyValueTypes.IsNullOrEmpty())
{
var propertyValueTypes = criteria.PropertyValueTypes.Cast<int>().ToList();

query = propertyValueTypes.Count == 1
? query.Where(x => x.PropertyValueType == propertyValueTypes.First())
: query.Where(x => propertyValueTypes.Contains(x.PropertyValueType));
}

if (!criteria.ExcludedPropertyValueTypes.IsNullOrEmpty())
{
var excludedPropertyValueTypes = criteria.ExcludedPropertyValueTypes.Cast<int>().ToList();

query = excludedPropertyValueTypes.Count == 1
? query.Where(x => x.PropertyValueType != excludedPropertyValueTypes.First())
: query.Where(x => !excludedPropertyValueTypes.Contains(x.PropertyValueType));
}

return query;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,101 +1,93 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;
using VirtoCommerce.CatalogModule.Core.Model;
using VirtoCommerce.CatalogModule.Core.Model.Search;
using VirtoCommerce.CatalogModule.Core.Search;
using VirtoCommerce.CatalogModule.Core.Services;
using VirtoCommerce.CatalogModule.Data.Search;
using VirtoCommerce.CatalogModule.Data.Services;
using VirtoCommerce.Platform.Core.Common;
using VirtoCommerce.Platform.Data.ExportImport;

namespace VirtoCommerce.CatalogModule.Data.Validation
{
public class CategoryPropertyNameValidator : AbstractValidator<CategoryPropertyValidationRequest>
{
private readonly ICategoryService _categoryService;
private readonly IPropertyService _propertyService;
private readonly Func<int, string, string, CategoryHierarchyIterator> _categoryHierarchyIteratorFactory;
private readonly IPropertySearchService _propertySearchService;

public CategoryPropertyNameValidator(
ICategoryService categoryService,
IPropertyService propertyService,
Func<int, string, string, CategoryHierarchyIterator> categoryHierarchyIteratorFactory)
IPropertySearchService propertySearchService)
{
_categoryService = categoryService;
_propertyService = propertyService;
_categoryHierarchyIteratorFactory = categoryHierarchyIteratorFactory;
_propertySearchService = propertySearchService;

AttachValidators();
}

private void AttachValidators()
{
RuleFor(r => r)
.CustomAsync(async (r, context, _) =>
{
var (isPropertyUniqueForHierarchy, categoryName) = await CheckPropertyUniquenessAsync(r);

if (!isPropertyUniqueForHierarchy)
{
context.AddFailure(new ValidationFailure(r.PropertyName, "duplicate-property")
{
CustomState = new
{
PropertyName = r.PropertyName,
CategoryName = categoryName,
},
});
}
});
.CustomAsync(async (r, context, _) =>
{
var (isPropertyUniqueForHierarchy, categoryName) = await CheckPropertyUniquenessAsync(r);

if (!isPropertyUniqueForHierarchy)
{
context.AddFailure(new ValidationFailure(r.PropertyName, "duplicate-property")
{
CustomState = new
{
PropertyName = r.PropertyName,
CategoryName = categoryName,
},
});
}
});
}

/// <summary>
/// Check the property uniqueness for the whole catalog
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
protected virtual async Task<(bool, string)> CheckPropertyUniquenessAsync(CategoryPropertyValidationRequest request)
{
var categoryIterator = _categoryHierarchyIteratorFactory(500, request.CatalogId, request.CategoryId);
Property existingProperty;
// Allow to create a new property with the same name and same type
var existingProperty = await FindProperty(request);

do
if (existingProperty != null)
{
var categoryIds = await categoryIterator.GetNextPageAsync();

if (categoryIds.IsNullOrEmpty())
{
// Category hierarchy iterator returns only child-relation category ids, excluding the category id itself
// so is required to load properties for requested category too
categoryIds = categoryIds.Append(request.CategoryId).ToImmutableArray();
}

var categories = await _categoryService.GetNoCloneAsync(categoryIds.ToList(), CategoryResponseGroup.WithProperties.ToString());

var properties = categories.SelectMany(x => x.Properties);

// If properties are empty and the requested Category missed
// it might meaning that catalog doesn't contains any categories and
if (string.IsNullOrEmpty(request.CategoryId) && !properties.Any())
{
properties = await _propertyService.GetAllCatalogPropertiesAsync(request.CatalogId);
}

var requiredPropertyType = EnumUtility.SafeParse(request.PropertyType, PropertyType.Category);

existingProperty = properties.FirstOrDefault(x => x.Name.EqualsInvariant(request.PropertyName) && x.Type.Equals(requiredPropertyType));

if (existingProperty != null)
{
break;
}
var categoryName = (await _categoryService.GetNoCloneAsync(existingProperty.CategoryId, CategoryResponseGroup.Info.ToString()))?.Name;
return (false, categoryName);
}

} while (categoryIterator.HasMoreResults);
return (true, null);
}

var categoryName = string.Empty;
private async Task<Property> FindProperty(CategoryPropertyValidationRequest request)
{
var propertyName = request.PropertyName;
// "Alcholic % Volume" == "Alcholic_Volume"
var azureFieldPropertyName = Regex.Replace(propertyName, @"\W", "_");

if (existingProperty != null)
var properties = await _propertySearchService.SearchPropertiesAsync(new PropertySearchCriteria
{
categoryName = (await _categoryService.GetNoCloneAsync(existingProperty.CategoryId, CategoryResponseGroup.Info.ToString()))?.Name;
}
PropertyTypes = [request.PropertyType],
PropertyNames = [propertyName, azureFieldPropertyName],
ExcludedPropertyValueTypes = [request.PropertyValueType],
Take = 1
});


return (existingProperty == null, categoryName);
return properties.Results.FirstOrDefault();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ angular.module('virtoCommerce.catalogModule')

return properties.validateCategoryPropertyName({
propertyName: value,
propertyValueType: blade.currentEntity.valueType,
propertyType: blade.origEntity.type,
categoryId: blade.origEntity.categoryId,
catalogId: blade.origEntity.catalogId
Expand Down
14 changes: 8 additions & 6 deletions src/VirtoCommerce.CatalogModule.Web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 88809bb

Please sign in to comment.