Skip to content

Commit ca753bc

Browse files
[iOS][Feature Flags] Remove SAD flag for active and inactive users (#2681)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1206329551987282/task/1211866611125558?focus=true Tech Design URL: CC: ### Description This PR removes two feature flags for SAD prompts: **Active Users** Commit: 60b2521 Feature Flag Task: https://app.asana.com/1/137249556945/project/1206329551987282/task/1211866611125558?focus=true **Inactive Users** Commit: 208a41a Feature Flag Task: https://app.asana.com/1/137249556945/project/1206329551987282/task/1211866470962985?focus=true Even though we use remote settings for the two prompts to function, the settings are defined in the parent feature, so it is safe to remove the subfeature from the codebase. ### Testing Steps 1. Put a breakpoint in `DefaultBrowserPromptFeatureFlagger.swift` -> `private func getSettings(_ value: DefaultBrowserPromptFeatureSettings) -> Int` line 102. 2. Open Debug Menu -> 'Default Browser Prompt’ and follow the instructions to make SAD prompt appear. 3. Ensure that `settingsProvider.defaultBrowserPromptFeatureSettings[value.rawValue] as? Int` returns a value and it’s not nil. ### Impact and Risks <!— What's the impact on users if something goes wrong? High: Could affect user privacy, lose user data, break core functionality Medium: Could disrupt specific features or user flows Low: Minor visual changes, small bug fixes, improvement to existing features None: Internal tooling, documentation —> #### What could go wrong? <!-- Describe specific scenarios and how you've addressed them —> ### Quality Considerations <!— Focus on what matters for your changes: - What edge cases exist? - How does this affect performance? - What monitoring have you added? - What documentation needs updating? - How does this impact privacy/security? —> ### Notes to Reviewer <!-- Anything specific you want reviewers to focus on —> — ###### Internal references: [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f) | [Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) | [Tech Design Template](https://app.asana.com/0/59792373528535/184709971311943) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Removes active/inactive scheduled default browser prompt feature flags and gating, simplifying to use privacy-config settings only and updating factory, logic, and tests accordingly. > > - **Feature flags/config**: > - Remove iOS `FeatureFlag` cases `scheduledSetDefaultBrowserPrompts` and `scheduledSetDefaultBrowserPromptsForInactiveUsers`, related override/support lists, and source mappings in `iOS/Core/FeatureFlag.swift`. > - Remove iOS subfeatures from `SetAsDefaultAndAddToDockSubfeature` in `PrivacyFeature.swift`. > - **Default Browser Prompt (SAD) implementation**: > - Simplify `DefaultBrowserPromptFeatureFlagAdapter` to only expose `defaultBrowserPromptFeatureSettings` (no enablement booleans). > - `DefaultBrowserPromptFactory` no longer accepts `featureFlagProvider`; `DefaultBrowserPromptFeatureFlag` now constructed with only `settingsProvider`. > - Drop enablement checks in prompt deciders (`DefaultBrowserPromptTypeDecider` active/inactive) and in `DefaultBrowserPromptService.resume()`; always record activity. > - Debug view: always shows settings and is enabled; remove feature-enabled gating. > - **SetDefaultBrowserCore**: > - Delete active/inactive feature provider protocols; keep only settings-driven `DefaultBrowserPromptFeatureFlagger` values. > - **Tests**: > - Remove tests/mocks for enablement booleans; update tests to use settings-only paths and constructor changes. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f04cf27. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 1a510ad commit ca753bc

File tree

15 files changed

+8
-185
lines changed

15 files changed

+8
-185
lines changed

SharedPackages/BrowserServicesKit/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,12 +467,6 @@ public enum SetAsDefaultAndAddToDockSubfeature: String, PrivacySubfeature {
467467

468468
// https://app.asana.com/1/137249556945/project/492600419927320/task/1210863200265479?focus=true
469469
case scheduledDefaultBrowserAndDockPromptsInactiveUser // macOS
470-
471-
// https://app.asana.com/1/137249556945/project/1206329551987282/task/1209304767941984?focus=true
472-
case scheduledDefaultBrowserPrompts // iOS
473-
474-
// https://app.asana.com/1/137249556945/project/1206329551987282/task/1210716028790591?focus=true
475-
case scheduledDefaultBrowserPromptsInactiveUser // iOS
476470
}
477471

478472
public enum OnboardingSubfeature: String, PrivacySubfeature {

iOS/Core/FeatureFlag.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,6 @@ public enum FeatureFlag: String {
154154
/// https://app.asana.com/1/137249556945/project/1211834678943996/task/1211866463389447
155155
case showSettingsCompleteSetupSection
156156

157-
/// https://app.asana.com/1/137249556945/project/1211834678943996/task/1211866611125558
158-
case scheduledSetDefaultBrowserPrompts
159-
160-
/// https://app.asana.com/1/137249556945/project/1211834678943996/task/1211866470962985
161-
case scheduledSetDefaultBrowserPromptsForInactiveUsers
162-
163157
/// https://app.asana.com/1/137249556945/project/1211834678943996/task/1211866607644644
164158
case canPromoteImportPasswordsInPasswordManagement
165159

@@ -318,8 +312,6 @@ extension FeatureFlag: FeatureFlagDescribing {
318312
.supportsAlternateStripePaymentFlow,
319313
.personalInformationRemoval,
320314
.createFireproofFaviconUpdaterSecureVaultInBackground,
321-
.scheduledSetDefaultBrowserPrompts,
322-
.scheduledSetDefaultBrowserPromptsForInactiveUsers,
323315
.duckAISearchParameter,
324316
.inactivityNotification,
325317
.daxEasterEggLogos,
@@ -488,10 +480,6 @@ extension FeatureFlag: FeatureFlagDescribing {
488480
return .remoteReleasable(.subfeature(AIChatSubfeature.keepSession))
489481
case .showSettingsCompleteSetupSection:
490482
return .remoteReleasable(.subfeature(OnboardingSubfeature.showSettingsCompleteSetupSection))
491-
case .scheduledSetDefaultBrowserPrompts:
492-
return .remoteReleasable(.subfeature(SetAsDefaultAndAddToDockSubfeature.scheduledDefaultBrowserPrompts))
493-
case .scheduledSetDefaultBrowserPromptsForInactiveUsers:
494-
return .remoteReleasable(.subfeature(SetAsDefaultAndAddToDockSubfeature.scheduledDefaultBrowserPromptsInactiveUser))
495483
case .supportsAlternateStripePaymentFlow:
496484
return .remoteReleasable(.subfeature(PrivacyProSubfeature.supportsAlternateStripePaymentFlow))
497485
case .personalInformationRemoval:

iOS/DuckDuckGo/AppServices/DefaultBrowserPromptService.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ final class DefaultBrowserPromptService {
5757
let promptActivityPixelHandler = DefaultBrowserPromptPixelHandler()
5858

5959
presenter = DefaultBrowserPromptFactory.makeDefaultBrowserPromptPresenter(
60-
featureFlagProvider: featureFlagAdapter,
6160
featureFlagSettingsProvider: featureFlagAdapter,
6261
promptActivityStore: promptTypeKeyValueFilesStore,
6362
userTypeProviding: userTypeManager,
@@ -74,15 +73,10 @@ final class DefaultBrowserPromptService {
7473

7574
func resume() {
7675
// Application has been launched or brought to foreground.
77-
guard shouldRecordActivity() else { return }
7876
Logger.defaultBrowserPrompt.debug("[Default Browser Prompt] - Record User Activity If Needed.")
7977
userActivityManager.recordActivity()
8078
}
8179

82-
private func shouldRecordActivity() -> Bool {
83-
// True if either active/inactive prompt features is enabled
84-
featureFlagAdapter.isDefaultBrowserPromptsForActiveUsersFeatureEnabled || featureFlagAdapter.isDefaultBrowserPromptsForInactiveUsersFeatureEnabled
85-
}
8680
}
8781

8882
// MARK: - Adapters

iOS/DuckDuckGo/DefaultBrowserPrompt/DefaultBrowserPromptFeatureFlagAdapter.swift

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import Core
2222
import BrowserServicesKit
2323
import SetDefaultBrowserCore
2424

25-
final class DefaultBrowserPromptFeatureFlagAdapter: DefaultBrowserPromptFeatureFlagProvider, DefaultBrowserPromptFeatureFlagSettingsProvider {
25+
final class DefaultBrowserPromptFeatureFlagAdapter: DefaultBrowserPromptFeatureFlagSettingsProvider {
2626

2727
private let featureFlagger: FeatureFlagger
2828
private let privacyConfigurationManager: PrivacyConfigurationManaging
@@ -32,14 +32,6 @@ final class DefaultBrowserPromptFeatureFlagAdapter: DefaultBrowserPromptFeatureF
3232
self.privacyConfigurationManager = privacyConfigurationManager
3333
}
3434

35-
public var isDefaultBrowserPromptsForActiveUsersFeatureEnabled: Bool {
36-
featureFlagger.isFeatureOn(FeatureFlag.scheduledSetDefaultBrowserPrompts)
37-
}
38-
39-
public var isDefaultBrowserPromptsForInactiveUsersFeatureEnabled: Bool {
40-
featureFlagger.isFeatureOn(FeatureFlag.scheduledSetDefaultBrowserPromptsForInactiveUsers)
41-
}
42-
4335
public var defaultBrowserPromptFeatureSettings: [String: Any] {
4436
privacyConfigurationManager.privacyConfig.settings(for: .setAsDefaultAndAddToDock)
4537
}

iOS/DuckDuckGo/DefaultBrowserPromptDebugView/DefaultBrowserPromptDebugView.swift

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@ struct DefaultBrowserPromptDebugView: View {
3333
}
3434

3535
var body: some View {
36-
if model.isFeatureEnabled {
37-
settingsView
38-
} else {
39-
Text(verbatim: "Feature Disabled. Ensure Internal user is On")
40-
}
36+
settingsView
4137
}
4238

4339
@ViewBuilder
@@ -99,7 +95,6 @@ struct DefaultBrowserPromptDebugView: View {
9995
}
10096
}
10197
}
102-
.disabled(!model.isFeatureEnabled)
10398
.navigationTitle("Default Browser Prompt")
10499
}
105100
}
@@ -121,7 +116,6 @@ final class DefaultBrowserPromptDebugViewModel: ObservableObject {
121116
return formatter
122117
}()
123118

124-
@Published private(set) var isFeatureEnabled: Bool
125119
@Published private(set) var activeDaysCount: Int
126120
@Published private(set) var debugLog: DebugLog = .init()
127121
@Published var defaultBrowserPromptUserType: DefaultBrowserPromptUserType? {
@@ -155,7 +149,6 @@ final class DefaultBrowserPromptDebugViewModel: ObservableObject {
155149
currentDate = currentDateDebugStore.simulatedTodayDate
156150
formattedCurrentDate = Self.dateFormatter.string(from: currentDateDebugStore.simulatedTodayDate)
157151
activeDaysCount = userActivityStore.currentActivity().numberOfActiveDays
158-
isFeatureEnabled = self.featureFlagger.isDefaultBrowserPromptsForActiveUsersFeatureEnabled
159152
makeDebugLog()
160153
}
161154

@@ -180,7 +173,6 @@ final class DefaultBrowserPromptDebugViewModel: ObservableObject {
180173
defaultBrowserPromptUserType = userTypeDebugStore.userType()
181174
currentDate = currentDateDebugStore.simulatedTodayDate
182175
activeDaysCount = userActivityStore.currentActivity().numberOfActiveDays
183-
isFeatureEnabled = self.featureFlagger.isDefaultBrowserPromptsForActiveUsersFeatureEnabled
184176
makeDebugLog()
185177
}
186178

iOS/DuckDuckGoTests/DefaultBrowserPrompt/DefaultBrowserPromptFeatureFlagAdapterTests.swift

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,6 @@ struct DefaultBrowserPromptFeatureFlagAdapterTests {
2828
private var featureFlaggerMock = MockFeatureFlagger(internalUserDecider: MockInternalUserDecider())
2929
private var privacyConfigurationManagerMock = MockPrivacyConfigurationManager()
3030

31-
@Test("Check Default Browser Prompts For Active Users Method Is Forwarded To Feature Flagger")
32-
func checkIsActiveUsersFeatureOnIsForwardedToFeatureFlagger() {
33-
// GIVEN
34-
featureFlaggerMock.enabledFeatureFlags = [.scheduledSetDefaultBrowserPrompts]
35-
let sut = DefaultBrowserPromptFeatureFlagAdapter(featureFlagger: featureFlaggerMock, privacyConfigurationManager: privacyConfigurationManagerMock)
36-
37-
// WHEN
38-
let result = sut.isDefaultBrowserPromptsForActiveUsersFeatureEnabled
39-
40-
// THEN
41-
#expect(result)
42-
}
43-
44-
@Test("Check Default Browser Prompts For Inactive Users Method Is Forwarded To Feature Flagger")
45-
func checkIsInactiveUsersFeatureOnIsForwardedToFeatureFlagger() {
46-
// GIVEN
47-
featureFlaggerMock.enabledFeatureFlags = [.scheduledSetDefaultBrowserPromptsForInactiveUsers]
48-
let sut = DefaultBrowserPromptFeatureFlagAdapter(featureFlagger: featureFlaggerMock, privacyConfigurationManager: privacyConfigurationManagerMock)
49-
50-
// WHEN
51-
let result = sut.isDefaultBrowserPromptsForInactiveUsersFeatureEnabled
52-
53-
// THEN
54-
#expect(result)
55-
}
56-
5731
@Test("Check Method is Dispatched To Feature Flagger")
5832
func checkMethodIsDispatchedToPrivacyConfigurationManager() throws {
5933
// GIVEN

iOS/LocalPackages/SetDefaultBrowser/Sources/SetDefaultBrowserCore/FeatureFlagger/DefaultBrowserPromptFeatureFlagger.swift

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,6 @@
1919

2020
import Foundation
2121

22-
public protocol DefaultBrowserPromptActiveUserFeatureFlagProvider {
23-
/// A Boolean value indicating whether Set Default Browser Prompts are enabled for active users.
24-
/// - Returns: `true` if the feature is enabled; otherwise, `false`.
25-
var isDefaultBrowserPromptsForActiveUsersFeatureEnabled: Bool { get }
26-
}
27-
28-
public protocol DefaultBrowserPromptInactiveUserFeatureFlagProvider {
29-
/// A Boolean value indicating whether Set Default Browser Prompts are enabled for inactive users.
30-
/// - Returns: `true` if the feature is enabled; otherwise, `false`.
31-
var isDefaultBrowserPromptsForInactiveUsersFeatureEnabled: Bool { get }
32-
}
33-
34-
public typealias DefaultBrowserPromptFeatureFlagProvider = DefaultBrowserPromptActiveUserFeatureFlagProvider & DefaultBrowserPromptInactiveUserFeatureFlagProvider
35-
3622
public protocol DefaultBrowserPromptFeatureFlagSettingsProvider {
3723
// A dictionary representing the settings for the feature.
3824
var defaultBrowserPromptFeatureSettings: [String: Any] { get }
@@ -62,7 +48,7 @@ public enum DefaultBrowserPromptFeatureSettings: String {
6248
}
6349
}
6450

65-
package protocol DefaultBrowserPromptActiveUserFeatureFlagger: DefaultBrowserPromptActiveUserFeatureFlagProvider {
51+
package protocol DefaultBrowserPromptActiveUserFeatureFlagger {
6652
/// The number of active days to wait after app installation before showing the first modal for active users. Default is 1.
6753
var firstActiveModalDelayDays: Int { get }
6854
/// The number of active days to wait after the first modal has been shown before displaying the second modal for active users. Default is 4.
@@ -71,7 +57,7 @@ package protocol DefaultBrowserPromptActiveUserFeatureFlagger: DefaultBrowserPro
7157
var subsequentActiveModalRepeatIntervalDays: Int { get }
7258
}
7359

74-
package protocol DefaultBrowserPromptInactiveUserFeatureFlagger: DefaultBrowserPromptInactiveUserFeatureFlagProvider {
60+
package protocol DefaultBrowserPromptInactiveUserFeatureFlagger {
7561
/// The setting for the number of days to wait after app installation before showing the modal to inactive users. Default to 28.
7662
var inactiveModalNumberOfDaysSinceInstall: Int { get }
7763
/// The setting for the number of inactive days to wait before showing the modal to inactive users. Default to 7.
@@ -82,26 +68,16 @@ package typealias DefaultBrowserPromptFeatureFlagger = DefaultBrowserPromptActiv
8268

8369
package final class DefaultBrowserPromptFeatureFlag {
8470
private let settingsProvider: DefaultBrowserPromptFeatureFlagSettingsProvider
85-
private let featureFlagProvider: DefaultBrowserPromptFeatureFlagProvider
8671

87-
package init(settingsProvider: DefaultBrowserPromptFeatureFlagSettingsProvider, featureFlagProvider: DefaultBrowserPromptFeatureFlagProvider) {
72+
package init(settingsProvider: DefaultBrowserPromptFeatureFlagSettingsProvider) {
8873
self.settingsProvider = settingsProvider
89-
self.featureFlagProvider = featureFlagProvider
9074
}
9175
}
9276

9377
// MARK: - DefaultBrowserPromptFeatureFlagger
9478

9579
extension DefaultBrowserPromptFeatureFlag: DefaultBrowserPromptFeatureFlagger {
9680

97-
public var isDefaultBrowserPromptsForActiveUsersFeatureEnabled: Bool {
98-
featureFlagProvider.isDefaultBrowserPromptsForActiveUsersFeatureEnabled
99-
}
100-
101-
public var isDefaultBrowserPromptsForInactiveUsersFeatureEnabled: Bool {
102-
featureFlagProvider.isDefaultBrowserPromptsForInactiveUsersFeatureEnabled
103-
}
104-
10581
package var firstActiveModalDelayDays: Int {
10682
getSettings(.firstActiveModalDelayDays)
10783
}

iOS/LocalPackages/SetDefaultBrowser/Sources/SetDefaultBrowserCore/PromptDecider/DefaultBrowserPromptTypeDecider+ActiveUser.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ extension DefaultBrowserPromptTypeDecider {
4242

4343

4444
func promptType() -> DefaultBrowserPromptType? {
45-
// If Feature is disabled return nil
46-
guard featureFlagger.isDefaultBrowserPromptsForActiveUsersFeatureEnabled else {
47-
Logger.defaultBrowserPrompt.debug("[Default Browser Prompt] - Feature disabled.")
48-
return nil
49-
}
50-
5145
guard let userType = userTypeProvider.currentUserType() else {
5246
Logger.defaultBrowserPrompt.debug("[Default Browser Prompt] - Failed to determine Active user type. Will not show prompt.")
5347
return nil

iOS/LocalPackages/SetDefaultBrowser/Sources/SetDefaultBrowserCore/PromptDecider/DefaultBrowserPromptTypeDecider+InactiveUser.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ extension DefaultBrowserPromptTypeDecider {
3838
}
3939

4040
func promptType() -> DefaultBrowserPromptType? {
41-
guard featureFlagger.isDefaultBrowserPromptsForInactiveUsersFeatureEnabled else { return nil }
42-
4341
// Conditions to show prompt for inactive users:
4442
// 1. The user has not seen this modal ever.
4543
// 2. User has been inactive for at least seven days.

iOS/LocalPackages/SetDefaultBrowser/Sources/SetDefaultBrowserTestSupport/MockDefaultBrowserPromptFeatureFlag.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ import SetDefaultBrowserCore
2323
public final class MockDefaultBrowserPromptFeatureFlag: DefaultBrowserPromptFeatureFlagger {
2424
public init() {}
2525

26-
public var isDefaultBrowserPromptsForActiveUsersFeatureEnabled: Bool = true
27-
28-
public var isDefaultBrowserPromptsForInactiveUsersFeatureEnabled: Bool = true
29-
3026
public var firstActiveModalDelayDays: Int = 1
3127

3228
public var secondActiveModalDelayDays: Int = 2
@@ -38,14 +34,6 @@ public final class MockDefaultBrowserPromptFeatureFlag: DefaultBrowserPromptFeat
3834
public var inactiveModalNumberOfInactiveDays: Int = 7
3935
}
4036

41-
package final class MockDefaultBrowserPromptFeatureFlagProvider: DefaultBrowserPromptFeatureFlagProvider {
42-
package var isDefaultBrowserPromptsForActiveUsersFeatureEnabled: Bool = true
43-
44-
package var isDefaultBrowserPromptsForInactiveUsersFeatureEnabled: Bool = true
45-
46-
package init() {}
47-
}
48-
4937
package final class MockDefaultBrowserPromptFeatureFlagSettingsProvider: DefaultBrowserPromptFeatureFlagSettingsProvider {
5038
package var defaultBrowserPromptFeatureSettings: [String: Any] = [:]
5139

0 commit comments

Comments
 (0)