From 4eaf88b619664ccb59ebf086970456da56462b32 Mon Sep 17 00:00:00 2001 From: Povilas Staskus Date: Wed, 29 Mar 2023 13:44:10 +0300 Subject: [PATCH] Domains Dashboard Card: Presentation Criteria (#20423) * Implement directDomainPurchaseDashboardCard remote feature flag * Created DomainsDashboardCardHelper with shouldShowCard logic - Added blog and user conditions based on which we show domains dashboard card - Added DomainsDashboardCardHelperTests to tests the different cases * Integrate domain dashboard card presentation logic into DashboardCard * Use blog.domainList instead of blog.domains to only include non-wpcom domains * Refresh domains when blog is synced Until now, domains were only refreshed when the blog details were opened, however, now we need to have blog domains list up-to-date * Remove extra empty line --- WordPress/Classes/Services/BlogService.m | 10 ++- .../BuildInformation/RemoteFeatureFlag.swift | 7 ++ .../Domains/DomainsDashboardCardHelper.swift | 34 +++++++ .../Blog/Blog Dashboard/DashboardCard.swift | 6 ++ .../BlogDashboardPersonalizationService.swift | 2 + WordPress/WordPress.xcodeproj/project.pbxproj | 18 ++++ WordPress/WordPressTest/BlogBuilder.swift | 17 ++++ .../DomainsDashboardCardHelperTests.swift | 88 +++++++++++++++++++ 8 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Domains/DomainsDashboardCardHelper.swift create mode 100644 WordPress/WordPressTest/Dashboard/DomainsDashboardCardHelperTests.swift diff --git a/WordPress/Classes/Services/BlogService.m b/WordPress/Classes/Services/BlogService.m index 7a9d0f2dac77..ecd70f84cafd 100644 --- a/WordPress/Classes/Services/BlogService.m +++ b/WordPress/Classes/Services/BlogService.m @@ -191,7 +191,15 @@ - (void)syncBlogAndAllMetadata:(Blog *)blog completionHandler:(void (^)(void))co [blazeService getStatusFor:blog completion:^{ dispatch_group_leave(syncGroup); }]; - + + dispatch_group_enter(syncGroup); + [self refreshDomainsFor:blog success:^{ + dispatch_group_leave(syncGroup); + } failure:^(NSError * _Nonnull error) { + DDLogError(@"Failed refreshing domains: %@", error); + dispatch_group_leave(syncGroup); + }]; + // When everything has left the syncGroup (all calls have ended with success // or failure) perform the completionHandler dispatch_group_notify(syncGroup, dispatch_get_main_queue(),^{ diff --git a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift index c049636013ac..ffbcc6dd08d6 100644 --- a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift @@ -13,6 +13,7 @@ enum RemoteFeatureFlag: Int, CaseIterable { case wordPressSupportForum case blaze case wordPressIndividualPluginSupport + case directDomainsPurchaseDashboardCard var defaultValue: Bool { switch self { @@ -38,6 +39,8 @@ enum RemoteFeatureFlag: Int, CaseIterable { return false case .wordPressIndividualPluginSupport: return AppConfiguration.isWordPress + case .directDomainsPurchaseDashboardCard: + return false } } @@ -66,6 +69,8 @@ enum RemoteFeatureFlag: Int, CaseIterable { return "blaze" case .wordPressIndividualPluginSupport: return "wp_individual_plugin_overlay" + case .directDomainsPurchaseDashboardCard: + return "direct_domain_purchase_dashboard_card" } } @@ -93,6 +98,8 @@ enum RemoteFeatureFlag: Int, CaseIterable { return "Blaze" case .wordPressIndividualPluginSupport: return "Jetpack Individual Plugin Support for WordPress" + case .directDomainsPurchaseDashboardCard: + return "Direct Domains Purchase Dashboard Card" } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Domains/DomainsDashboardCardHelper.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Domains/DomainsDashboardCardHelper.swift new file mode 100644 index 000000000000..1aef027603f2 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Domains/DomainsDashboardCardHelper.swift @@ -0,0 +1,34 @@ +import Foundation + +final class DomainsDashboardCardHelper { + + /// Checks conditions for showing domain dashboard cards + static func shouldShowCard( + for blog: Blog, + isJetpack: Bool = AppConfiguration.isJetpack, + featureFlagEnabled: Bool = RemoteFeatureFlag.directDomainsPurchaseDashboardCard.enabled() + ) -> Bool { + guard isJetpack, featureFlagEnabled else { + return false + } + + let isHostedAtWPcom = blog.isHostedAtWPcom + let isAtomic = blog.isAtomic() + let isAdmin = blog.isAdmin + let hasOtherDomains = blog.domainsList.count > 0 + let hasDomainCredit = blog.hasDomainCredit + + return (isHostedAtWPcom || isAtomic) && isAdmin && !hasOtherDomains && !hasDomainCredit + } + + static func hideCard(for blog: Blog?) { + guard let blog, + let siteID = blog.dotComID?.intValue else { + DDLogError("Domains Dashboard Card: error hiding the card.") + return + } + + BlogDashboardPersonalizationService(siteID: siteID) + .setEnabled(false, for: .domainsDashboardCard) + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift index fbfb1cc79f59..397b738e2d8d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift @@ -11,6 +11,7 @@ enum DashboardCard: String, CaseIterable { case quickStart case prompts case blaze + case domainsDashboardCard case todaysStats = "todays_stats" case draftPosts case scheduledPosts @@ -48,6 +49,9 @@ enum DashboardCard: String, CaseIterable { return DashboardBadgeCell.self case .blaze: return DashboardBlazeCardCell.self + case .domainsDashboardCard: + /// TODO + return DashboardFailureCardCell.self } } @@ -82,6 +86,8 @@ enum DashboardCard: String, CaseIterable { return JetpackBrandingVisibility.all.enabled case .blaze: return BlazeHelper.shouldShowCard(for: blog) + case .domainsDashboardCard: + return DomainsDashboardCardHelper.shouldShowCard(for: blog) } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift index 7a13269a5bfa..40801e59b7e8 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift @@ -50,6 +50,8 @@ private func makeKey(for card: DashboardCard) -> String? { // have a "-card" component in the key name. Keeping it like this to // avoid having to migrate data. return "prompts-enabled-site-settings" + case .domainsDashboardCard: + return "domains-dashboard-enabled-site-settings" case .quickStart, .jetpackBadge, .jetpackInstall, .nextPost, .createPost, .failure, .ghost: return nil } diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 08a6137bdf51..fbddc95d8639 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -136,6 +136,9 @@ 0107E18D29000E3300DE87DB /* AppStyleGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD2538E26116A1600EDAF88 /* AppStyleGuide.swift */; }; 0107E18E29000EA100DE87DB /* UIColor+MurielColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435B762122973D0600511813 /* UIColor+MurielColors.swift */; }; 0107E18F29000EA200DE87DB /* UIColor+MurielColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435B762122973D0600511813 /* UIColor+MurielColors.swift */; }; + 0118968F29D1EB5E00D34BA9 /* DomainsDashboardCardHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0118968E29D1EB5E00D34BA9 /* DomainsDashboardCardHelper.swift */; }; + 0118969129D1F2FE00D34BA9 /* DomainsDashboardCardHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0118969029D1F2FE00D34BA9 /* DomainsDashboardCardHelperTests.swift */; }; + 0118969229D2CA6F00D34BA9 /* DomainsDashboardCardHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0118968E29D1EB5E00D34BA9 /* DomainsDashboardCardHelper.swift */; }; 0141929C2983F0A300CAEDB0 /* SupportConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0141929B2983F0A300CAEDB0 /* SupportConfiguration.swift */; }; 0141929D2983F0A300CAEDB0 /* SupportConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0141929B2983F0A300CAEDB0 /* SupportConfiguration.swift */; }; 014192A02983F5E800CAEDB0 /* SupportConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0141929F2983F5E800CAEDB0 /* SupportConfigurationTests.swift */; }; @@ -5692,6 +5695,8 @@ 0107E1832900043200DE87DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0107E1842900059300DE87DB /* LocalizationConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationConfiguration.swift; sourceTree = ""; }; 0107E1862900065400DE87DB /* LocalizationConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationConfiguration.swift; sourceTree = ""; }; + 0118968E29D1EB5E00D34BA9 /* DomainsDashboardCardHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainsDashboardCardHelper.swift; sourceTree = ""; }; + 0118969029D1F2FE00D34BA9 /* DomainsDashboardCardHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainsDashboardCardHelperTests.swift; sourceTree = ""; }; 011A2815DB0DE7E3973CBC0E /* Pods-Apps-Jetpack.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Apps-Jetpack.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Apps-Jetpack/Pods-Apps-Jetpack.release.xcconfig"; sourceTree = ""; }; 0141929B2983F0A300CAEDB0 /* SupportConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportConfiguration.swift; sourceTree = ""; }; 0141929F2983F5E800CAEDB0 /* SupportConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportConfigurationTests.swift; sourceTree = ""; }; @@ -9525,6 +9530,14 @@ path = JetpackStatsWidgets; sourceTree = ""; }; + 0118968D29D1EAC900D34BA9 /* Domains */ = { + isa = PBXGroup; + children = ( + 0118968E29D1EB5E00D34BA9 /* DomainsDashboardCardHelper.swift */, + ); + path = Domains; + sourceTree = ""; + }; 0141929E2983F5D900CAEDB0 /* Support */ = { isa = PBXGroup; children = ( @@ -13366,6 +13379,7 @@ 8B4DDF23278F3AED0022494D /* Cards */ = { isa = PBXGroup; children = ( + 0118968D29D1EAC900D34BA9 /* Domains */, FA98B61429A3B71E0071AAE8 /* Blaze */, FE18495627F5ACA400D26879 /* Prompts */, 8B92D69427CD51CE001F5371 /* Ghost */, @@ -13413,6 +13427,7 @@ 806E53E327E01CFE0064315E /* DashboardStatsViewModelTests.swift */, 80B016CE27FEBDC900D15566 /* DashboardCardTests.swift */, 80EF9283280CFEB60064A971 /* DashboardPostsSyncManagerTests.swift */, + 0118969029D1F2FE00D34BA9 /* DomainsDashboardCardHelperTests.swift */, ); path = Dashboard; sourceTree = ""; @@ -21333,6 +21348,7 @@ 08E39B4528A3DEB200874CB8 /* UserPersistentStoreFactory.swift in Sources */, C81CCD67243AECA200A83E27 /* TenorGIFCollection.swift in Sources */, 4AD5656C28E3D0670054C676 /* ReaderPost+Helper.swift in Sources */, + 0118968F29D1EB5E00D34BA9 /* DomainsDashboardCardHelper.swift in Sources */, E137B1661F8B77D4006AC7FC /* WebNavigationDelegate.swift in Sources */, 937D9A0F19F83812007B9D5F /* WordPress-22-23.xcmappingmodel in Sources */, 803DE81328FFAE36007D4E9C /* RemoteConfigStore.swift in Sources */, @@ -23202,6 +23218,7 @@ 933D1F471EA64108009FB462 /* TestingAppDelegate.m in Sources */, 5789E5C822D7D40800333698 /* AztecPostViewControllerAttachmentTests.swift in Sources */, 0A9687BC28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift in Sources */, + 0118969129D1F2FE00D34BA9 /* DomainsDashboardCardHelperTests.swift in Sources */, 400199AB222590E100EB0906 /* AllTimeStatsRecordValueTests.swift in Sources */, C8567498243F41CA001A995E /* MockTenorService.swift in Sources */, 1D19C56629C9DB0A00FB0087 /* GutenbergVideoPressUploadProcessorTests.swift in Sources */, @@ -25076,6 +25093,7 @@ F1C740C026B1D4D2005D0809 /* StoreSandboxSecretScreen.swift in Sources */, 801D94F02919E7D70051993E /* JetpackFullscreenOverlayGeneralViewModel.swift in Sources */, FABB25FF2602FC2C00C8785C /* RegisterDomainDetailsViewModel+RowDefinitions.swift in Sources */, + 0118969229D2CA6F00D34BA9 /* DomainsDashboardCardHelper.swift in Sources */, 98E082A02637545C00537BF1 /* PostService+Likes.swift in Sources */, FABB26002602FC2C00C8785C /* GravatarProfile.swift in Sources */, FABB26012602FC2C00C8785C /* ReaderShareAction.swift in Sources */, diff --git a/WordPress/WordPressTest/BlogBuilder.swift b/WordPress/WordPressTest/BlogBuilder.swift index d232f352770d..eeded2f75a5c 100644 --- a/WordPress/WordPressTest/BlogBuilder.swift +++ b/WordPress/WordPressTest/BlogBuilder.swift @@ -120,6 +120,23 @@ final class BlogBuilder { return self } + func with(registeredDomainCount: Int) -> Self { + var domains: [ManagedDomain] = [] + for _ in 0.. Self { + blog.hasDomainCredit = hasDomainCredit + return self + } + @discardableResult func build() -> Blog { return blog diff --git a/WordPress/WordPressTest/Dashboard/DomainsDashboardCardHelperTests.swift b/WordPress/WordPressTest/Dashboard/DomainsDashboardCardHelperTests.swift new file mode 100644 index 000000000000..a8593d6dc899 --- /dev/null +++ b/WordPress/WordPressTest/Dashboard/DomainsDashboardCardHelperTests.swift @@ -0,0 +1,88 @@ +import XCTest +@testable import WordPress + +final class DomainsDashboardCardHelperTests: CoreDataTestCase { + func testShouldShowCardEnabledFeatureFlagAndHostedAtWPcom() { + let blog = BlogBuilder(mainContext) + .isHostedAtWPcom() + .with(atomic: false) + .with(isAdmin: true) + .with(registeredDomainCount: 0) + .with(hasDomainCredit: false) + .build() + + let result = DomainsDashboardCardHelper.shouldShowCard(for: blog, isJetpack: true, featureFlagEnabled: true) + + XCTAssertTrue(result, "Card should show for WPcom hosted blogs") + } + + func testShouldShowCardEnabledFeatureFlagAndAtomic() { + let blog = BlogBuilder(mainContext) + .isNotHostedAtWPcom() + .with(atomic: true) + .with(isAdmin: true) + .with(registeredDomainCount: 0) + .with(hasDomainCredit: false) + .build() + + let result = DomainsDashboardCardHelper.shouldShowCard(for: blog, isJetpack: true, featureFlagEnabled: true) + + XCTAssertTrue(result, "Card should show for Atomic blogs") + } + + func testShouldNotShowCardNotAdmin() { + let blog = BlogBuilder(mainContext) + .isHostedAtWPcom() + .with(atomic: false) + .with(isAdmin: false) + .with(registeredDomainCount: 0) + .with(hasDomainCredit: false) + .build() + + let result = DomainsDashboardCardHelper.shouldShowCard(for: blog, isJetpack: true, featureFlagEnabled: true) + + XCTAssertFalse(result, "Card should not show for non-admin users") + } + + func testShouldNotShowCardHasOtherDomains() { + let blog = BlogBuilder(mainContext) + .isHostedAtWPcom() + .with(atomic: false) + .with(isAdmin: true) + .with(registeredDomainCount: 2) + .with(hasDomainCredit: false) + .build() + + let result = DomainsDashboardCardHelper.shouldShowCard(for: blog, isJetpack: true, featureFlagEnabled: true) + + XCTAssertFalse(result, "Card should not show for blogs with more than one domain") + } + + func testShouldNotShowCardHasDomainCredit() { + let blog = BlogBuilder(mainContext) + .isHostedAtWPcom() + .with(atomic: false) + .with(isAdmin: true) + .with(registeredDomainCount: 0) + .with(hasDomainCredit: true) + .build() + + let result = DomainsDashboardCardHelper.shouldShowCard(for: blog, isJetpack: true, featureFlagEnabled: true) + + XCTAssertFalse(result, "Card should not show for blogs with domain credit") + } + + func testShouldNotShowCardFeatureFlagDisabled() { + let blog = BlogBuilder(mainContext) + .isHostedAtWPcom() + .with(atomic: false) + .with(isAdmin: true) + .with(registeredDomainCount: 0) + .with(hasDomainCredit: false) + .build() + + let result = DomainsDashboardCardHelper.shouldShowCard(for: blog, isJetpack: true, featureFlagEnabled: false) + + XCTAssertFalse(result, "Card should not show when the feature flag is disabled") + } +}