Skip to content

Commit

Permalink
feat: More DemoApp tests and cases
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziodemaria committed Dec 3, 2024
1 parent d62cb2b commit 669e856
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 49 deletions.
53 changes: 42 additions & 11 deletions ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
Original file line number Diff line number Diff line change
@@ -1,31 +1,48 @@
import Confidence
import SwiftUI

@main
struct ConfidenceDemoApp: App {
@AppStorage("loggedUser") private var loggedUser: String?
@AppStorage("appVersion") private var appVersion = 0

private let confidence: Confidence
private let flaggingState = ExperimentationFlags()
private let secret = ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "<Empty Secret>"

init() {
@AppStorage("appVersion") var appVersion = 0
@AppStorage("loggedUser") var loggedUser: String?
appVersion += 1 // Simulate update of the app on every new run
var context = ["app_version": ConfidenceValue.init(integer: appVersion)]
if let user = loggedUser {
context["user_id"] = ConfidenceValue.init(string: user)
}

confidence = Confidence
.Builder(clientSecret: ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "", loggerLevel: .TRACE)
.Builder(clientSecret: secret, loggerLevel: .TRACE)
.withContext(initialContext: context)
.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))
}
do {
// DANGER: here we are activating anything in storage, could be data of a different user
try confidence.activate() // Activating the existing cache, in case the new fetch fails i.e. offline mode
} catch {
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription))
}
flaggingState.color = ContentView.getColor(
color: confidence.getValue(
key: "swift-demoapp.color",
defaultValue: "Gray"))

self.appVersion = appVersion
self.loggedUser = loggedUser
updateConfidence()
}

var body: some Scene {
WindowGroup {
Text("Client secret: \(secret)")
.font(.caption)
if loggedUser == nil {
LoginView(confidence: confidence)
.environmentObject(flaggingState)
Expand All @@ -35,9 +52,23 @@ struct ConfidenceDemoApp: App {
}
}
}

private func updateConfidence() {
Task {
do {
flaggingState.state = .loading
try await Task.sleep(nanoseconds: 2 * 1_000_000_000)
try await confidence.fetchAndActivate()
flaggingState.state = .ready
} catch {
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription))
}
}
}
}

class ExperimentationFlags: ObservableObject {
var color: Color = .red // This is set on applicaaton start, and reset on user logout
@Published var state: State = .notReady

enum State: Equatable {
Expand Down
78 changes: 57 additions & 21 deletions ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,99 @@ import Confidence
import Combine

struct ContentView: View {
@EnvironmentObject var status: ExperimentationFlags
@EnvironmentObject var flaggingState: ExperimentationFlags
@AppStorage("loggedUser") private var loggedUser: String?
@State var isLoggingOut: Bool = false
@State var loggedOut: Bool = false
@State private var isLoggingOut = false
@State private var loggedOut = false
@State private var textColor = Color.red

private let confidence: Confidence

init(confidence: Confidence) {
init(confidence: Confidence, color: Color? = nil) {
self.confidence = confidence
}

var body: some View {
NavigationStack {
if (status.state == .loading && !isLoggingOut) {
if flaggingState.state == .loading && !isLoggingOut {
VStack {
Spacer()
Text("Welcome \(loggedUser ?? "?")")
Text("Login successful: \(loggedUser ?? "?")")
Text("We are preparing your experience...")
ProgressView()
Spacer()
}
} else {
VStack {
Text("Hello World!")
.font(.largeTitle)
.foregroundStyle(ContentView.getColor(color: confidence.getValue(key: "swift-demoapp.color", defaultValue: "Gray")))
if flaggingState.state == .ready {
VStack {
Spacer()
if let user = loggedUser {
let eval = confidence.getEvaluation(key: "swift-demoapp.color", defaultValue: "Gray")
Text("Hello \(user)")
.font(.largeTitle)
.foregroundStyle(ContentView.getColor(color: eval.value))
.padding()
}
}
}
NavigationLink(destination: AboutPage()) {
Text("Navigate")
}
.padding()
Button("Logout") {
isLoggingOut = true

loggedUser = nil
status.state = .loading
flaggingState.state = .loading
flaggingState.color = .gray
Task {
await confidence.removeContext(key: "user_id")
status.state = .ready
flaggingState.state = .ready
}
loggedUser = nil
loggedOut = true
}
.navigationDestination(isPresented: $loggedOut) {
LoginView(confidence: confidence)
}
Spacer()
}
ZStack {
VStack {
Spacer()
Text("This text is set on onAppear, doesn't wait for reconciliation")
.font(.caption)
.foregroundStyle(textColor)
Text("This text dynamically changes on flags fetch")
.font(.caption)
.foregroundStyle(ContentView.getColor(
color: confidence.getValue(
key: "swift-demoapp.color",
defaultValue: "Gray")))
Text("This text is fixed from user session start, and before any flags fetch")
.font(.caption)
.foregroundStyle(flaggingState.color)
}
}.onAppear {
let eval = confidence.getEvaluation(key: "swift-demoapp.color", defaultValue: "Gray")
print(">> Evaluation reason: \(eval)")
textColor = ContentView.getColor(color: eval.value)
}
}
}

private static func getColor(color: String) -> Color {
static func getColor(color: String) -> Color {
switch color {
case "Green":
.green
return .green
case "Yellow":
.yellow
return .yellow
case "Gray":
.gray
return .gray
default:
.red
return .red
}
}
}

struct AboutPage: View {
var body: some View {
Text("About Page")
}
}
34 changes: 21 additions & 13 deletions ConfidenceDemoApp/ConfidenceDemoApp/LoginView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SwiftUI
import Confidence

struct LoginView: View {
@EnvironmentObject var status: ExperimentationFlags
@EnvironmentObject var flaggingState: ExperimentationFlags
@AppStorage("loggedUser") private var loggedUser: String?
@State private var loginCompleted = false
@State private var flagsLoaded = false
Expand All @@ -13,23 +13,35 @@ struct LoginView: View {
init(confidence: Confidence) {
self.confidence = confidence
}

var body: some View {
NavigationStack {
VStack {
Spacer()
Button("Login as user1") {
status.state = .loading
do {
try confidence.activate()
} catch {
flaggingState.state = .error(
ExperimentationFlags.CustomError(message: error.localizedDescription))
}

flaggingState.color = ContentView.getColor(
color: confidence.getValue(key: "swift-demoapp.color", defaultValue: "Gray")
)

// Load flags
Task {
try? await Task.sleep(nanoseconds: 5 * 1_000_000_000) // Sleep for 3 seconds
flaggingState.state = .loading
try? await Task.sleep(nanoseconds: 5 * 1_000_000_000)
await confidence.putContext(context: ["user_id": .init(string: "user1")])
status.state = .ready
flaggingState.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
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000)
loggedUser = "user1"
loggingIn = false
loginCompleted = true
Expand All @@ -38,17 +50,13 @@ struct LoginView: View {
.navigationDestination(isPresented: $loginCompleted) {
ContentView(confidence: confidence)
}
if (loggingIn) {

if loggingIn {
ProgressView()
}

Spacer()
}
}
}
private var combinedBinding: Binding<Bool> {
Binding(
get: { loginCompleted && flagsLoaded },
set: { _ in } // No-op; navigation state is derived from other properties
)
}
}
4 changes: 0 additions & 4 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ public class Confidence: ConfidenceEventSender {
}
let savedFlags = try storage.load(defaultValue: FlagResolution.EMPTY)
cache = savedFlags
debugLogger?.logMessage(
message: "[Activating stored cache]",
isWarning: false
)
}
}

Expand Down

0 comments on commit 669e856

Please sign in to comment.