-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AB infrastructure #357
AB infrastructure #357
Changes from 1 commit
890855f
6be701c
e33995f
7047945
c5c22b5
06ccce9
98a9481
653173b
f599e4c
94190b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// | ||
// ABSocialAuthString.swift | ||
// Stepic | ||
// | ||
// Created by Ostrenkiy on 04.09.2018. | ||
// Copyright © 2018 Alex Karpov. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
|
||
class ABSocialAuthString: ActiveABTest { | ||
let ID = "ab_social_auth_string" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
let controlValue: String = NSLocalizedString("SignInTitleSocial", comment: "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
func value(group: String) -> String? { | ||
switch group { | ||
case "control": | ||
return controlValue | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Предлагаю начать указывать явно |
||
case "test": | ||
return NSLocalizedString("SignInTitleSocialTest", comment: "") | ||
default: | ||
return nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Смысл делать There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А еще можно строки превратить в enum, где для всех неизвестных значений будет определяться кейс |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// | ||
// ABTestService.swift | ||
// Stepic | ||
// | ||
// Created by Ostrenkiy on 03.09.2018. | ||
// Copyright © 2018 Alex Karpov. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol ActiveABTest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Почему |
||
associatedtype ValueType | ||
var ID: String { get } | ||
func value(group: String) -> ValueType? | ||
var controlValue: ValueType { get } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Почему бы не добавить сюда еще |
||
} | ||
|
||
class ABTestService<T: ActiveABTest> { | ||
|
||
var test: T | ||
|
||
private let defaults = UserDefaults.standard | ||
|
||
init(test: T) { | ||
self.test = test | ||
} | ||
|
||
var value: T.ValueType { | ||
if let resultGroup = RemoteConfig.shared.string(forKey: test.ID + "_result") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if let resultGroup = RemoteConfig.shared.string(forKey: test.ID + "_result"),
!resultGroup.isEmpty {
defaults.set(resultGroup, forKey: test.ID)
return test.value(group: resultGroup) ?? test.controlValue
} Без лишней вложенности. |
||
if !resultGroup.isEmpty { | ||
defaults.set(resultGroup, forKey: test.ID) | ||
return test.value(group: resultGroup) ?? test.controlValue | ||
} | ||
} | ||
if let group = defaults.value(forKey: test.ID) as? String { | ||
return test.value(group: group) ?? test.controlValue | ||
} else { | ||
if RemoteConfig.shared.fetchComplete { | ||
guard | ||
let group = RemoteConfig.shared.string(forKey: test.ID), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Давай не будем изобретать новое форматирование длинных гвардов |
||
let value = test.value(group: group) | ||
else { | ||
return test.controlValue | ||
} | ||
defaults.set(group, forKey: test.ID) | ||
AnalyticsUserProperties.shared.setAmplitudeProperty(key: test.ID, value: group) | ||
return value | ||
} else { | ||
return test.controlValue | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,7 @@ class AnalyticsUserProperties { | |
|
||
static let shared = AnalyticsUserProperties() | ||
|
||
private func setAmplitudeProperty(key: String, value: Any?) { | ||
func setAmplitudeProperty(key: String, value: Any?) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Кмк, лучше сделать так же как и для других проперти: func setABProperty(test: ABTest.IDType, value: ABTest.Group) {
self.setAmplitudeProperty(...)
} |
||
if let v = value { | ||
Amplitude.instance().setUserProperties([key: v]) | ||
} else { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
|
||
import Foundation | ||
import FirebaseRemoteConfig | ||
import FirebaseInstanceID | ||
|
||
enum RemoteConfigKeys: String { | ||
case showStreaksNotificationTrigger = "show_streaks_notification_trigger" | ||
|
@@ -86,9 +87,10 @@ class RemoteConfig { | |
} | ||
|
||
private func fetchCloudValues() { | ||
let fetchDuration: TimeInterval = 43200 | ||
var fetchDuration: TimeInterval = 43200 | ||
#if DEBUG | ||
activateDebugMode() | ||
fetchDuration = 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Почему не поле класса и не в |
||
#endif | ||
FirebaseRemoteConfig.RemoteConfig.remoteConfig().fetch(withExpirationDuration: fetchDuration) { | ||
[weak self] | ||
|
@@ -110,4 +112,8 @@ class RemoteConfig { | |
let debugSettings = RemoteConfigSettings(developerModeEnabled: true) | ||
FirebaseRemoteConfig.RemoteConfig.remoteConfig().configSettings = debugSettings | ||
} | ||
|
||
func string(forKey key: String) -> String? { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Это конечно плохо, потому что тогда уж лучше переписать весь класс и сделать новый гибкий интерфейс. А получается, что какие-то ключи у нас обернуты в проперти, а какие-то мы дергаем без обертки 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Если мы так будем переписывать каждый класс, в котором что-то не нравится, не будем по пол года релизиться. |
||
return FirebaseRemoteConfig.RemoteConfig.remoteConfig().configValue(forKey: key).stringValue | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,13 +39,15 @@ class SocialAuthPresenter { | |
var stepicsAPI: StepicsAPI | ||
var authAPI: AuthAPI | ||
var notificationStatusesAPI: NotificationStatusesAPI | ||
var abSocialAuthService: ABTestService<ABSocialAuthString> | ||
|
||
var pendingAuthProviderInfo: SocialProviderInfo? | ||
|
||
init(authAPI: AuthAPI, stepicsAPI: StepicsAPI, notificationStatusesAPI: NotificationStatusesAPI, view: SocialAuthView) { | ||
init(authAPI: AuthAPI, stepicsAPI: StepicsAPI, notificationStatusesAPI: NotificationStatusesAPI, abSocialAuthService: ABTestService<ABSocialAuthString>, view: SocialAuthView) { | ||
self.authAPI = authAPI | ||
self.stepicsAPI = stepicsAPI | ||
self.notificationStatusesAPI = notificationStatusesAPI | ||
self.abSocialAuthService = abSocialAuthService | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Если у нас в модуле будет 10 A/B тестов, мы будем иметь 10 сервисов? Это, конечно, не очень и стоит подумать, как переделать так, чтобы было удобно. |
||
self.view = view | ||
|
||
// TODO: create NSNotification.Name extension | ||
|
@@ -125,6 +127,10 @@ class SocialAuthPresenter { | |
} | ||
} | ||
|
||
var socialAuthHeaderString: String { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Проперти наверх |
||
return abSocialAuthService.value | ||
} | ||
|
||
@objc private func didAuthCodeReceive(_ notification: NSNotification) { | ||
print("social auth: auth code received") | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -554,6 +554,9 @@ PersonalDeadline = "Personal deadline"; | |
DeadlineModeQuestion = "How much time would you like to spend studying on this course?"; | ||
PersonalDeadlineWidgetSuggestionText = "Constant learning is a key to success. We can smartly create personal learning schedule for you, so you'll never forget to study!"; | ||
|
||
/* Sign in AB */ | ||
SignInTitleSocialTest = "<b>Contunue</b> with"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Continue |
||
|
||
/* ExamEGERussian */ | ||
"ErrorMessage" = "Sorry, something went wrong: please try again later."; | ||
"Ok" = "Ok"; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Давай помечать класс как
final
, если не планируется от него наследоваться