-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for local overrides for feature flags (#3545)
Task/Issue URL: https://app.asana.com/0/72649045549333/1208716221426945/f Tech Design URL: https://app.asana.com/0/481882893211075/1208716218352496/f Description: This change adds support for overriding feature flags locally. FeatureFlagLocalOverrides is instantiated and passed to DefaultFeatureFlagger to allow local overrides. A dedicated submenu within Debug menu was added to handle feature flags state. The menu is dynamically populated and only available to internal users. Local overrides have no effect when default user flag is disabled. Currently only 1 flag uses the local overrides: the new HTML New Tab Page flag. It's a WIP feature and the feature flag currently only presents an empty webview in place of the NTP.
- Loading branch information
Showing
18 changed files
with
237 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
DuckDuckGo/InternalUserDecider/FeatureFlagOverridesMenu.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// | ||
// FeatureFlagOverridesMenu.swift | ||
// | ||
// Copyright © 2024 DuckDuckGo. All rights reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
|
||
import AppKit | ||
import BrowserServicesKit | ||
import FeatureFlags | ||
|
||
final class FeatureFlagOverridesMenu: NSMenu { | ||
|
||
let featureFlagger: FeatureFlagger | ||
|
||
let setInternalUserStateItem: NSMenuItem = { | ||
let item = NSMenuItem(title: "Set Internal User State First") | ||
item.isEnabled = false | ||
return item | ||
}() | ||
|
||
init(featureFlagOverrides: FeatureFlagger) { | ||
self.featureFlagger = featureFlagOverrides | ||
super.init(title: "") | ||
|
||
buildItems { | ||
setInternalUserStateItem | ||
NSMenuItem.separator() | ||
|
||
FeatureFlag.allCases.filter(\.supportsLocalOverriding).map { flag in | ||
NSMenuItem( | ||
title: "\(flag.rawValue) (default: \(featureFlagger.isFeatureOn(for: flag, allowOverride: false) ? "on" : "off"))", | ||
action: #selector(toggleFeatureFlag(_:)), | ||
target: self, | ||
representedObject: flag | ||
) | ||
} | ||
|
||
NSMenuItem.separator() | ||
NSMenuItem(title: "Remove All Overrides", action: #selector(resetAllOverrides(_:))).targetting(self) | ||
} | ||
} | ||
|
||
required init(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
override func update() { | ||
super.update() | ||
|
||
items.forEach { item in | ||
guard let flag = item.representedObject as? FeatureFlag else { | ||
return | ||
} | ||
item.isHidden = !featureFlagger.internalUserDecider.isInternalUser | ||
item.title = "\(flag.rawValue) (default: \(defaultValue(for: flag)), override: \(overrideValue(for: flag)))" | ||
let override = featureFlagger.localOverrides?.override(for: flag) | ||
item.state = override == true ? .on : .off | ||
|
||
if override != nil { | ||
item.submenu = NSMenu(items: [ | ||
NSMenuItem( | ||
title: "Remove Override", | ||
action: #selector(resetOverride(_:)), | ||
target: self, | ||
representedObject: flag | ||
) | ||
]) | ||
} else { | ||
item.submenu = nil | ||
} | ||
} | ||
|
||
setInternalUserStateItem.isHidden = featureFlagger.internalUserDecider.isInternalUser | ||
} | ||
|
||
private func defaultValue(for flag: FeatureFlag) -> String { | ||
featureFlagger.isFeatureOn(for: flag, allowOverride: false) ? "on" : "off" | ||
} | ||
|
||
private func overrideValue(for flag: FeatureFlag) -> String { | ||
guard let override = featureFlagger.localOverrides?.override(for: flag) else { | ||
return "none" | ||
} | ||
return override ? "on" : "off" | ||
} | ||
|
||
@objc func toggleFeatureFlag(_ sender: NSMenuItem) { | ||
guard let featureFlag = sender.representedObject as? FeatureFlag else { return } | ||
featureFlagger.localOverrides?.toggleOverride(for: featureFlag) | ||
} | ||
|
||
@objc func resetOverride(_ sender: NSMenuItem) { | ||
guard let featureFlag = sender.representedObject as? FeatureFlag else { return } | ||
featureFlagger.localOverrides?.clearOverride(for: featureFlag) | ||
} | ||
|
||
@objc func resetAllOverrides(_ sender: NSMenuItem) { | ||
featureFlagger.localOverrides?.clearAllOverrides(for: FeatureFlag.self) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.