Skip to content

Allow custom FeedGeneratorService implementations #44

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ export type NotificationHeaderModel = {

export type ProductFeedSettingReadModelReadable = {
id: string;
feedType: ProductFeedType;
readonly feedTypeName: string;
feedGeneratorId: string;
feedName: string;
feedDescription: string;
storeId: string;
Expand Down Expand Up @@ -45,7 +44,7 @@ export type ProductFeedSettingReadModelWritable = {
export type ProductFeedSettingWriteModel = {
id?: string | null;
feedRelativePath: string;
feedType: ProductFeedType;
feedGeneratorId: string;
feedName: string;
feedDescription: string;
storeId: string;
Expand All @@ -56,8 +55,6 @@ export type ProductFeedSettingWriteModel = {
includeTaxInPrice: boolean;
};

export type ProductFeedType = 'GoogleMerchantCenter';

export type PropertyAndNodeMapItem = {
propertyAlias: string;
nodeName: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,49 +97,22 @@ export class UcpfDetailsWorkspaceContext
return this.#model.getValue();
}

protected resetState(): void {
protected resetState(): void {
super.resetState();

const availableFeedTypes = this.#feedTypes.getValue();

this.#unique.setValue('');
this.#model.setValue({
feedDescription: '',
storeId: this.#store!.id,
feedName: '',
feedGeneratorId: availableFeedTypes.length > 0 ? availableFeedTypes[0] : '',
feedRelativePath: '',
productChildVariantTypeIds: [],
productDocumentTypeIds: [],
includeTaxInPrice: true,
propertyNameMappings: [
{
uiId: nanoid(),
nodeName: 'g:id',
propertyAlias: 'sku',
valueExtractorId: 'DefaultSingleValuePropertyExtractor',
},
{
uiId: nanoid(),
nodeName: 'g:title',
propertyAlias: 'Name',
valueExtractorId: 'DefaultSingleValuePropertyExtractor',
},
{
uiId: nanoid(),
nodeName: 'g:availability',
propertyAlias: 'stock',
valueExtractorId: 'DefaultGoogleAvailabilityValueExtractor',
},
{
uiId: nanoid(),
nodeName: 'g:image_link',
propertyAlias: 'image',
valueExtractorId: 'DefaultMediaPickerPropertyValueExtractor',
},
{
uiId: nanoid(),
nodeName: 'g:image_link',
propertyAlias: 'images',
valueExtractorId: 'DefaultMultipleMediaPickerPropertyValueExtractor',
},
],
propertyNameMappings: [],
} as FeProductFeedSettingWriteModel);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ export type FeProductFeedSettingWriteModel = Omit<ProductFeedSettingWriteModel,
id?: string
propertyNameMappings: Array<FePropertyAndNodeMapDetails>
productRootId?: string,
feedType?: 'GoogleMerchantCenter'
feedGeneratorId: string
}

export type FePropertyAndNodeMapDetails = PropertyAndNodeMapItem & {
uiId: string
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ export class UcpfDetailsWorkspaceViewElement

this.observe(this.#workspaceContext.model, (model) => {
this._model = model;
this._feedTypes = this.#markSelectedOption(this._feedTypes, model!.feedType);
this._feedTypes = this.#markSelectedOption(this._feedTypes, model!.feedGeneratorId);
});

this.observe(this.#workspaceContext.feedTypeOptions, (feedTypes) => {
this._feedTypes = this.#markSelectedOption(feedTypes, this._model?.feedType);
this._feedTypes = this.#markSelectedOption(feedTypes, this._model?.feedGeneratorId);
});

this.observe(this.#workspaceContext.propertyValueExtractorOptions, (options) => {
Expand Down Expand Up @@ -188,7 +188,7 @@ export class UcpfDetailsWorkspaceViewElement
label=${this.localize.term('ucProductFeeds_propFeedTypeLabel')}
placeholder=${`-- ${this.localize.term('ucPlaceholders_selectAnItem')} --`}
slot='editor'
name='feedType'
name='feedGeneratorId'
.options=${this._feedTypes}
@change=${this.#onSelectElementChange}>
</uui-select>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Application
{
public enum FeedFormat
{
Unknown,
Xml,
Json
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Application
{
public interface IProductFeedGeneratorFactory
{
IProductFeedGeneratorService GetGenerator(ProductFeedType feedType);
IProductFeedGeneratorService GetGenerator(string feedGeneratorId);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
using System.Text.Json;
using System.Xml;
using Umbraco.Commerce.ProductFeeds.Core.Features.FeedSettings.Application;

namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Application
{
public interface IProductFeedGeneratorService
{
Task<XmlDocument> GenerateFeedAsync(ProductFeedSettingReadModel feedSetting);
/// <summary>
/// Returns the value extractor id. Must be unique.
/// </summary>
public string Id { get; }

/// <summary>
/// Returns a user friendly name of the value extractor.
/// </summary>
public string DisplayName { get; }

/// <summary>
/// Returns the feed format that this generator can generate.
/// </summary>
public FeedFormat Format { get; }
Task<XmlDocument> GenerateXmlFeedAsync(ProductFeedSettingReadModel feedSetting) => throw new NotImplementedException();
Task<JsonDocument> GenerateJsonFeedAsync(ProductFeedSettingReadModel feedSetting) => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Umbraco.Cms.Core.Composing;
using Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Application;

namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Implementations
{
public class FeedGeneratorCollection : BuilderCollectionBase<IProductFeedGeneratorService>
{
public FeedGeneratorCollection(Func<IEnumerable<IProductFeedGeneratorService>> items) : base(items)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Composing;
using Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Application;

namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Implementations
{
public class FeedGeneratorCollectionBuilder : OrderedCollectionBuilderBase<FeedGeneratorCollectionBuilder, FeedGeneratorCollection, IProductFeedGeneratorService>
{
protected override FeedGeneratorCollectionBuilder This => this;
protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Scoped;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Xml;
using Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Application;
using Umbraco.Commerce.ProductFeeds.Core.Features.FeedSettings.Application;
using Umbraco.Commerce.ProductFeeds.Core.Features.PropertyValueExtractors.Application;

namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Implementations
{
public abstract class FeedGeneratorServiceBase : IProductFeedGeneratorService
{

protected FeedGeneratorServiceBase(
ISingleValuePropertyExtractorFactory singleValuePropertyExtractorFactory,
IMultipleValuePropertyExtractorFactory multipleValuePropertyExtractorFactory)
{
SingleValuePropertyExtractorFactory = singleValuePropertyExtractorFactory;
MultipleValuePropertyExtractorFactory = multipleValuePropertyExtractorFactory;
}

public abstract string Id { get; }
public abstract string DisplayName { get; }
public abstract FeedFormat Format { get; }
protected ISingleValuePropertyExtractorFactory SingleValuePropertyExtractorFactory { get; }
protected IMultipleValuePropertyExtractorFactory MultipleValuePropertyExtractorFactory { get; }

public virtual Task<XmlDocument> GenerateXmlFeedAsync(ProductFeedSettingReadModel feedSetting)
{
throw new NotImplementedException("XML feed generation is not implemented.");
}
public virtual Task<JsonDocument> GenerateJsonFeedAsync(ProductFeedSettingReadModel feedSetting)
{
throw new NotImplementedException("JSON feed generation is not implemented.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Implementat
/// <summary>
/// This is the feed generator that follows Google Merchant Center's standard.
/// </summary>
public class GoogleMerchantCenterFeedService : IProductFeedGeneratorService
public class GoogleMerchantCenterFeedService : FeedGeneratorServiceBase
{
private const string GoogleXmlNamespaceUri = "http://base.google.com/ns/1.0";

Expand All @@ -33,23 +33,26 @@ public class GoogleMerchantCenterFeedService : IProductFeedGeneratorService
private readonly ICurrencyService _currencyService;
private readonly IProductQueryService _productQueryService;
private readonly IUmbracoCommerceApi _commerceApi;
private readonly ISingleValuePropertyExtractorFactory _singleValuePropertyExtractorFactory;
private readonly IMultipleValuePropertyExtractorFactory _multipleValuePropertyExtractorFactory;

public override string Id => "GoogleMerchantCenter";

public override string DisplayName => "Google Merchant Center Feed";

public override FeedFormat Format => FeedFormat.Xml;

public GoogleMerchantCenterFeedService(
ILogger<GoogleMerchantCenterFeedService> logger,
ICurrencyService currencyService,
IProductQueryService productQueryService,
IUmbracoCommerceApi commerceApi,
ISingleValuePropertyExtractorFactory singleValuePropertyExtractor,
ISingleValuePropertyExtractorFactory singleValuePropertyExtractorFactory,
IMultipleValuePropertyExtractorFactory multipleValuePropertyExtractorFactory)
: base(singleValuePropertyExtractorFactory, multipleValuePropertyExtractorFactory)
{
_logger = logger;
_currencyService = currencyService;
_productQueryService = productQueryService;
_commerceApi = commerceApi;
_singleValuePropertyExtractorFactory = singleValuePropertyExtractor;
_multipleValuePropertyExtractorFactory = multipleValuePropertyExtractorFactory;
}

/// <summary>
Expand All @@ -58,7 +61,7 @@ public GoogleMerchantCenterFeedService(
/// <param name="feedSetting"></param>
/// <returns></returns>
/// <exception cref="IdPropertyNodeMappingNotFoundException"></exception>
public async Task<XmlDocument> GenerateFeedAsync(ProductFeedSettingReadModel feedSetting)
public override async Task<XmlDocument> GenerateXmlFeedAsync(ProductFeedSettingReadModel feedSetting)
{
ArgumentNullException.ThrowIfNull(feedSetting, nameof(feedSetting));

Expand Down Expand Up @@ -157,13 +160,13 @@ private async Task<XmlElement> NewItemNodeAsync(ProductFeedSettingReadModel feed
// add custom properties
foreach (PropertyAndNodeMapItem map in feedSetting.PropertyNameMappings)
{
if (_singleValuePropertyExtractorFactory.TryGetExtractor(map.ValueExtractorId, out ISingleValuePropertyExtractor? singleValueExtractor)
if (SingleValuePropertyExtractorFactory.TryGetExtractor(map.ValueExtractorId, out ISingleValuePropertyExtractor? singleValueExtractor)
&& singleValueExtractor != null)
{
string propValue = singleValueExtractor.Extract(variant, map.PropertyAlias, mainProduct);
itemNode.AddChild(map.NodeName, propValue, GoogleXmlNamespaceUri);
}
else if (_multipleValuePropertyExtractorFactory.TryGetExtractor(map.ValueExtractorId!, out IMultipleValuePropertyExtractor? multipleValueExtractor)
else if (MultipleValuePropertyExtractorFactory.TryGetExtractor(map.ValueExtractorId!, out IMultipleValuePropertyExtractor? multipleValueExtractor)
&& multipleValueExtractor != null)
{
var values = multipleValueExtractor.Extract(variant, map.PropertyAlias, mainProduct).ToList();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Application;
using Umbraco.Commerce.ProductFeeds.Core.Features.FeedSettings.Application;

namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedGenerators.Implementations
{
public class ProductFeedGeneratorFactory : IProductFeedGeneratorFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly FeedGeneratorCollection _feedGenerators;

public ProductFeedGeneratorFactory(IServiceProvider serviceProvider)
public ProductFeedGeneratorFactory(FeedGeneratorCollection feedGenerators)
{
_serviceProvider = serviceProvider;
_feedGenerators = feedGenerators;
}

public IProductFeedGeneratorService GetGenerator(ProductFeedType feedType)
public IProductFeedGeneratorService GetGenerator(string feedGeneratorId)
{
switch (feedType)
{
case ProductFeedType.GoogleMerchantCenter:
return _serviceProvider.GetRequiredService<GoogleMerchantCenterFeedService>();
IProductFeedGeneratorService? feedGenerator = _feedGenerators.FirstOrDefault(p => p.Id == feedGeneratorId);

default:
throw new InvalidOperationException($"Invalid feed type: {feedType}");
if (feedGenerator == null)
{
throw new InvalidOperationException($"Invalid feed id: {feedGeneratorId}");
}

return feedGenerator;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ public class ProductFeedSettingReadModel
{
public Guid Id { get; set; }

public ProductFeedType FeedType { get; set; }

public string FeedTypeName => FeedType.GetDescription();
public required string FeedGeneratorId { get; set; }

public required string FeedName { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Runtime.Serialization;

namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedSettings.Application
{
public class ProductFeedSettingWriteModel
Expand All @@ -6,7 +8,7 @@ public class ProductFeedSettingWriteModel

public required string FeedRelativePath { get; set; }

public ProductFeedType? FeedType { get; set; }
public required string FeedGeneratorId { get; set; }

public required string FeedName { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public ProductFeedSettingWriteModelValidator()
RuleFor(x => x.FeedName).NotEmpty().WithName("Feed Name");
RuleFor(x => x.FeedRelativePath).NotEmpty().WithName("Feed Relative Path");
RuleFor(x => x.FeedDescription).MaximumLength(MaximumStringLength).WithName("Feed Description");
RuleFor(x => x.FeedType).NotEmpty().WithName("Feed Type");
RuleFor(x => x.FeedGeneratorId).NotEmpty().WithName("Feed Generator Id");
RuleFor(x => x.StoreId).NotEmpty().WithName("Umbraco Commerce Store");
RuleFor(x => x.ProductRootId).NotEmpty().WithName("Product Root");
RuleFor(x => x.ProductDocumentTypeIds).NotEmpty().WithName("Product Document Types");
Expand Down

This file was deleted.

Loading