diff --git a/Apps.Magento/Actions/BlockActions.cs b/Apps.Magento/Actions/BlockActions.cs index f862303..1196b45 100644 --- a/Apps.Magento/Actions/BlockActions.cs +++ b/Apps.Magento/Actions/BlockActions.cs @@ -18,7 +18,7 @@ namespace Apps.Magento.Actions; [ActionList] public class BlockActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : AppInvocable(invocationContext) { - [Action("Get all blocks", Description = "Get all blocks")] + [Action("Search blocks", Description = "Retrieve all blocks that match the specified criteria")] public async Task GetAllBlocksAsync([ActionParameter] StoreViewOptionalIdentifier storeViewIdentifier, [ActionParameter] FilterBlockRequest filterRequest) { diff --git a/Apps.Magento/Actions/CategoryActions.cs b/Apps.Magento/Actions/CategoryActions.cs index f626222..2389273 100644 --- a/Apps.Magento/Actions/CategoryActions.cs +++ b/Apps.Magento/Actions/CategoryActions.cs @@ -12,7 +12,7 @@ namespace Apps.Magento.Actions; [ActionList] public class CategoryActions(InvocationContext invocationContext) : AppInvocable(invocationContext) { - [Action("Get all categories", Description = "Get all categories")] + [Action("Search categories", Description = "Retrieve all categories that match the specified criteria")] public async Task GetAllCategoriesAsync( [ActionParameter] StoreViewOptionalIdentifier storeViewIdentifier) { diff --git a/Apps.Magento/Actions/PageActions.cs b/Apps.Magento/Actions/PageActions.cs index 12b9877..ec8bd0d 100644 --- a/Apps.Magento/Actions/PageActions.cs +++ b/Apps.Magento/Actions/PageActions.cs @@ -18,7 +18,7 @@ namespace Apps.Magento.Actions; [ActionList] public class PageActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : AppInvocable(invocationContext) { - [Action("Get all pages", Description = "Get all pages")] + [Action("Search pages", Description = "Retrieve all pages that match the specified criteria")] public async Task GetAllPagesAsync([ActionParameter] StoreViewOptionalIdentifier storeViewIdentifier, [ActionParameter] FilterPageRequest filterRequest) { diff --git a/Apps.Magento/Actions/ProductActions.cs b/Apps.Magento/Actions/ProductActions.cs index 6679b85..bfd11bb 100644 --- a/Apps.Magento/Actions/ProductActions.cs +++ b/Apps.Magento/Actions/ProductActions.cs @@ -23,7 +23,7 @@ namespace Apps.Magento.Actions; public class ProductActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : AppInvocable(invocationContext) { - [Action("Get all products", Description = "Get all products")] + [Action("Search products", Description = "Retrieve all products that match the specified criteria")] public async Task GetAllProductsAsync( [ActionParameter] StoreViewOptionalIdentifier storeViewIdentifier, [ActionParameter] FilterProductRequest filterRequest) @@ -104,7 +104,7 @@ public async Task CreateProductAsync( [Action("Update product", Description = "Update product by specified SKU")] public async Task UpdateProductBySkuAsync( - [ActionParameter] StoreViewOptionalIdentifier storeViewIdentifier, + [ActionParameter] StoreViewWithAllOptionalIdentifier storeViewIdentifier, [ActionParameter] ProductIdentifier identifier, [ActionParameter] UpdateProductRequest updateProductRequest) { @@ -116,7 +116,16 @@ public async Task UpdateProductBySkuAsync( } } - var product = await GetProductBySkuAsync(storeViewIdentifier, identifier); + var product = await GetProductBySkuAsync(new StoreViewOptionalIdentifier { StoreView = storeViewIdentifier.StoreView == "all" ? "default" : storeViewIdentifier.StoreView }, identifier); + + if(updateProductRequest.CustomAttributeKeys != null && updateProductRequest.CustomAttributeValues != null) + { + if(updateProductRequest.CustomAttributeKeys.Count() != updateProductRequest.CustomAttributeValues.Count()) + { + throw new ArgumentException("Custom attribute keys and values count must be equal"); + } + } + var mergedCustomAttributes = updateProductRequest.CustomAttributeKeys != null && updateProductRequest.CustomAttributeValues != null ? updateProductRequest.CustomAttributeKeys.Zip(updateProductRequest.CustomAttributeValues, @@ -138,18 +147,22 @@ public async Task UpdateProductBySkuAsync( { category_links = new List() }, - custom_attributes = mergedCustomAttributes + custom_attributes = mergedCustomAttributes.Select(x => new + { + attribute_code = x.AttributeCode, + value = x.Value + }).ToList() } }; - + var request = new ApiRequest($"/rest/{storeViewIdentifier}/V1/products/{identifier.Sku}", Method.Put, Creds) .AddBody(body); return await Client.ExecuteWithErrorHandling(request); } - [Action("Update product as HTML", Description = "Update product by specified SKU with HTML content")] + [Action("Update product from HTML", Description = "Update product by specified SKU with HTML content")] public async Task UpdateProductBySkuAsHtmlAsync( - [ActionParameter] StoreViewOptionalIdentifier storeViewIdentifier, + [ActionParameter] StoreViewWithAllOptionalIdentifier storeViewIdentifier, [ActionParameter] UpdateProductAsHtmlRequest updateProductAsHtmlRequest) { var htmlStream = await fileManagementClient.DownloadAsync(updateProductAsHtmlRequest.File); @@ -161,7 +174,7 @@ public async Task UpdateProductBySkuAsHtmlAsync( var productSku = updateProductAsHtmlRequest.ProductSku ?? htmlModel.ResourceId ?? throw new ArgumentException( "Couldn't find product SKU in the HTML document. Please specify it manually in the optional input."); - var product = await GetProductBySkuAsync(storeViewIdentifier, new ProductIdentifier { Sku = productSku }); + var product = await GetProductBySkuAsync(new StoreViewOptionalIdentifier { StoreView = storeViewIdentifier.StoreView == "all" ? "default" : storeViewIdentifier.StoreView }, new ProductIdentifier { Sku = productSku }); foreach (var customAttribute in productModel.CustomAttributes) { @@ -220,11 +233,11 @@ public async Task UpdateProductBySkuAsHtmlAsync( } [Action("Delete product", Description = "Delete product by specified SKU")] - public async Task DeleteProductBySkuAsync([ActionParameter] StoreViewOptionalIdentifier storeViewIdentifier, - [ActionParameter] ProductIdentifier identifier) + public async Task DeleteProductBySkuAsync([ActionParameter] ProductIdentifier identifier) { + var endpoint = $"/rest/default/V1/products/{identifier.Sku}"; await Client.ExecuteWithErrorHandling( - new ApiRequest($"/rest/{storeViewIdentifier}/V1/products/{identifier.Sku}", Method.Delete, Creds)); + new ApiRequest(endpoint, Method.Delete, Creds)); } [Action("Add custom attribute", Description = "Add custom attribute to product by specified SKU")] diff --git a/Apps.Magento/Apps.Magento.csproj b/Apps.Magento/Apps.Magento.csproj index a7b16d7..07ee96f 100644 --- a/Apps.Magento/Apps.Magento.csproj +++ b/Apps.Magento/Apps.Magento.csproj @@ -6,7 +6,7 @@ enable Adobe Commerce - Magento [Beta] Magento is the leading platform for open commerce innovation. It’s designed to be flexible and scalable, able to support businesses of all sizes – from small startups to large enterprises. - 1.0.3 + 1.0.4 Apps.Magento Apps.Magento Apps.Magento diff --git a/Apps.Magento/Connections/ConnectionValidator.cs b/Apps.Magento/Connections/ConnectionValidator.cs index f5fcfe3..5fe0db5 100644 --- a/Apps.Magento/Connections/ConnectionValidator.cs +++ b/Apps.Magento/Connections/ConnectionValidator.cs @@ -16,7 +16,7 @@ public async ValueTask ValidateConnection( try { - await apiClient.ExecuteWithErrorHandling(new ApiRequest("/rest/default/V1/analytics/link", Method.Get, credentialsProviders)); + await apiClient.ExecuteWithErrorHandling(new ApiRequest("/rest/V1/analytics/link", Method.Get, credentialsProviders)); return new ConnectionValidationResponse { IsValid = true diff --git a/Apps.Magento/DataSources/StoreViewDataSource.cs b/Apps.Magento/DataSources/StoreViewDataSource.cs index f3637c3..f3dcb9f 100644 --- a/Apps.Magento/DataSources/StoreViewDataSource.cs +++ b/Apps.Magento/DataSources/StoreViewDataSource.cs @@ -9,7 +9,7 @@ namespace Apps.Magento.DataSources; public class StoreViewDataSource(InvocationContext invocationContext) : AppInvocable(invocationContext), IAsyncDataSourceHandler { - public async Task> GetDataAsync(DataSourceContext context, CancellationToken cancellationToken) + public virtual async Task> GetDataAsync(DataSourceContext context, CancellationToken cancellationToken) { var request = new ApiRequest("/rest/default/V1/store/storeViews", Method.Get, Creds); var storeViews = await Client.ExecuteWithErrorHandling>(request); diff --git a/Apps.Magento/DataSources/StoreViewWithAllDataSource.cs b/Apps.Magento/DataSources/StoreViewWithAllDataSource.cs new file mode 100644 index 0000000..ffb70a8 --- /dev/null +++ b/Apps.Magento/DataSources/StoreViewWithAllDataSource.cs @@ -0,0 +1,19 @@ +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Magento.DataSources; + +public class StoreViewWithAllDataSource(InvocationContext invocationContext) : StoreViewDataSource(invocationContext) +{ + public override async Task> GetDataAsync(DataSourceContext context, CancellationToken cancellationToken) + { + var result = await base.GetDataAsync(context, cancellationToken); + + if (result.All(x => x.Key != "all")) + { + result.Add("all", "All"); + } + + return result; + } +} \ No newline at end of file diff --git a/Apps.Magento/Models/Identifiers/StoreViewOptionalIdentifier.cs b/Apps.Magento/Models/Identifiers/StoreViewOptionalIdentifier.cs index f2970bc..a327f27 100644 --- a/Apps.Magento/Models/Identifiers/StoreViewOptionalIdentifier.cs +++ b/Apps.Magento/Models/Identifiers/StoreViewOptionalIdentifier.cs @@ -4,13 +4,13 @@ namespace Apps.Magento.Models.Identifiers; -public class StoreViewOptionalIdentifier +public class StoreViewOptionalIdentifier { [Display("Store view", Description = "If you will not specify it it will be default"), DataSource(typeof(StoreViewDataSource))] public string? StoreView { get; set; } public override string ToString() { - return StoreView ?? "default"; + return StoreView ?? String.Empty; } } \ No newline at end of file diff --git a/Apps.Magento/Models/Identifiers/StoreViewWithAllOptionalIdentifier.cs b/Apps.Magento/Models/Identifiers/StoreViewWithAllOptionalIdentifier.cs new file mode 100644 index 0000000..12bc4e0 --- /dev/null +++ b/Apps.Magento/Models/Identifiers/StoreViewWithAllOptionalIdentifier.cs @@ -0,0 +1,16 @@ +using Apps.Magento.DataSources; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Magento.Models.Identifiers; + +public class StoreViewWithAllOptionalIdentifier +{ + [Display("Store view", Description = "If you will not specify it it will be default"), DataSource(typeof(StoreViewWithAllDataSource))] + public string? StoreView { get; set; } + + public override string ToString() + { + return StoreView ?? "default"; + } +} \ No newline at end of file diff --git a/Apps.Magento/Models/Requests/Products/GetProductAsHtmlRequest.cs b/Apps.Magento/Models/Requests/Products/GetProductAsHtmlRequest.cs index aa32e96..79fb235 100644 --- a/Apps.Magento/Models/Requests/Products/GetProductAsHtmlRequest.cs +++ b/Apps.Magento/Models/Requests/Products/GetProductAsHtmlRequest.cs @@ -1,9 +1,11 @@ +using Apps.Magento.DataSources; using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; namespace Apps.Magento.Models.Requests.Products; public class GetProductAsHtmlRequest { - [Display("Custom attributes", Description = "Specify custom attributes to include in the HTML file. By default, only name, meta_title, meta_description, meta_keyword and description are included.")] + [Display("Custom attributes", Description = "Specify custom attributes to include in the HTML file. By default, only name, meta_title, meta_description, meta_keyword and description are included."), DataSource(typeof(ProductAttributeDataSource))] public IEnumerable? CustomAttributes { get; set; } } \ No newline at end of file diff --git a/Apps.Magento/Models/Responses/Products/ProductResponse.cs b/Apps.Magento/Models/Responses/Products/ProductResponse.cs index 6a39514..6579659 100644 --- a/Apps.Magento/Models/Responses/Products/ProductResponse.cs +++ b/Apps.Magento/Models/Responses/Products/ProductResponse.cs @@ -1,6 +1,7 @@ using Apps.Magento.Utils; using Blackbird.Applications.Sdk.Common; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; namespace Apps.Magento.Models.Responses.Products; @@ -183,10 +184,10 @@ public class Content public class CustomAttribute { - [Display("Attribute code")] + [Display("Attribute code"), JsonProperty("attribute_code")] public string AttributeCode { get; set; } = string.Empty; - [Display("Value"), JsonConverter(typeof(StringOrUndefinedConverter))] + [Display("Value"), JsonProperty("value"), JsonConverter(typeof(StringOrUndefinedConverter))] public string Value { get; set; } = string.Empty; } diff --git a/Apps.Magento/Utils/HtmlHelper.cs b/Apps.Magento/Utils/HtmlHelper.cs index 5560cfa..04a7ac7 100644 --- a/Apps.Magento/Utils/HtmlHelper.cs +++ b/Apps.Magento/Utils/HtmlHelper.cs @@ -17,7 +17,12 @@ public static string GenerateProductHtmlContent(ProductResponse product, List(defaultAttributes); + if (customAttributes != null) + { + attributesToInclude.UnionWith(customAttributes); + } + foreach (var customAttribute in product.CustomAttributes) { if (attributesToInclude.Contains(customAttribute.AttributeCode)) diff --git a/README.md b/README.md index a0e2dd1..02dd960 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Adobe Commerce is the leading platform for open commerce innovation. It’s desi ### Blocks -- **Get all blocks** - Get all blocks. +- **Search blocks** - Retrieve all blocks that match the specified search criteria. - **Get block** - Get block by specified ID. - **Get block as HTML** - Get block by specified ID as HTML. - **Create block** - Create block with specified data. @@ -61,7 +61,7 @@ Adobe Commerce is the leading platform for open commerce innovation. It’s desi ### Pages -- **Get all pages** - Get all pages. +- **Search pages** - Retrieve all pages that match the specified search criteria. - **Get page** - Get page by specified ID. - **Get page as HTML** - Get page by specified ID as HTML. - **Create page** - Create page with specified data. @@ -71,7 +71,7 @@ Adobe Commerce is the leading platform for open commerce innovation. It’s desi ### Products -- **Get all products** - Get all products. +- **Search products** - Retrieve all products that match the specified search criteria. - **Get product** - Get product by specified SKU. - **Get product as HTML** - Get product by specified SKU as HTML. Using optional parameters you can specify `Custom attributes` that you want to include in the HTML. - **Create product** - Create product with specified data.