Skip to content

Commit

Permalink
feat: Demo App emulates login scenarios
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziodemaria committed Nov 29, 2024
1 parent e2a2476 commit e15eeff
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 84 deletions.
4 changes: 4 additions & 0 deletions ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
733219BF2BE3C11100747AC2 /* ConfidenceOpenFeature in Frameworks */ = {isa = PBXBuildFile; productRef = 733219BE2BE3C11100747AC2 /* ConfidenceOpenFeature */; };
735EADF52CF9B64E007BC42C /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 735EADF42CF9B64E007BC42C /* LoginView.swift */; };
C770C99A2A739FBC00C2AC8C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C770C9962A739FBC00C2AC8C /* Preview Assets.xcassets */; };
C770C99B2A739FBC00C2AC8C /* ConfidenceDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C770C9972A739FBC00C2AC8C /* ConfidenceDemoApp.swift */; };
C770C99C2A739FBC00C2AC8C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C770C9982A739FBC00C2AC8C /* ContentView.swift */; };
Expand Down Expand Up @@ -35,6 +36,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
735EADF42CF9B64E007BC42C /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
C770C9682A739FA000C2AC8C /* ConfidenceDemoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ConfidenceDemoApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
C770C9782A739FA100C2AC8C /* ConfidenceDemoAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ConfidenceDemoAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
C770C9822A739FA100C2AC8C /* ConfidenceDemoAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ConfidenceDemoAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -103,6 +105,7 @@
C770C9AA2A73A06000C2AC8C /* Info.plist */,
C770C9992A739FBC00C2AC8C /* Assets.xcassets */,
C770C9972A739FBC00C2AC8C /* ConfidenceDemoApp.swift */,
735EADF42CF9B64E007BC42C /* LoginView.swift */,
C770C9982A739FBC00C2AC8C /* ContentView.swift */,
C770C9952A739FBC00C2AC8C /* Preview Content */,
);
Expand Down Expand Up @@ -285,6 +288,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
735EADF52CF9B64E007BC42C /* LoginView.swift in Sources */,
C770C99C2A739FBC00C2AC8C /* ContentView.swift in Sources */,
C770C99B2A739FBC00C2AC8C /* ConfidenceDemoApp.swift in Sources */,
);
Expand Down
78 changes: 41 additions & 37 deletions ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
Original file line number Diff line number Diff line change
@@ -1,50 +1,54 @@
import Confidence
import SwiftUI

class Status: ObservableObject {
enum State {
case unknown
case ready
case error(Error?)
}

@Published var state: State = .unknown
}


@main
struct ConfidenceDemoApp: App {
@StateObject private var lifecycleObserver = ConfidenceAppLifecycleProducer()
@AppStorage("loggedUser") private var loggedUser: String?

private let confidence: Confidence
private let flaggingState = ExperimentationFlags()

init() {
confidence = Confidence
.Builder(clientSecret: ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "", loggerLevel: .TRACE)
.build()

if let loggedUser = loggedUser {
confidence.putContextLocal(context: ["user_id": ConfidenceValue.init(string: loggedUser)], removeKeys: [])
do {
try confidence.activate()
flaggingState.state = .ready
} catch {
print("Error during activation of Confidence")
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription))
}
}
}

var body: some Scene {
WindowGroup {
let secret = ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? ""
let confidence = Confidence.Builder(clientSecret: secret, loggerLevel: .TRACE)
.withContext(initialContext: [
"targeting_key": ConfidenceValue(string: UUID.init().uuidString),
"user_id": .init(string: "user2")
])
.build()

let status = Status()

ContentView(confidence: confidence, status: status)
.task {
do {
confidence.track(producer: lifecycleObserver)
try await self.setup(confidence: confidence)
status.state = .ready
} catch {
status.state = .error(error)
print(error.localizedDescription)
}
}
if loggedUser == nil {
LoginView(confidence: confidence)
.environmentObject(flaggingState)
} else {
ContentView(confidence: confidence)
.environmentObject(flaggingState)
}
}
}
}

extension ConfidenceDemoApp {
func setup(confidence: Confidence) async throws {
try await confidence.fetchAndActivate()
class ExperimentationFlags: ObservableObject {
@Published var state: State = .notReady

enum State: Equatable {
case unknown
case notReady
case loading
case ready
case error(CustomError?)
}

public struct CustomError: Error, Equatable {
let message: String
}
}
72 changes: 32 additions & 40 deletions ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,51 @@ import Confidence
import Combine

struct ContentView: View {
@ObservedObject var status: Status
@StateObject var text = DisplayText()
@StateObject var color = FlagColor()
@EnvironmentObject var status: ExperimentationFlags
@AppStorage("loggedUser") private var loggedUser: String?
@State var isLoggingOut: Bool = false

private let color: Color
private let confidence: Confidence

init(confidence: Confidence, status: Status) {
init(confidence: Confidence) {
self.confidence = confidence
self.status = status
self.color = ContentView.getColor(color: confidence.getValue(key: "swift-demoapp.color", defaultValue: "Gray"))
}

var body: some View {
if case .ready = status.state {
NavigationStack {
VStack {
Image(systemName: "flag")
.imageScale(.large)
.foregroundColor(color.color)
.padding(10)
Text(text.text)
Button("Get remote flag value") {
text.text = confidence.getValue(key: "swift-demoapp.color", defaultValue: "ERROR")
if text.text == "Green" {
color.color = .green
} else if text.text == "Yellow" {
color.color = .yellow
} else {
color.color = .red
}
}
Button("Flush 🚽") {
confidence.flush()
}
Text("Hello World!")
.foregroundStyle(color)
}
.padding()
} else if case .error(let error) = status.state {
VStack {
Text("Provider Error")
Text(error?.localizedDescription ?? "An unknow error has occured.")
.foregroundColor(.red)
Button("Logout") {
loggedUser = nil
status.state = .loading
Task {
await confidence.removeContext(key: "user_id")
status.state = .ready
}
loggedUser = nil
isLoggingOut = true
}
} else {
VStack {
ProgressView()
.navigationDestination(isPresented: $isLoggingOut) {
LoginView(confidence: confidence)
}
}
}
}

class DisplayText: ObservableObject {
@Published var text = "Hello World!"
}


class FlagColor: ObservableObject {
@Published var color: Color = .black
private static func getColor(color: String) -> Color {
switch color {
case "Green":
.green
case "Yellow":
.yellow
case "Gray":
.gray
default:
.red
}
}
}
54 changes: 54 additions & 0 deletions ConfidenceDemoApp/ConfidenceDemoApp/LoginView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import SwiftUI
import Confidence

struct LoginView: View {
@EnvironmentObject var status: ExperimentationFlags
@AppStorage("loggedUser") private var loggedUser: String?
@State private var loginCompleted = false
@State private var flagsLoaded = false
@State private var loggingIn = false

private let confidence: Confidence

init(confidence: Confidence) {
self.confidence = confidence
}

var body: some View {
NavigationStack {
VStack {
Spacer()
Button("Login as user1") {
status.state = .loading
// Load flags
Task {
try? await Task.sleep(nanoseconds: 5 * 1_000_000_000) // Sleep for 3 seconds
await confidence.putContext(context: ["user_id": .init(string: "user1")])
status.state = .ready
}
// Load everything else for this user
Task {
loggingIn = true
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) // Sleep for 3 seconds
loggedUser = "user1"
loggingIn = false
loginCompleted = true
}
}
.navigationDestination(isPresented: $loginCompleted) {
ContentView(confidence: confidence)
}
if (loggingIn) {
ProgressView()
}
Spacer()
}
}
}
private var combinedBinding: Binding<Bool> {
Binding(
get: { loginCompleted && flagsLoaded },
set: { _ in } // No-op; navigation state is derived from other properties
)
}
}
2 changes: 1 addition & 1 deletion Sources/Confidence/Apply/FlagApplierWithRetries.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ final class FlagApplierWithRetries: FlagApplier {
return
}

debugLogger?.logFlags(action: "Apply", flag: flagName)
debugLogger?.logFlags(action: "Apply", flag: flagName, resolveToken: resolveToken)
self.writeToFile(data: data)
await triggerBatch()
}
Expand Down
14 changes: 8 additions & 6 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ public class Confidence: ConfidenceEventSender {
}
let savedFlags = try storage.load(defaultValue: FlagResolution.EMPTY)
cache = savedFlags
debugLogger?.logFlags(action: "Activate", flag: "")
debugLogger?.logMessage(
message: "[Activating stored cache]",
isWarning: false
)
}
}

Expand Down Expand Up @@ -111,7 +114,6 @@ public class Confidence: ConfidenceEventSender {
flags: resolvedFlags.resolvedValues,
resolveToken: resolvedFlags.resolveToken ?? ""
)
debugLogger?.logFlags(action: "Fetch", flag: "")
try storage.save(data: resolution)
}

Expand Down Expand Up @@ -252,7 +254,7 @@ public class Confidence: ConfidenceEventSender {
}
contextSubject.value = map
debugLogger?.logContext(
action: "PutContext",
action: "PutContextLocal",
context: contextSubject.value)
}
}
Expand All @@ -270,7 +272,7 @@ public class Confidence: ConfidenceEventSender {
do {
try await fetchAndActivate()
debugLogger?.logContext(
action: "PutContext & FetchAndActivate",
action: "PutContext - Done with FetchAndActivate",
context: contextSubject.value)
} catch {
debugLogger?.logMessage(
Expand All @@ -296,7 +298,7 @@ public class Confidence: ConfidenceEventSender {
do {
try await self.fetchAndActivate()
debugLogger?.logContext(
action: "PutContext & FetchAndActivate",
action: "PutContext - Done with FetchAndActivate",
context: contextSubject.value)
} catch {
debugLogger?.logMessage(
Expand All @@ -318,7 +320,7 @@ public class Confidence: ConfidenceEventSender {
do {
try await self.fetchAndActivate()
debugLogger?.logContext(
action: "RemoveContextKey & FetchAndActivate",
action: "RemoveContext - Done with FetchAndActivate",
context: contextSubject.value)
} catch {
debugLogger?.logMessage(
Expand Down
10 changes: 10 additions & 0 deletions Sources/Confidence/DebugLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ internal protocol DebugLogger {
func logEvent(action: String, event: ConfidenceEvent?)
func logMessage(message: String, isWarning: Bool)
func logFlags(action: String, flag: String)
func logFlags(action: String, flag: String, resolveToken: String)
func logFlags(action: String, context: ConfidenceStruct)
func logContext(action: String, context: ConfidenceStruct)
func logResolveDebugURL(flagName: String, context: ConfidenceStruct)
}
Expand Down Expand Up @@ -59,6 +61,14 @@ internal class DebugLoggerImpl: DebugLogger {
log(messageLevel: .TRACE, message: "[\(action)] \(flag)")
}

func logFlags(action: String, flag: String, resolveToken: String) {
log(messageLevel: .TRACE, message: "[\(action)] \(flag) - \(resolveToken)")
}

func logFlags(action: String, context: ConfidenceStruct) {
log(messageLevel: .TRACE, message: "[\(action)] \(context)")
}

func logContext(action: String, context: ConfidenceStruct) {
log(messageLevel: .TRACE, message: "[\(action)] \(context)")
}
Expand Down

0 comments on commit e15eeff

Please sign in to comment.