From 6a861befb9886542f947fe7a51e7ad4751c00cbb Mon Sep 17 00:00:00 2001 From: Joan Disho Date: Wed, 26 Feb 2025 02:11:11 +0100 Subject: [PATCH] Add usability study TestFlight deployment with secure OpenAI config --- .../workflows/user-study-beta-deployment.yml | 54 +++++++++++++++++++ .../UserStudy/UserStudyWelcomeView.swift | 22 ++++++++ LLMonFHIR/Onboarding/OnboardingFlow.swift | 4 +- LLMonFHIR/SharedContext/FeatureFlags.swift | 8 ++- fastlane/Fastfile | 31 +++++++++++ 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/user-study-beta-deployment.yml diff --git a/.github/workflows/user-study-beta-deployment.yml b/.github/workflows/user-study-beta-deployment.yml new file mode 100644 index 0000000..860c65e --- /dev/null +++ b/.github/workflows/user-study-beta-deployment.yml @@ -0,0 +1,54 @@ +# +# This source file is part of the Stanford LLM on FHIR project +# +# SPDX-FileCopyrightText: 2025 Stanford University +# +# SPDX-License-Identifier: MIT +# + + +name: User Study Beta Deployment +on: + push: + branches: + - user-study-deployment + workflow_dispatch: +jobs: + buildandtest: + name: Build and Test + uses: ./.github/workflows/build-and-test.yml + permissions: + contents: read + actions: read + security-events: write + secrets: inherit + setup-openai-config: + name: Setup OpenAI Configuration + runs-on: macos-latest + needs: buildandtest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Setup OpenAI Configuration + run: > + echo -n "${{ secrets.OPENAI_CONFIG_PLIST_BASE64 }}" | base64 --decode -o + "LLMonFHIR/Supporting Files/OpenAIConfig.plist" + - name: Upload Configuration as Artifact + uses: actions/upload-artifact@v3 + with: + name: openai-config + path: LLMonFHIR/Supporting Files/OpenAIConfig.plist + retention-days: 1 + userstudytestflightdeployment: + name: User Study TestFlight Deployment + needs: + - buildandtest + - setup-openai-config + uses: StanfordBDHG/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 + permissions: + contents: read + with: + runsonlabels: '["macOS", "self-hosted"]' + setupsigning: true + fastlanelane: beta_user_study + secrets: inherit diff --git a/LLMonFHIR/FHIRInterpretation/MultipleResources/UserStudy/UserStudyWelcomeView.swift b/LLMonFHIR/FHIRInterpretation/MultipleResources/UserStudy/UserStudyWelcomeView.swift index 26adff1..8e9a40f 100644 --- a/LLMonFHIR/FHIRInterpretation/MultipleResources/UserStudy/UserStudyWelcomeView.swift +++ b/LLMonFHIR/FHIRInterpretation/MultipleResources/UserStudy/UserStudyWelcomeView.swift @@ -7,10 +7,12 @@ // import SpeziAccessGuard +import SpeziLLMOpenAI import SwiftUI struct UserStudyWelcomeView: View { + @Environment(LLMOpenAITokenSaver.self) private var tokenSaver @State private var isPresentingSettings = false @State private var isPresentingStudy = false @@ -34,6 +36,9 @@ struct UserStudyWelcomeView: View { UserStudyChatView(survey: Survey(.defaultTasks)) } } + .onAppear { + tokenSaver.token = OpenAIPlistConfiguration.shared.apiKey + } } } @@ -193,3 +198,20 @@ extension [SurveyTask] { ]) ] } + +private struct OpenAIPlistConfiguration { + static let shared = OpenAIPlistConfiguration() + + let apiKey: String + + private init() { + guard let path = Bundle.main.path(forResource: "OpenAIConfig", ofType: "plist"), + let dict = NSDictionary(contentsOfFile: path) as? [String: Any], + let key = dict["OpenAI_API_Key"] as? String + else { + fatalError("Failed to load OpenAI configuration") + } + + self.apiKey = key + } +} diff --git a/LLMonFHIR/Onboarding/OnboardingFlow.swift b/LLMonFHIR/Onboarding/OnboardingFlow.swift index 085a45a..2993a74 100644 --- a/LLMonFHIR/Onboarding/OnboardingFlow.swift +++ b/LLMonFHIR/Onboarding/OnboardingFlow.swift @@ -31,7 +31,9 @@ struct OnboardingFlow: View { OnboardingStack(onboardingFlowComplete: $completedOnboardingFlow) { Welcome() Disclaimer() - OpenAIAPIKey() + if !FeatureFlags.isUserStudyEnabled { + OpenAIAPIKey() + } if HKHealthStore.isHealthDataAvailable() && !healthKitAuthorization { HealthKitPermissions() } diff --git a/LLMonFHIR/SharedContext/FeatureFlags.swift b/LLMonFHIR/SharedContext/FeatureFlags.swift index 260076b..9931a2c 100644 --- a/LLMonFHIR/SharedContext/FeatureFlags.swift +++ b/LLMonFHIR/SharedContext/FeatureFlags.swift @@ -6,6 +6,9 @@ // SPDX-License-Identifier: MIT // +import Foundation + + /// A collection of feature flags for the PAWS app. enum FeatureFlags { /// Skips the onboarding flow to enable easier development of features in the application and to allow UI tests to skip the onboarding flow. @@ -15,5 +18,8 @@ enum FeatureFlags { /// Sets the application in test mode static let testMode = CommandLine.arguments.contains("--testMode") /// Sets the application in user study mode - static let isUserStudyEnabled = CommandLine.arguments.contains("--userStudy") + static var isUserStudyEnabled: Bool { + CommandLine.arguments.contains("--userStudy") || + Bundle.main.infoDictionary?["UserStudyEnabled"] as? Bool == true + } } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 1cf37dd..c6f0b04 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -93,4 +93,35 @@ platform :ios do changelog: commit[:message] ) end + + desc "Publish a user study build to TestFlight" + lane :beta_user_study do + signin + increment_build_number( + { + build_number: latest_testflight_build_number + 1 + } + ) + + update_info_plist( + plist_path: "LLMonFHIR/Supporting Files/Info.plist", + block: proc do |plist| + plist["UserStudyEnabled"] = true + end + ) + + build + + commit = last_git_commit + upload_to_testflight( + distribute_external: true, + groups: [ + "External Testers" + ], + submit_beta_review: true, + notify_external_testers: true, + expire_previous_builds: true, + changelog: commit[:message] + ) + end end