Skip to content

Commit 48eacad

Browse files
authored
Merge pull request #8017 from woocommerce/issue/8015-add-build-identifiers-to-jitm-request
[Just In Time Messages] Add build and locale identifiers to JITM request
2 parents 4b2accc + 0ac84df commit 48eacad

File tree

13 files changed

+200
-22
lines changed

13 files changed

+200
-22
lines changed

Experiments/Experiments/DefaultFeatureFlagService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
4040
case .storeCreationMVP:
4141
return true
4242
case .justInTimeMessagesOnDashboard:
43-
return buildConfig == .localDeveloper || buildConfig == .alpha
43+
return true
4444
case .productsOnboarding:
4545
return buildConfig == .localDeveloper || buildConfig == .alpha
4646
case .performanceMonitoring,

Hardware/Hardware.xcodeproj/project.pbxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,7 @@
12411241
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
12421242
SKIP_INSTALL = YES;
12431243
SUPPORTS_MACCATALYST = YES;
1244+
SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALPHA;
12441245
SWIFT_VERSION = 5.0;
12451246
TARGETED_DEVICE_FAMILY = "1,2";
12461247
};

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@
132132
09EA564B27C75FCE00407D40 /* ProductVariationsBulkUpdateMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EA564A27C75FCE00407D40 /* ProductVariationsBulkUpdateMapper.swift */; };
133133
21DB5B99C4107CF69C0A57EC /* Pods_NetworkingTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */; };
134134
24F98C522502E79800F49B68 /* FeatureFlagsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F98C512502E79800F49B68 /* FeatureFlagsRemote.swift */; };
135-
24F98C542502E8DD00F49B68 /* Bundle+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F98C532502E8DD00F49B68 /* Bundle+Woo.swift */; };
136135
24F98C562502EA4800F49B68 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F98C552502EA4800F49B68 /* FeatureFlag.swift */; };
137136
24F98C582502EA8800F49B68 /* FeatureFlagMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F98C572502EA8800F49B68 /* FeatureFlagMapper.swift */; };
138137
24F98C5E2502EDCF00F49B68 /* BundleWooTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F98C5D2502EDCF00F49B68 /* BundleWooTests.swift */; };
@@ -865,7 +864,6 @@
865864
09885C7F27C3FFD200910A62 /* product-variations-bulk-update.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-variations-bulk-update.json"; sourceTree = "<group>"; };
866865
09EA564A27C75FCE00407D40 /* ProductVariationsBulkUpdateMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationsBulkUpdateMapper.swift; sourceTree = "<group>"; };
867866
24F98C512502E79800F49B68 /* FeatureFlagsRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagsRemote.swift; sourceTree = "<group>"; };
868-
24F98C532502E8DD00F49B68 /* Bundle+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Woo.swift"; sourceTree = "<group>"; };
869867
24F98C552502EA4800F49B68 /* FeatureFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlag.swift; sourceTree = "<group>"; };
870868
24F98C572502EA8800F49B68 /* FeatureFlagMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagMapper.swift; sourceTree = "<group>"; };
871869
24F98C5D2502EDCF00F49B68 /* BundleWooTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleWooTests.swift; sourceTree = "<group>"; };
@@ -2346,7 +2344,6 @@
23462344
021C7BF623863D1800A3BCBD /* Encodable+Serialization.swift */,
23472345
02BDB83423EA98C800BCC63E /* String+HTML.swift */,
23482346
57E8FED2246616AC0057CD68 /* Result+Extensions.swift */,
2349-
24F98C532502E8DD00F49B68 /* Bundle+Woo.swift */,
23502347
265EFBDB285257950033BD33 /* Order+Fallbacks.swift */,
23512348
);
23522349
path = Extensions;
@@ -3011,7 +3008,6 @@
30113008
D88E229025AC990A0023F3B1 /* OrderFeeLine.swift in Sources */,
30123009
74046E1F217A6B70007DD7BF /* SiteSettingsMapper.swift in Sources */,
30133010
26B2F74524C5573F0065CCC8 /* LeaderboardListMapper.swift in Sources */,
3014-
24F98C542502E8DD00F49B68 /* Bundle+Woo.swift in Sources */,
30153011
31D27C812602889C002EDB1D /* SitePluginsRemote.swift in Sources */,
30163012
02BE0A7B274B695F001176D2 /* WordPressMediaMapper.swift in Sources */,
30173013
45150A9A268340D2006922EA /* Country.swift in Sources */,
@@ -3484,6 +3480,7 @@
34843480
PRODUCT_BUNDLE_IDENTIFIER = com.automattic.woo.Networking;
34853481
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
34863482
SKIP_INSTALL = YES;
3483+
SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALPHA;
34873484
SWIFT_VERSION = 5.0;
34883485
TARGETED_DEVICE_FAMILY = "1,2";
34893486
VALID_ARCHS = "$(inherited)";

Networking/Networking/Remote/JustInTimeMessagesRemote.swift

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import Foundation
22

33
public protocol JustInTimeMessagesRemoteProtocol {
44
func loadAllJustInTimeMessages(for siteID: Int64,
5-
messagePath: JustInTimeMessagesRemote.MessagePath) async -> Result<[JustInTimeMessage], Error>
5+
messagePath: JustInTimeMessagesRemote.MessagePath,
6+
query: [String: String?]?,
7+
locale: String?) async -> Result<[JustInTimeMessage], Error>
68
func dismissJustInTimeMessage(for siteID: Int64,
79
messageID: String,
810
featureClass: String) async -> Result<Bool, Error>
@@ -18,16 +20,22 @@ public final class JustInTimeMessagesRemote: Remote, JustInTimeMessagesRemotePro
1820
/// - Parameters:
1921
/// - siteID: The site for which we'll fetch JustInTimeMessages.
2022
/// - messagePath: The location for JITMs to be displayed
23+
/// - query: A dictionary of "query parameters" to include in the JITM request payload
24+
/// - locale: the locale identifier (language and region only, e.g. en_US) for the current device.
2125
/// - Returns:
2226
/// Async result with an array of `[JustInTimeMessage]` (usually contains one element) or an error
2327
///
2428
public func loadAllJustInTimeMessages(for siteID: Int64,
25-
messagePath: JustInTimeMessagesRemote.MessagePath) async -> Result<[JustInTimeMessage], Error> {
29+
messagePath: JustInTimeMessagesRemote.MessagePath,
30+
query: [String: String?]?,
31+
locale: String?) async -> Result<[JustInTimeMessage], Error> {
2632
let request = JetpackRequest(wooApiVersion: .none,
2733
method: .get,
2834
siteID: siteID,
35+
locale: locale,
2936
path: Path.jitm,
30-
parameters: [ParameterKey.messagePath: messagePath.requestValue])
37+
parameters: getParameters(messagePath: messagePath,
38+
query: query))
3139

3240
let mapper = JustInTimeMessageListMapper(siteID: siteID)
3341

@@ -39,6 +47,41 @@ public final class JustInTimeMessagesRemote: Remote, JustInTimeMessagesRemotePro
3947
}
4048
}
4149

50+
private func getParameters(messagePath: JustInTimeMessagesRemote.MessagePath,
51+
query: [String: String?]?) -> [String: String] {
52+
var parameters = [ParameterKey.messagePath: messagePath.requestValue]
53+
if let query = query,
54+
let queryString = justInTimeMessageQuery(from: query) {
55+
parameters[ParameterKey.query] = queryString
56+
}
57+
return parameters
58+
}
59+
60+
private func justInTimeMessageQuery(from parameters: [String: String?]) -> String? {
61+
let queryItems = parameters.map { (key: String, value: String?) in
62+
URLQueryItem(name: key, value: value)
63+
}
64+
var components = URLComponents()
65+
/// This is a workaround for a backend bug where only the first param can be used for targeting JITMs.
66+
/// `build_type` is the most important, but absent in release builds. In release builds, `platform` is the most important
67+
/// This can be removed when the backend bug is fixed, order should not matter here.
68+
components.queryItems = queryItems.sorted(by: { lhs, rhs in
69+
switch (lhs.name, rhs.name) {
70+
case (_, "build_type"):
71+
return false
72+
case ("build_type", "platform"):
73+
return true
74+
case (_, "platform"):
75+
return false
76+
case ("platform", _):
77+
return true
78+
default:
79+
return lhs.name < rhs.name
80+
}
81+
})
82+
return components.query
83+
}
84+
4285
/// Dismisses a `JustInTimeMessage` using the API.
4386
///
4487
/// - Parameters:
@@ -81,6 +124,7 @@ public extension JustInTimeMessagesRemote {
81124
static let messagePath = "message_path"
82125
static let featureClass = "feature_class"
83126
static let messageID = "id"
127+
static let query = "query"
84128
}
85129

86130
/// Message Path parameter

Networking/Networking/Requests/JetpackRequest.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ struct JetpackRequest: URLRequestConvertible {
2222
///
2323
let siteID: Int64
2424

25+
/// Locale identifier, simplified as `language_Region` e.g. `en_US`
26+
///
27+
let locale: String?
28+
2529
/// Jetpack-Tunneled RPC
2630
///
2731
let path: String
@@ -40,13 +44,14 @@ struct JetpackRequest: URLRequestConvertible {
4044
/// - path: RPC that should be called.
4145
/// - parameters: Collection of Key/Value parameters, to be forwarded to the Jetpack Connected site.
4246
///
43-
init(wooApiVersion: WooAPIVersion, method: HTTPMethod, siteID: Int64, path: String, parameters: [String: Any]? = nil) {
47+
init(wooApiVersion: WooAPIVersion, method: HTTPMethod, siteID: Int64, locale: String? = nil, path: String, parameters: [String: Any]? = nil) {
4448
if [.mark1, .mark2].contains(wooApiVersion) {
4549
DDLogWarn("⚠️ You are using an older version of the Woo REST API: \(wooApiVersion.rawValue), for path: \(path)")
4650
}
4751
self.wooApiVersion = wooApiVersion
4852
self.method = method
4953
self.siteID = siteID
54+
self.locale = locale
5055
self.path = path
5156
self.parameters = parameters ?? [:]
5257
}
@@ -102,6 +107,10 @@ private extension JetpackRequest {
102107
"path": jetpackPath + "&_method=" + method.rawValue.lowercased()
103108
]
104109

110+
if let locale = locale {
111+
output["locale"] = locale
112+
}
113+
105114
if let jetpackQueryParams = jetpackQueryParams {
106115
output["query"] = jetpackQueryParams
107116
}

Networking/NetworkingTests/Remote/JustInTimeMessagesRemoteTests.swift

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ final class JustInTimeMessagesRemoteTests: XCTestCase {
1616
network = MockNetwork()
1717
}
1818

19-
override func tearDown() {
20-
network = nil
21-
super.tearDown()
22-
}
23-
2419
// MARK: - Load all Just In Time Messages tests
2520

2621
/// Verifies that loadAllJustInTimeMessages properly parses the `just-in-time-message-list` sample response.
@@ -37,7 +32,9 @@ final class JustInTimeMessagesRemoteTests: XCTestCase {
3732
messagePath: JustInTimeMessagesRemote.MessagePath(
3833
app: .wooMobile,
3934
screen: "my_store",
40-
hook: .adminNotices))
35+
hook: .adminNotices),
36+
query: nil,
37+
locale: "en_US")
4138

4239
// Then
4340
XCTAssert(result.isSuccess)
@@ -57,7 +54,9 @@ final class JustInTimeMessagesRemoteTests: XCTestCase {
5754
messagePath: JustInTimeMessagesRemote.MessagePath(
5855
app: .wooMobile,
5956
screen: "my_store",
60-
hook: .adminNotices))
57+
hook: .adminNotices),
58+
query: nil,
59+
locale: "en_US")
6160

6261
// Then
6362
let request = try XCTUnwrap(network.requestsForResponseData.first as? JetpackRequest)
@@ -78,7 +77,9 @@ final class JustInTimeMessagesRemoteTests: XCTestCase {
7877
messagePath: JustInTimeMessagesRemote.MessagePath(
7978
app: .wooMobile,
8079
screen: "my_store",
81-
hook: .adminNotices))
80+
hook: .adminNotices),
81+
query: nil,
82+
locale: "en_US")
8283

8384
// Then
8485
let justInTimeMessages = try result.get()
@@ -97,7 +98,9 @@ final class JustInTimeMessagesRemoteTests: XCTestCase {
9798
messagePath: JustInTimeMessagesRemote.MessagePath(
9899
app: .wooMobile,
99100
screen: "my_store",
100-
hook: .adminNotices))
101+
hook: .adminNotices),
102+
query: nil,
103+
locale: "en_US")
101104

102105
// Then
103106
let request = try XCTUnwrap(network.requestsForResponseData.first as? JetpackRequest)
@@ -122,11 +125,65 @@ final class JustInTimeMessagesRemoteTests: XCTestCase {
122125
messagePath: JustInTimeMessagesRemote.MessagePath(
123126
app: .wooMobile,
124127
screen: "my_store",
125-
hook: .adminNotices))
128+
hook: .adminNotices),
129+
query: nil,
130+
locale: "en_US")
126131

127132
// Then
128133
XCTAssertTrue(result.isFailure)
129134
let resultError = try XCTUnwrap(result.failure as? NetworkError)
130135
assertEqual(error, resultError)
131136
}
137+
138+
func test_test_loadAllJustInTimeMessages_uses_passed_locale_for_request() async throws {
139+
// Given
140+
let remote = JustInTimeMessagesRemote(network: network)
141+
142+
// When
143+
_ = await remote.loadAllJustInTimeMessages(
144+
for: self.sampleSiteID,
145+
messagePath: JustInTimeMessagesRemote.MessagePath(
146+
app: .wooMobile,
147+
screen: "my_store",
148+
hook: .adminNotices),
149+
query: nil,
150+
locale: "en_US")
151+
152+
// Then
153+
let request = try XCTUnwrap(network.requestsForResponseData.first as? JetpackRequest)
154+
let url = try XCTUnwrap(request.urlRequest?.url)
155+
let queryItems = try XCTUnwrap(URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems)
156+
let locale = try XCTUnwrap(queryItems.first { $0.name == "locale" }?.value)
157+
assertEqual("en_US", locale)
158+
}
159+
160+
func test_test_loadAllJustInTimeMessages_uses_passed_query_for_request() async throws {
161+
// Given
162+
let remote = JustInTimeMessagesRemote(network: network)
163+
164+
// When
165+
_ = await remote.loadAllJustInTimeMessages(
166+
for: self.sampleSiteID,
167+
messagePath: JustInTimeMessagesRemote.MessagePath(
168+
app: .wooMobile,
169+
screen: "my_store",
170+
hook: .adminNotices),
171+
query: [
172+
"platform": "ios",
173+
"version": "11.1"
174+
],
175+
locale: "en_US")
176+
177+
// Then
178+
let request = try XCTUnwrap(network.requestsForResponseData.first as? JetpackRequest)
179+
let url = try XCTUnwrap(request.urlRequest?.url)
180+
let queryItems = try XCTUnwrap(URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems)
181+
let queryJson = try XCTUnwrap(queryItems.first { $0.name == "query" }?.value)
182+
assertThat(queryJson, contains: "\"query\":")
183+
let parameters = request.parameters
184+
let jitmQuery = try XCTUnwrap(request.parameters["query"] as? String)
185+
// Individually check query items because dictionaries aren't ordered
186+
assertThat(jitmQuery, contains: "platform=ios") // platform=ios
187+
assertThat(jitmQuery, contains: "version=11.1") // version=11.1
188+
}
132189
}

Networking/NetworkingTests/Requests/JetpackRequestTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,20 @@ final class JetpackRequestTests: XCTestCase {
106106
XCTAssertEqual(output.httpMethod?.uppercased(), "POST")
107107
XCTAssertTrue(generatedBody.contains("%26_method%3Dput"))
108108
}
109+
110+
/// Verifies that a JetpackRequest with `locale` encodes `&locale=fr_FR` query string parameter.
111+
///
112+
func test_request_with_locale_includes_locale_parameter() {
113+
let request = JetpackRequest(wooApiVersion: .mark3,
114+
method: .get,
115+
siteID: sampleSiteID,
116+
locale: "fr_FR",
117+
path: sampleRPC,
118+
parameters: sampleParameters)
119+
120+
let output = try! request.asURLRequest()
121+
XCTAssertTrue((output.url?.absoluteString.contains("locale=fr_FR"))!)
122+
}
109123
}
110124

111125

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [**] You can now search customers when creating or editing an order. [https://github.com/woocommerce/woocommerce-ios/issues/7741]
66
- [internal] Store creation is available from the login prologue, login email error screen, and store picker. [https://github.com/woocommerce/woocommerce-ios/pull/8023]
77
- [internal] The login flow is simplified with only the option to log in with WordPress.com. This flow is presented in parallel with the existing flow in an A/B test experiment. [https://github.com/woocommerce/woocommerce-ios/pull/7996]
8+
- [**] Relevant Just In Time Messages will be displayed on the My Store screen [https://github.com/woocommerce/woocommerce-ios/issues/7853]
89

910
11.0
1011
-----

Storage/Storage.xcodeproj/project.pbxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,7 @@
15061506
PRODUCT_BUNDLE_IDENTIFIER = com.automattic.woo.Storage;
15071507
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
15081508
SKIP_INSTALL = YES;
1509+
SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALPHA;
15091510
SWIFT_VERSION = 5.0;
15101511
TARGETED_DEVICE_FAMILY = "1,2";
15111512
VALID_ARCHS = "$(inherited)";

0 commit comments

Comments
 (0)