Skip to content

Commit 0366ca7

Browse files
authored
[HACK][Merchant AI key] Add feature flag & render settings row if eligible (#15404)
2 parents 08447da + 6060f22 commit 0366ca7

File tree

9 files changed

+79
-1
lines changed

9 files changed

+79
-1
lines changed

Experiments/Experiments/DefaultFeatureFlagService.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
9797
return buildConfig == .localDeveloper || buildConfig == .alpha
9898
case .notificationSettings:
9999
return true
100+
case .allowMerchantAIAPIKey:
101+
return buildConfig == .localDeveloper || buildConfig == .alpha
100102
default:
101103
return true
102104
}

Experiments/Experiments/FeatureFlag.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,8 @@ public enum FeatureFlag: Int {
208208
/// Supports managing notification settings from the app settings
209209
///
210210
case notificationSettings
211+
212+
/// Allows merchants to use their own API keys for AI-powered features
213+
///
214+
case allowMerchantAIAPIKey
211215
}

WooCommerce/Classes/Extensions/UIImage+Woo.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,10 @@ extension UIImage {
569569
return UIImage.gridicon(.cog)
570570
}
571571

572+
static var wandAndRaysInverse: UIImage {
573+
return UIImage(systemName: "wand.and.rays.inverse")!
574+
}
575+
572576
static func prologueBackgroundBubbles(tint: UIColor) -> UIImage {
573577
let image = UIImage(named: "login-prologue-background-bubbles")!
574578
return image.withTintColor(tint)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import SwiftUI
2+
3+
struct AISettingsView: View {
4+
var body: some View {
5+
EmptyView()
6+
}
7+
}

WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ private extension HubMenu {
184184
BlazeCampaignListHostingControllerRepresentable(siteID: viewModel.siteID, selectedCampaignID: campaignID)
185185
case .blazeCampaignCreation:
186186
BlazeCampaignListHostingControllerRepresentable(siteID: viewModel.siteID, startsCampaignCreationOnAppear: true)
187+
case .aiSettings:
188+
AISettingsView()
187189
}
188190
}
189191
.navigationBarTitleDisplayMode(.inline)

WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extension NSNotification.Name {
1616
/// Destination views that the hub menu can navigate to.
1717
enum HubMenuNavigationDestination: Hashable {
1818
case payments
19+
case aiSettings
1920
case settings
2021
case blaze
2122
case blazeCampaignDetails(campaignID: String)
@@ -122,6 +123,10 @@ final class HubMenuViewModel: ObservableObject {
122123
@Published private var isSiteEligibleForGoogleAds = false
123124
@Published private var isSiteEligibleForInbox = false
124125

126+
private var shouldShowAISettings: Bool {
127+
featureFlagService.isFeatureFlagEnabled(.allowMerchantAIAPIKey)
128+
}
129+
125130
private var cancellables: Set<AnyCancellable> = []
126131

127132
let tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker
@@ -356,6 +361,10 @@ private extension HubMenuViewModel {
356361
Payments(iconBadge: shouldShowBadgeOnPayments ? .dot : nil)
357362
]
358363

364+
if shouldShowAISettings {
365+
items.append(AISettings())
366+
}
367+
359368
if eligibleForGoogleAds {
360369
items.append(GoogleAds())
361370
}
@@ -556,6 +565,23 @@ extension HubMenuViewModel {
556565
let navigationDestination: HubMenuNavigationDestination? = .settings
557566
}
558567

568+
struct AISettings: HubMenuItem {
569+
static var id = "ai-settings"
570+
571+
let title: String = Localization.aiSettings
572+
let description: String = Localization.aiSettingsDescription
573+
let icon: UIImage = .wandAndRaysInverse
574+
let iconColor: UIColor = .primary
575+
let accessibilityIdentifier: String = "ai-settings"
576+
let trackingOption: String = "ai-settings"
577+
let iconBadge: HubMenuBadgeType?
578+
let navigationDestination: HubMenuNavigationDestination? = .aiSettings
579+
580+
init(iconBadge: HubMenuBadgeType? = nil) {
581+
self.iconBadge = iconBadge
582+
}
583+
}
584+
559585
struct Payments: HubMenuItem {
560586

561587
static var id = "payments"
@@ -732,6 +758,16 @@ extension HubMenuViewModel {
732758
"Payments",
733759
comment: "Title of the hub menu payments button")
734760

761+
static let aiSettings = NSLocalizedString(
762+
"hubMenuViewModel.aiSettings",
763+
value: "AI Settings",
764+
comment: "Title of the hub menu AI settings button")
765+
766+
static let aiSettingsDescription = NSLocalizedString(
767+
"hubMenuViewModel.aiSettingsDescription",
768+
value: "Manage your store's AI-powered features",
769+
comment: "Description of the hub menu AI settings button")
770+
735771
static let paymentsDescription = NSLocalizedString(
736772
"Take payments on the go",
737773
comment: "Description of the hub menu payments button")

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,6 +1608,7 @@
16081608
68AC9D292ACE598B0042F784 /* ProductImageThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */; };
16091609
68AF3C3B2D01481C006F1ED2 /* POSReceiptEligibilityBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68AF3C3A2D01481A006F1ED2 /* POSReceiptEligibilityBanner.swift */; };
16101610
68B681162D9257810098D5CD /* PointOfSaleCouponsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */; };
1611+
68B3BA262D9147480000B2F2 /* AISettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B3BA252D9147440000B2F2 /* AISettingsView.swift */; };
16111612
68B6F22B2ADE7ED500D171FC /* TooltipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B6F22A2ADE7ED500D171FC /* TooltipView.swift */; };
16121613
68C31B712A8617C500AE5C5A /* NewNoteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C31B702A8617C500AE5C5A /* NewNoteViewModel.swift */; };
16131614
68C53CBE2C1FE59B00C6D80B /* ItemListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C53CBD2C1FE59B00C6D80B /* ItemListView.swift */; };
@@ -4786,6 +4787,7 @@
47864787
68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductImageThumbnail.swift; sourceTree = "<group>"; };
47874788
68AF3C3A2D01481A006F1ED2 /* POSReceiptEligibilityBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSReceiptEligibilityBanner.swift; sourceTree = "<group>"; };
47884789
68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCouponsController.swift; sourceTree = "<group>"; };
4790+
68B3BA252D9147440000B2F2 /* AISettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AISettingsView.swift; sourceTree = "<group>"; };
47894791
68B6F22A2ADE7ED500D171FC /* TooltipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipView.swift; sourceTree = "<group>"; };
47904792
68C31B702A8617C500AE5C5A /* NewNoteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteViewModel.swift; sourceTree = "<group>"; };
47914793
68C53CBD2C1FE59B00C6D80B /* ItemListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListView.swift; sourceTree = "<group>"; };
@@ -9917,6 +9919,14 @@
99179919
path = Analytics;
99189920
sourceTree = "<group>";
99199921
};
9922+
68B3BA242D91473D0000B2F2 /* AI Settings */ = {
9923+
isa = PBXGroup;
9924+
children = (
9925+
68B3BA252D9147440000B2F2 /* AISettingsView.swift */,
9926+
);
9927+
path = "AI Settings";
9928+
sourceTree = "<group>";
9929+
};
99209930
68DF5A8B2CB38EC5000154C9 /* Coupons */ = {
99219931
isa = PBXGroup;
99229932
children = (
@@ -10756,6 +10766,7 @@
1075610766
B56DB3EF2049C06D00D4AA8E /* ViewRelated */ = {
1075710767
isa = PBXGroup;
1075810768
children = (
10769+
68B3BA242D91473D0000B2F2 /* AI Settings */,
1075910770
B626C7192876599B0083820C /* Custom Fields */,
1076010771
86023FA82B15CA8D00A28F07 /* Themes */,
1076110772
DED91DF72AD78A0C00CDCC53 /* Blaze */,
@@ -16897,6 +16908,7 @@
1689716908
B6C78B90293BAF37008934A1 /* AnalyticsHubLastYearRangeData.swift in Sources */,
1689816909
314265B12645A07800500598 /* CardReaderSettingsConnectedViewController.swift in Sources */,
1689916910
B57C744520F55BA600EEFC87 /* NSObject+Helpers.swift in Sources */,
16911+
68B3BA262D9147480000B2F2 /* AISettingsView.swift in Sources */,
1690016912
0269576A23726304001BA0BF /* KeyboardFrameObserver.swift in Sources */,
1690116913
45CE2D852625D7ED00E3CA00 /* SelectableItemRow.swift in Sources */,
1690216914
CEEC9B6421E7AB850055EEF0 /* AppRatingManager.swift in Sources */,

WooCommerce/WooCommerceTests/Mocks/MockFeatureFlagService.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ final class MockFeatureFlagService: FeatureFlagService {
2424
var isProductGlobalUniqueIdentifierSupported: Bool
2525
var hideSitesInStorePicker: Bool
2626
var notificationSettings: Bool
27+
var allowMerchantAIAPIKey: Bool
2728

2829
init(isInboxOn: Bool = false,
2930
isShowInboxCTAEnabled: Bool = false,
@@ -46,7 +47,8 @@ final class MockFeatureFlagService: FeatureFlagService {
4647
favoriteProducts: Bool = false,
4748
isProductGlobalUniqueIdentifierSupported: Bool = false,
4849
hideSitesInStorePicker: Bool = false,
49-
notificationSettings: Bool = false) {
50+
notificationSettings: Bool = false,
51+
allowMerchantAIAPIKey: Bool = false) {
5052
self.isInboxOn = isInboxOn
5153
self.isShowInboxCTAEnabled = isShowInboxCTAEnabled
5254
self.isUpdateOrderOptimisticallyOn = isUpdateOrderOptimisticallyOn
@@ -69,6 +71,7 @@ final class MockFeatureFlagService: FeatureFlagService {
6971
self.isProductGlobalUniqueIdentifierSupported = isProductGlobalUniqueIdentifierSupported
7072
self.hideSitesInStorePicker = hideSitesInStorePicker
7173
self.notificationSettings = notificationSettings
74+
self.allowMerchantAIAPIKey = allowMerchantAIAPIKey
7275
}
7376

7477
func isFeatureFlagEnabled(_ featureFlag: FeatureFlag) -> Bool {
@@ -117,6 +120,8 @@ final class MockFeatureFlagService: FeatureFlagService {
117120
return hideSitesInStorePicker
118121
case .notificationSettings:
119122
return notificationSettings
123+
case .allowMerchantAIAPIKey:
124+
return allowMerchantAIAPIKey
120125
default:
121126
return false
122127
}

WooCommerce/WooCommerceTests/ViewRelated/HubMenu/HubMenuViewModelTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,13 @@ final class HubMenuViewModelTests: XCTestCase {
206206
stores.updateDefaultStore(storeID: sampleSiteID)
207207
stores.updateDefaultStore(.fake().copy(siteID: sampleSiteID))
208208

209+
let featureFlagService = MockFeatureFlagService(allowMerchantAIAPIKey: false)
209210
let blazeEligibilityChecker = MockBlazeEligibilityChecker(isSiteEligible: true)
210211

211212
// When
212213
let viewModel = HubMenuViewModel(siteID: sampleSiteID,
213214
tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker(),
215+
featureFlagService: featureFlagService,
214216
stores: stores,
215217
blazeEligibilityChecker: blazeEligibilityChecker)
216218

@@ -267,11 +269,13 @@ final class HubMenuViewModelTests: XCTestCase {
267269
stores.updateDefaultStore(storeID: sampleSiteID)
268270
stores.updateDefaultStore(.fake().copy(siteID: sampleSiteID))
269271

272+
let featureFlagService = MockFeatureFlagService(allowMerchantAIAPIKey: false)
270273
let checker = MockGoogleAdsEligibilityChecker(isEligible: true)
271274

272275
// When
273276
let viewModel = HubMenuViewModel(siteID: sampleSiteID,
274277
tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker(),
278+
featureFlagService: featureFlagService,
275279
stores: stores,
276280
googleAdsEligibilityChecker: checker)
277281
waitUntil {
@@ -568,6 +572,7 @@ final class HubMenuViewModelTests: XCTestCase {
568572
let blazeEligibilityChecker = MockBlazeEligibilityChecker(isSiteEligible: true)
569573
let googleAdsEligibilityChecker = MockGoogleAdsEligibilityChecker(isEligible: true)
570574
let inboxEligibilityChecker = MockInboxEligibilityChecker()
575+
let featureFlagService = MockFeatureFlagService(allowMerchantAIAPIKey: false)
571576
inboxEligibilityChecker.isEligible = true
572577

573578
let stores = MockStoresManager(sessionManager: .makeForTesting())
@@ -578,6 +583,7 @@ final class HubMenuViewModelTests: XCTestCase {
578583
let navigationPath = NavigationPath(["testPath1", "testPath2"])
579584
let viewModel = HubMenuViewModel(siteID: sampleSiteID,
580585
tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker(),
586+
featureFlagService: featureFlagService,
581587
stores: stores,
582588
generalAppSettings: generalAppSettings,
583589
inboxEligibilityChecker: inboxEligibilityChecker,

0 commit comments

Comments
 (0)