From 8d16e28324449ae6c97d25b34ee553ae9dda36fa Mon Sep 17 00:00:00 2001 From: Steffen Balslev Date: Mon, 11 Nov 2024 09:59:00 +0100 Subject: [PATCH] Backport --- .../src/AppSourceProductDetails.Page.al | 5 +- .../src/AppSourceProductManager.Codeunit.al | 82 ++++++++++--------- .../src/AppSourceGalleryTest.Codeunit.al | 76 ----------------- 3 files changed, 46 insertions(+), 117 deletions(-) diff --git a/src/System Application/App/AppSource Gallery/src/AppSourceProductDetails.Page.al b/src/System Application/App/AppSource Gallery/src/AppSourceProductDetails.Page.al index a39e4ecdfc..5898be3f86 100644 --- a/src/System Application/App/AppSource Gallery/src/AppSourceProductDetails.Page.al +++ b/src/System Application/App/AppSource Gallery/src/AppSourceProductDetails.Page.al @@ -180,9 +180,10 @@ page 2516 "AppSource Product Details" var ExtensionManagement: Codeunit "Extension Management"; begin - if (PlansAreVisible) then + if PlansAreVisible then if not Confirm(PurchaseLicensesElsewhereLbl) then exit; + ExtensionManagement.InstallMarketplaceExtension(AppID); end; } @@ -305,7 +306,7 @@ page 2516 "AppSource Product Details" PlansOverview := ''; end; - CurrentRecordCanBeInstalled := (AppID <> '') and (not CurrentRecordCanBeUninstalled) and AppSourceProductManager.CanInstallProductWithPlans(AllPlans); + CurrentRecordCanBeInstalled := (AppID <> '') and (not CurrentRecordCanBeUninstalled) and AppSourceProductManager.CanInstallProductWithPlans(UniqueProductID); end; local procedure BuildPlanPriceText(Availabilities: JsonArray; var MonthlyPriceText: Text; var YearlyPriceText: Text): Boolean diff --git a/src/System Application/App/AppSource Gallery/src/AppSourceProductManager.Codeunit.al b/src/System Application/App/AppSource Gallery/src/AppSourceProductManager.Codeunit.al index 89d7bc58d2..da2b2eda96 100644 --- a/src/System Application/App/AppSource Gallery/src/AppSourceProductManager.Codeunit.al +++ b/src/System Application/App/AppSource Gallery/src/AppSourceProductManager.Codeunit.al @@ -28,8 +28,10 @@ codeunit 2515 "AppSource Product Manager" CatalogProductsUriLbl: Label 'https://catalogapi.azure.com/products', Locked = true; CatalogApiVersionQueryParamNameLbl: Label 'api-version', Locked = true; CatalogApiVersionQueryParamValueLbl: Label '2023-05-01-preview', Locked = true; + CatalogApiVersionOldQueryParamValueLbl: Label '2018-08-01-beta', Locked = true; CatalogApiOrderByQueryParamNameLbl: Label '$orderby', Locked = true; CatalogMarketQueryParamNameLbl: Label 'market', Locked = true; + CatalogListMarketNameLbl: label 'all', Locked = true; CatalogLanguageQueryParamNameLbl: Label 'language', Locked = true; CatalogApiFilterQueryParamNameLbl: Label '$filter', Locked = true; CatalogApiSelectQueryParamNameLbl: Label '$select', Locked = true; @@ -92,54 +94,46 @@ codeunit 2515 "AppSource Product Manager" /// /// Checks if the product can be installed or your are required to perform operations on AppSource before you can install the product. /// - /// JSonArray representing the product plans - /// True if the product can be installed, otherwise false - internal procedure CanInstallProductWithPlans(Plans: JsonArray): Boolean + internal procedure CanInstallProductWithPlans(UniqieProductIDValue: Text): Boolean var + LegacyProductObject: JsonObject; + LegacyPlansToken: JsonToken; + LegacyPlans: JsonArray; PlanToken: JsonToken; PlanObject: JsonObject; - PricingTypesToken: JsonToken; - PricingTypes: JsonArray; - PricingType: JsonToken; + CallToActionToken: JsonToken; begin - foreach PlanToken in Plans do begin - PlanObject := PlanToken.AsObject(); + Init(); - if PlanObject.Get('pricingTypes', PricingTypesToken) then - if (PricingTypesToken.IsArray()) then begin - PricingTypes := PricingTypesToken.AsArray(); - if PricingTypes.Count() = 0 then - exit(false); // No price structure, you need to contact the publisher - - foreach PricingType in PricingTypes do - case LowerCase(PricingType.AsValue().AsText()) of - 'free', // Free - 'freetrial', // Free trial - 'payg', // Pay as you go - 'byol': // Bring your own license - exit(true); - end; - end; + // Query legacy api toget all the plan and test if there is a contact me call to action. + LegacyProductObject := GetProductDetails(UniqieProductIDValue, ConstructProductUri(UniqieProductIDValue, CatalogApiVersionOldQueryParamValueLbl)); + LegacyProductObject.Get('plans', LegacyPlansToken); + LegacyPlans := LegacyPlansToken.AsArray(); + foreach PlanToken in LegacyPlans do begin + PlanObject := PlanToken.AsObject(); + if PlanObject.Get('callToAction', CallToActionToken) then + if LowerCase(CallToActionToken.AsValue().AsText()) = 'contactme' then + exit(false); end; - exit(false); + exit(true); end; #endregion #region Market and language helper functions - procedure GetCurrentLanguageCultureName(): Text + local procedure GetCurrentLanguageCultureName(): Text var Language: Codeunit Language; begin exit(Language.GetCultureName(GetCurrentUserLanguageID())); end; - procedure ResolveMarketAndLanguage(var Market: Code[2]; var LanguageName: Text) + local procedure ResolveLanguageName() LanguageName: Text; var Language: Codeunit Language; LanguageID: Integer; begin - GetCurrentUserLanguageAndLocaleID(LanguageID, Market); + LanguageID := GetCurrentUserLanguageAndLocaleID(); // Marketplace API only supports two letter languages. LanguageName := Language.GetTwoLetterISOLanguageName(LanguageID); @@ -161,7 +155,7 @@ codeunit 2515 "AppSource Product Manager" exit(LanguageID); end; - local procedure GetCurrentUserLanguageAndLocaleID(var LanguageID: Integer; var LocaleID: Code[2]) + local procedure GetCurrentUserLanguageAndLocaleID() LanguageID: Integer var TempUserSettings: Record "User Settings" temporary; Language: Codeunit Language; @@ -172,9 +166,6 @@ codeunit 2515 "AppSource Product Manager" LanguageID := Language.GetLanguageIdFromCultureName(AppSourceProductManagerDependencies.GetPreferredLanguage()); if (LanguageID = 0) then LanguageID := 1033; // Default to EN-US - - if (AppSourceProductManagerDependencies.IsSaas()) then - LocaleID := AppSourceProductManagerDependencies.GetCountryLetterCode(); end; /// @@ -229,14 +220,21 @@ codeunit 2515 "AppSource Product Manager" /// local procedure GetProductDetails(UniqueProductIDValue: Text): JsonObject var - RestClient: Codeunit "Rest Client"; RequestUri: Text; + begin + Init(); + RequestUri := ConstructProductUri(UniqueProductIDValue); + exit(GetProductDetails(UniqueProductIDValue, RequestUri)); + end; + + local procedure GetProductDetails(UniqueProductIDValue: Text; RequestUri: Text): JsonObject + var + RestClient: Codeunit "Rest Client"; ClientRequestID: Guid; TelemetryDictionary: Dictionary of [Text, Text]; begin Init(); ClientRequestID := CreateGuid(); - RequestUri := ConstructProductUri(UniqueProductIDValue); PopulateTelemetryDictionary(ClientRequestID, UniqueProductIDValue, RequestUri, TelemetryDictionary); Session.LogMessage('AL:AppSource-GetProduct', 'Requesting product details.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::All, TelemetryDictionary); @@ -297,13 +295,12 @@ codeunit 2515 "AppSource Product Manager" UriBuilder: Codeunit "Uri Builder"; Uri: Codeunit Uri; Language: Text; - Market: Code[2]; begin - ResolveMarketAndLanguage(Market, Language); + Language := ResolveLanguageName(); UriBuilder.Init(CatalogProductsUriLbl); UriBuilder.AddQueryParameter(CatalogApiVersionQueryParamNameLbl, CatalogApiVersionQueryParamValueLbl); - UriBuilder.AddQueryParameter(CatalogMarketQueryParamNameLbl, Market); + UriBuilder.AddQueryParameter(CatalogMarketQueryParamNameLbl, CatalogListMarketNameLbl); UriBuilder.AddQueryParameter(CatalogLanguageQueryParamNameLbl, Language); UriBuilder.AddODataQueryParameter(CatalogApiFilterQueryParamNameLbl, 'productType eq ''DynamicsBC'''); @@ -315,16 +312,23 @@ codeunit 2515 "AppSource Product Manager" end; local procedure ConstructProductUri(UniqueIdentifier: Text): Text + begin + exit(ConstructProductUri(UniqueIdentifier, CatalogApiVersionQueryParamValueLbl)); + end; + + local procedure ConstructProductUri(UniqueIdentifier: Text; ApiVersion: Text): Text var UriBuilder: Codeunit "Uri Builder"; Uri: Codeunit Uri; Language: Text; - Market: Code[2]; + Market: Text; begin - ResolveMarketAndLanguage(Market, Language); + // For market in the product details we use the Entra ID country code. + Market := AppSourceProductManagerDependencies.GetCountryLetterCode(); + Language := ResolveLanguageName(); UriBuilder.Init(CatalogProductsUriLbl); UriBuilder.SetPath('products/' + UniqueIdentifier); - UriBuilder.AddQueryParameter(CatalogApiVersionQueryParamNameLbl, CatalogApiVersionQueryParamValueLbl); + UriBuilder.AddQueryParameter(CatalogApiVersionQueryParamNameLbl, ApiVersion); UriBuilder.AddQueryParameter(CatalogMarketQueryParamNameLbl, Market); UriBuilder.AddQueryParameter(CatalogLanguageQueryParamNameLbl, Language); UriBuilder.GetUri(Uri); diff --git a/src/System Application/Test/AppSource Gallery/src/AppSourceGalleryTest.Codeunit.al b/src/System Application/Test/AppSource Gallery/src/AppSourceGalleryTest.Codeunit.al index 0f79dc74d0..b50f0da56c 100644 --- a/src/System Application/Test/AppSource Gallery/src/AppSourceGalleryTest.Codeunit.al +++ b/src/System Application/Test/AppSource Gallery/src/AppSourceGalleryTest.Codeunit.al @@ -172,82 +172,6 @@ codeunit 135074 "AppSource Gallery Test" AppSrcProductMgrTestImpl.ResetDependencies(); end; - [Test] - // In AppSource this shows up as having the Buy Now button enabled - procedure TestCanInstallProduct_BuyNow() - var - PlansList: JsonArray; - CanInstall: Boolean; - begin - // Given - // Hello world sample: PUBID.microsoftdynsmb%7CAID.helloworld%7CPAPPID.8e315acc-413d-46d5-abb9-c16912d3f3e3 - PlansList.ReadFrom('[{"id": "0002","availabilities": [{"id": "DZH318Z0BMGT","actions": ["Browse","Curate","Details","License","Purchase"],"meter": null,"pricingAudience": "DirectCommercial","terms": [{"termDescriptionParameters": null,"termId": "bh3541oe15ry","termUnit": "P1M","prorationPolicy": {"minimumProratedUnits": "P1D"},"termDescription": "1 Month Trial to 1 Year Subscription","price": {"currencyCode": "USD","isPIRequired": true,"listPrice": 0.0,"msrp": 0.0},"renewTermId": "qdp73gtwa5dy","renewTermUnits": "P1Y","isAutorenewable": true},{"termDescriptionParameters": null,"termId": "njspcsugneyy","termUnit": "P1M","prorationPolicy": {"minimumProratedUnits": "P1D"},"termDescription": "1 Month Trial to 1 Month Subscription","price": {"currencyCode": "USD","isPIRequired": true,"listPrice": 0.0,"msrp": 0.0},"renewTermId": "usrac41besqy","renewTermUnits": "P1M","isAutorenewable": true}],"hasFreeTrials": true,"consumptionUnitType": "DAY","displayRank": 0},{"id": "DZH318Z0BMGP","actions": ["Browse","Curate","Details","License","Purchase","Renew"],"meter": null,"pricingAudience": "DirectCommercial","terms": [{"termDescriptionParameters": null,"termId": "qdp73gtwa5dy","termUnit": "P1Y","prorationPolicy": {"minimumProratedUnits": "P1D"},"termDescription": "1 Year Subscription","price": {"currencyCode": "USD","isPIRequired": true,"listPrice": 0.02,"msrp": 0.02},"renewTermId": "qdp73gtwa5dy","renewTermUnits": "P1Y","isAutorenewable": true},{"termDescriptionParameters": null,"termId": "usrac41besqy","termUnit": "P1M","prorationPolicy": {"minimumProratedUnits": "P1D"},"termDescription": "1 Month Subscription","price": {"currencyCode": "USD","isPIRequired": true,"listPrice": 0.01,"msrp": 0.01},"renewTermId": "usrac41besqy","renewTermUnits": "P1M","isAutorenewable": true}],"hasFreeTrials": false,"consumptionUnitType": "DAY","displayRank": 1}],"uiDefinitionUri": "https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RW15J0G","isHidden": false,"isStopSell": false,"cspState": "OptOut","minQuantity": 1,"maxQuantity": 10,"isQuantifiable": true,"purchaseDurationDiscounts": [],"planId": "transactableplan1","uniquePlanId": "microsoftdynsmb.helloworldtransactableplan1","displayName": "First 10 users","metadata": {"generation": null,"altStackReference": null},"categoryIds": [],"pricingTypes": ["FreeTrial","Payg"],"description": "Test plan to test first 10 users configurations","skuId": "0002","planType": "DynamicsBC","displayRank": "2147483647","isPrivate": false},{"id": "0003","availabilities": [{"id": "DZH318Z0BMGW","actions": ["Browse","Curate","Details","License","Purchase","Renew"],"meter": null,"pricingAudience": "DirectCommercial","terms": [{"termDescriptionParameters": null,"termId": "qdp73gtwa5dy","termUnit": "P1Y","prorationPolicy": {"minimumProratedUnits": "P1D"},"termDescription": "1 Year Subscription","price": {"currencyCode": "USD","isPIRequired": true,"listPrice": 0.03,"msrp": 0.03},"renewTermId": "qdp73gtwa5dy","renewTermUnits": "P1Y","isAutorenewable": true},{"termDescriptionParameters": null,"termId": "usrac41besqy","termUnit": "P1M","prorationPolicy": {"minimumProratedUnits": "P1D"},"termDescription": "1 Month Subscription","price": {"currencyCode": "USD","isPIRequired": true,"listPrice": 0.02,"msrp": 0.02},"renewTermId": "usrac41besqy","renewTermUnits": "P1M","isAutorenewable": true}],"hasFreeTrials": false,"consumptionUnitType": "DAY","displayRank": 0}],"uiDefinitionUri": "https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RW15J0H","isHidden": false,"isStopSell": false,"cspState": "OptOut","minQuantity": 10,"maxQuantity": 100,"isQuantifiable": true,"purchaseDurationDiscounts": [],"planId": "transactableplan2","uniquePlanId": "microsoftdynsmb.helloworldtransactableplan2","displayName": "Ten to Hundred plan","metadata": {"generation": null,"altStackReference": null},"categoryIds": [],"pricingTypes": ["Payg"],"description": "Test 10 - 100 User plan","skuId": "0003","planType": "DynamicsBC","displayRank": "2147483647","isPrivate": false}]'); - - // When - CanInstall := AppSrcProductMgrTestImpl.CanInstallProductWithPlans(PlansList); - - // Then - LibraryAssert.IsTrue(CanInstall, 'The product should be installable.'); - end; - - [Test] - // In AppSource this shows up as having the Get It Now button enabled - procedure TestCanInstallProduct_GetItNow() - var - PlansList: JsonArray; - CanInstall: Boolean; - begin - // Given - // Hello world too sample: PUBID.microsoftdynsmb%7CAID.helloworldtoo%7CPAPPID.37447a59-b131-4e9c-83a3-a7856bfc30ff - PlansList.ReadFrom('[{"id": "0001","availabilities": [{"id": "DZH318Z0BMTW","actions": ["Browse","Curate","Details"],"meter": null,"pricingAudience": "DirectCommercial","terms": null,"hasFreeTrials": false,"displayRank": 0}],"uiDefinitionUri": "https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE5c9A4","isHidden": false,"isStopSell": false,"isQuantifiable": false,"purchaseDurationDiscounts": [],"planId": "69fe2a5e-cb01-43ea-9af3-2417d0c843f2","uniquePlanId": "microsoftdynsmb.helloworldtoo69fe2a5e-cb01-43ea-9af3-2417d0c843f2","displayName": "HelloWorldToo","metadata": {"generation": null,"altStackReference": null},"categoryIds": [],"pricingTypes": [],"description": "
desc
","skuId": "0001","planType": "DynamicsBC","isPrivate": false}]'); - - // When - CanInstall := AppSrcProductMgrTestImpl.CanInstallProductWithPlans(PlansList); - - // Then - if (CanInstall) then - LibraryAssert.Fail('Test now produces expected outcome and should be update.'); - end; - - - [Test] - // In AppSource this shows up as having the Free Trial button enabled - procedure TestCanInstallProduct_FreeTrial() - var - PlansList: JsonArray; - CanInstall: Boolean; - begin - // Given - // Manufacturing Central by Intech: PUBID.intechsystems%7CAID.manufacturing_central%7CPAPPID.cea8e27e-050e-4880-840c-954ceb2e3f13 - PlansList.ReadFrom('[{"id": "0001","availabilities": [{"id": "DZH318Z0BMV3","actions": ["Browse","Curate","Details"],"meter": null,"pricingAudience": "DirectCommercial","terms": null,"hasFreeTrials": false,"displayRank": 0}],"uiDefinitionUri": "https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RW1fI7S","isHidden": false,"isStopSell": false,"isQuantifiable": false,"purchaseDurationDiscounts": [],"planId": "77447b7c-9737-4132-8191-b9ce21d35a0e","uniquePlanId": "intechsystems.manufacturing_central77447b7c-9737-4132-8191-b9ce21d35a0e","displayName": "Manufacturing Central","metadata": {"generation": null,"altStackReference": null},"categoryIds": [],"pricingTypes": [],"description": "

Hey there

","skuId": "0001","planType": "DynamicsBC","isPrivate": false}]'); - - // When - CanInstall := AppSrcProductMgrTestImpl.CanInstallProductWithPlans(PlansList); - - // Then - if (CanInstall) then - LibraryAssert.Fail('Test now produces expected outcome and should be update.'); - end; - - [Test] - - // In AppSource this shows up as having the Contact Me button enabled - procedure TestCanInstallProduct_ContactMe() - var - PlansList: JsonArray; - CanInstall: Boolean; - begin - // Given - // Salesforce Integration by Celigo Inc: PUBID.celigoinc-causa1621285384596%7CAID.salesforce-integration-celigo%7CPAPPID.0595b87b-7670-4bd2-91e2-bd98a7fe2f5a - PlansList.ReadFrom('[{"id": "0001","availabilities": [{"id": "DZH318Z0BMV7","actions": ["Browse","Curate","Details"],"meter": null,"pricingAudience": "DirectCommercial","terms": null,"hasFreeTrials": false,"displayRank": 0}],"uiDefinitionUri": "https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE5fzzR","isHidden": false,"isStopSell": false,"isQuantifiable": false,"purchaseDurationDiscounts": [],"planId": "4b4a5c29-40ad-4169-b23c-1ea9a51fd176","uniquePlanId": "celigoinc-causa1621285384596.salesforce-integration-celigo4b4a5c29-40ad-4169-b23c-1ea9a51fd176","displayName": "Salesforce Integration for Dynamics 365 Business Central","metadata": {"generation": null,"altStackReference": null},"categoryIds": [],"pricingTypes": [],"description": "
Celigo
","skuId": "0001","planType": "DynamicsBC","isPrivate": false}]'); - - // When - CanInstall := AppSrcProductMgrTestImpl.CanInstallProductWithPlans(PlansList); - - // Then - LibraryAssert.IsFalse(CanInstall, 'The product NOT should be installable.'); - end; - [HyperlinkHandler] procedure HyperlinkHandler(Message: Text[1024]) begin