Commit a1616226 authored by Mholloway's avatar Mholloway
Browse files

[Swift] Support adding context values based on stream config

Bug: T274175
Bug: T281758
Change-Id: I97f826d532184f2628b850ae34ff418da5e7b86b
parent 28a02657
import Foundation
class ContextController {
private let integration: MetricsClientIntegration
init(integration: MetricsClientIntegration) {
self.integration = integration
}
func addRequestedValues(_ event: Event, config streamConfig: StreamConfig) {
guard let requestedValues = streamConfig.producerConfig?.clientConfig?.requestedValues else {
return
}
for value in requestedValues {
switch value {
// Page
case .pageId:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.id = integration.getPageId()
case .pageNamespaceId:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.namespaceId = integration.getPageNamespaceId()
case .pageNamespaceText:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.namespaceText = integration.getPageNamespaceText()
case .pageTitle:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.title = integration.getPageTitle()
case .pageIsRedirect:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.isRedirect = integration.getPageIsRedirect()
case .pageRevisionId:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.revisionId = integration.getPageRevisionId()
case .pageWikidataId:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.wikidataId = integration.getPageWikidataItemId()
case .pageContentLanguage:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.contentLanguage = integration.getPageContentLanguage()
case .pageUserGroupsAllowedToEdit:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.userGroupsAllowedToEdit = integration.getGroupsAllowedToEditPage()
case .pageUserGroupsAllowedToMove:
if event.pageData == nil {
event.pageData = PageData()
}
event.pageData?.userGroupsAllowedToMove = integration.getGroupsAllowedToMovePage()
// User
case .userId:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.id = integration.getUserId()
case .userName:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.name = integration.getUserName()
case .userGroups:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.groups = integration.getUserGroups()
case .userIsLoggedIn:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.isLoggedIn = integration.getUserIsLoggedIn()
case .userIsBot:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.isBot = integration.getUserIsBot()
case .userCanProbablyEditPage:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.canProbablyEditPage = integration.getUserCanProbablyEditPage()
case .userEditCount:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.editCount = integration.getUserEditCount()
case .userEditCountBucket:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.editCountBucket = integration.getUserEditCountBucket()
case .userRegistrationTimestamp:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.registrationTimestamp = integration.getUserRegistrationTimestamp()
case .userLanguage:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.language = integration.getUserLanguage()
case .userLanguageVariant:
if event.userData == nil {
event.userData = UserData()
}
event.userData?.languageVariant = integration.getUserLanguageVariant()
// Device
case .devicePixelRatio:
if event.deviceData == nil {
event.deviceData = DeviceData()
}
event.deviceData?.pixelRatio = integration.getDevicePixelRatio()
case .deviceHardwareConcurrency:
if event.deviceData == nil {
event.deviceData = DeviceData()
}
event.deviceData?.hardwareConcurrency = integration.getDeviceHardwareConcurrency()
case .deviceMaxTouchPoints:
if event.deviceData == nil {
event.deviceData = DeviceData()
}
event.deviceData?.maxTouchPoints = integration.getDeviceMaxTouchPoints()
// Other
case .accessMethod:
event.accessMethod = "mobile app"
case .platform:
event.platform = "ios"
case .platformFamily:
event.platformFamily = "app"
case .isProduction:
event.isProduction = integration.isProduction()
}
}
}
}
import Foundation
enum ContextValue: String, Codable {
case pageId = "page_id"
case pageNamespaceId = "page_namespace_id"
case pageNamespaceText = "page_namespace_text"
case pageTitle = "page_title"
case pageRevisionId = "page_revision_id"
case pageWikidataId = "page_wikidata_id"
case pageIsRedirect = "page_is_redirect"
case pageContentLanguage = "page_content_language"
case pageUserGroupsAllowedToEdit = "page_user_groups_allowed_to_edit"
case pageUserGroupsAllowedToMove = "page_user_groups_allowed_to_move"
case userId = "user_id"
case userName = "user_name"
case userGroups = "user_groups"
case userIsLoggedIn = "user_is_logged_in"
case userIsBot = "user_is_bot"
case userCanProbablyEditPage = "user_can_probably_edit_page"
case userEditCount = "user_edit_count"
case userEditCountBucket = "user_edit_count_bucket"
case userRegistrationTimestamp = "user_registration_timestamp"
case userLanguage = "user_language"
case userLanguageVariant = "user_language_variant"
case devicePixelRatio = "device_pixel_ratio"
case deviceHardwareConcurrency = "device_hardware_concurrency"
case deviceMaxTouchPoints = "device_max_touch_points"
case accessMethod = "access_method"
case platform = "platform"
case platformFamily = "platform_family"
case isProduction = "is_production"
}
\ No newline at end of file
import Foundation
struct DeviceData: Encodable {
var pixelRatio: Float?
var hardwareConcurrency: Int?
var maxTouchPoints: Int?
enum CodingKeys: String, CodingKey {
case pixelRatio = "pixel_ratio"
case hardwareConcurrency = "hardware_concurrency"
case maxTouchPoints = "max_touch_points"
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
do {
try container.encodeIfPresent(pixelRatio, forKey: .pixelRatio)
try container.encodeIfPresent(hardwareConcurrency, forKey: .hardwareConcurrency)
try container.encodeIfPresent(maxTouchPoints, forKey: .maxTouchPoints)
} catch let error {
NSLog("EPC: Error encoding event body: \(error)")
}
}
}
import Foundation
struct PageData: Encodable {
var id: Int?
var namespaceId: Int?
var namespaceText: String?
var title: String?
var isRedirect: Bool?
var revisionId: Int?
var wikidataId: String?
var contentLanguage: String?
var userGroupsAllowedToMove: [String]?
var userGroupsAllowedToEdit: [String]?
enum CodingKeys: String, CodingKey {
case id
case namespaceId = "namespace_id"
case namespaceText = "namespace_text"
case title
case isRedirect = "is_redirect"
case revisionId = "revision_id"
case wikidataId = "wikidata_id"
case contentLanguage = "content_language"
case userGroupsAllowedToMove = "user_groups_allowed_to_move"
case userGroupsAllowedToEdit = "user_groups_allowed_to_edit"
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
do {
try container.encodeIfPresent(id, forKey: .id)
try container.encodeIfPresent(namespaceId, forKey: .namespaceId)
try container.encodeIfPresent(namespaceText, forKey: .namespaceText)
try container.encodeIfPresent(title, forKey: .title)
try container.encodeIfPresent(isRedirect, forKey: .isRedirect)
try container.encodeIfPresent(revisionId, forKey: .revisionId)
try container.encodeIfPresent(wikidataId, forKey: .wikidataId)
try container.encodeIfPresent(contentLanguage, forKey: .contentLanguage)
try container.encodeIfPresent(userGroupsAllowedToMove, forKey: .userGroupsAllowedToMove)
try container.encodeIfPresent(userGroupsAllowedToEdit, forKey: .userGroupsAllowedToEdit)
} catch let error {
NSLog("EPC: Error encoding event body: \(error)")
}
}
}
import Foundation
struct UserData: Encodable {
var id: Int?
var name: String?
var groups: [String]?
var isLoggedIn: Bool?
var isBot: Bool?
var canProbablyEditPage: Bool?
var editCount: Int?
var editCountBucket: String?
var registrationTimestamp: Int?
var language: String?
var languageVariant: String?
enum CodingKeys: String, CodingKey {
case id
case name
case groups
case isLoggedIn = "is_logged_in"
case isBot = "is_bot"
case canProbablyEditPage = "can_probably_edit_page"
case editCount = "edit_count"
case editCountBucket = "edit_count_bucket"
case registrationTimestamp = "registration_timestamp"
case language
case languageVariant = "language_variant"
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
do {
try container.encodeIfPresent(id, forKey: .id)
try container.encodeIfPresent(name, forKey: .name)
try container.encodeIfPresent(groups, forKey: .groups)
try container.encodeIfPresent(isLoggedIn, forKey: .isLoggedIn)
try container.encodeIfPresent(isBot, forKey: .isBot)
try container.encodeIfPresent(canProbablyEditPage, forKey: .canProbablyEditPage)
try container.encodeIfPresent(editCount, forKey: .editCount)
try container.encodeIfPresent(editCountBucket, forKey: .editCountBucket)
try container.encodeIfPresent(registrationTimestamp, forKey: .registrationTimestamp)
try container.encodeIfPresent(language, forKey: .language)
try container.encodeIfPresent(languageVariant, forKey: .languageVariant)
} catch let error {
NSLog("EPC: Error encoding event body: \(error)")
}
}
}
......@@ -36,6 +36,14 @@ public class Event: Encodable {
*/
var dt: String?
var pageData: PageData?
var userData: UserData?
var deviceData: DeviceData?
var accessMethod: String?
var platform: String?
var platformFamily: String?
var isProduction: Bool?
init(stream: String, schema: String) {
self.schema = schema
self.meta = Meta(stream: stream)
......@@ -47,6 +55,13 @@ public class Event: Encodable {
case appInstallId = "app_install_id"
case appSessionId = "app_session_id"
case dt
case pageData = "page"
case userData = "user"
case deviceData = "device"
case accessMethod = "access_method"
case platform
case platformFamily = "platform_family"
case isProduction = "is_production"
}
public func encode(to encoder: Encoder) throws {
......@@ -57,6 +72,13 @@ public class Event: Encodable {
try container.encode(appSessionId, forKey: .appSessionId)
try container.encode(dt, forKey: .dt)
try container.encode(schema, forKey: .schema)
try container.encodeIfPresent(pageData, forKey: .pageData)
try container.encodeIfPresent(userData, forKey: .userData)
try container.encodeIfPresent(deviceData, forKey: .deviceData)
try container.encodeIfPresent(accessMethod, forKey: .accessMethod)
try container.encodeIfPresent(platform, forKey: .platform)
try container.encodeIfPresent(platformFamily, forKey: .platformFamily)
try container.encodeIfPresent(isProduction, forKey: .isProduction)
} catch let error {
NSLog("EPC: Error encoding event body: \(error)")
}
......
......@@ -6,4 +6,33 @@ protocol MetricsClientIntegration {
func appInstallId() -> String
func httpGet(_ url: URL, callback: @escaping (Data?, URLResponse?, Error?) -> Void)
func httpPost(_ url: URL, body: Data?, callback: @escaping (Result<Void, Error>) -> Void)
func getPageId() -> Int?
func getPageNamespaceId() -> Int?
func getPageNamespaceText() -> String?
func getPageTitle() -> String?
func getPageIsRedirect() -> Bool?
func getPageRevisionId() -> Int?
func getPageWikidataItemId() -> String?
func getPageContentLanguage() -> String?
func getGroupsAllowedToEditPage() -> [String]?
func getGroupsAllowedToMovePage() -> [String]?
func getUserId() -> Int?
func getUserName() -> String?
func getUserGroups() -> [String]?
func getUserIsLoggedIn() -> Bool?
func getUserIsBot() -> Bool?
func getUserCanProbablyEditPage() -> Bool?
func getUserEditCount() -> Int?
func getUserEditCountBucket() -> String?
func getUserRegistrationTimestamp() -> Int?
func getUserLanguage() -> String?
func getUserLanguageVariant() -> String?
func getDevicePixelRatio() -> Float?
func getDeviceHardwareConcurrency() -> Int?
func getDeviceMaxTouchPoints() -> Int?
func isProduction() -> Bool?
}
......@@ -25,9 +25,11 @@ struct StreamConfig: Decodable {
struct MetricsPlatformClientConfig: Decodable {
var samplingConfig: SamplingConfig?
var requestedValues: [ContextValue]?
enum CodingKeys: String, CodingKey {
case samplingConfig = "sampling"
case requestedValues = "provide_values"
}
}
}
......
import XCTest
@testable import WikimediaMetricsPlatform
final class ContextControllerTests: XCTestCase {
var streamConfigs: [String: StreamConfig] = [:]
override func setUp() {
super.setUp()
let requestedValues = [
ContextValue.pageId,
ContextValue.pageNamespaceId,
ContextValue.pageNamespaceText,
ContextValue.pageTitle,
ContextValue.pageIsRedirect,
ContextValue.pageRevisionId,
ContextValue.pageContentLanguage,
ContextValue.pageWikidataId,
ContextValue.pageUserGroupsAllowedToEdit,
ContextValue.pageUserGroupsAllowedToMove,
ContextValue.userId,
ContextValue.userName,
ContextValue.userGroups,
ContextValue.userIsLoggedIn,
ContextValue.userIsBot,
ContextValue.userEditCount,
ContextValue.userEditCountBucket,
ContextValue.userRegistrationTimestamp,
ContextValue.userLanguage,
ContextValue.userLanguageVariant,
ContextValue.userCanProbablyEditPage,
ContextValue.devicePixelRatio,
ContextValue.deviceHardwareConcurrency,
ContextValue.deviceMaxTouchPoints,
ContextValue.accessMethod,
ContextValue.platform,
ContextValue.platformFamily,
ContextValue.isProduction,
]
let clientConfig = StreamConfig.ProducerConfig.MetricsPlatformClientConfig(requestedValues: requestedValues)
let producerConfig = StreamConfig.ProducerConfig(clientConfig: clientConfig)
let streamConfig = StreamConfig(stream: "test.event", schema: "test/event", producerConfig: producerConfig)
self.streamConfigs = ["test.event": streamConfig]
}
func testAddRequestedValues() {
let event = Event(stream: "test.event", schema: "test/event")
let contextController = ContextController(integration: TestMetricsClientIntegration())
contextController.addRequestedValues(event, config: self.streamConfigs["test.event"]!)
XCTAssertEqual(event.pageData?.id, 1)
XCTAssertEqual(event.pageData?.namespaceId, 0)
XCTAssertEqual(event.pageData?.namespaceText, "")
XCTAssertEqual(event.pageData?.title, "Test")
XCTAssertEqual(event.pageData?.isRedirect, false)
XCTAssertEqual(event.pageData?.revisionId, 1)
XCTAssertEqual(event.pageData?.contentLanguage, "zh")
XCTAssertEqual(event.pageData?.wikidataId, "Q1")
XCTAssertEqual(event.pageData?.userGroupsAllowedToMove, [])
XCTAssertEqual(event.pageData?.userGroupsAllowedToEdit, [])
XCTAssertEqual(event.userData?.id, 1)
XCTAssertEqual(event.userData?.name, "TestUser")
XCTAssertEqual(event.userData?.groups, ["*"])
XCTAssertEqual(event.userData?.isLoggedIn, true)
XCTAssertEqual(event.userData?.isBot, false)
XCTAssertEqual(event.userData?.editCount, 10)
XCTAssertEqual(event.userData?.editCountBucket, "5-99 edits")
XCTAssertEqual(event.userData?.registrationTimestamp, 1427224089000)
XCTAssertEqual(event.userData?.language, "zh")
XCTAssertEqual(event.userData?.languageVariant, "zh-tw")
XCTAssertEqual(event.userData?.canProbablyEditPage, true)
XCTAssertEqual(event.deviceData?.pixelRatio, 1.0)
XCTAssertEqual(event.deviceData?.hardwareConcurrency, 1)
XCTAssertEqual(event.deviceData?.maxTouchPoints, 1)
XCTAssertEqual(event.accessMethod, "mobile app")
XCTAssertEqual(event.platform, "ios")
XCTAssertEqual(event.platformFamily, "app")
XCTAssertEqual(event.isProduction, true)
}
static var allTests = [
("testAddRequestedValues", testAddRequestedValues),
]
}
......@@ -20,4 +20,112 @@ class TestMetricsClientIntegration: MetricsClientIntegration {
callback(Result() {})
}
// Page
func getPageId() -> Int? {
return 1
}
func getPageNamespaceId() -> Int? {
return 0
}
func getPageNamespaceText() -> String? {
return ""
}
func getPageTitle() -> String? {
return "Test"
}
func getPageIsRedirect() -> Bool? {
return false
}
func getPageRevisionId() -> Int? {
return 1
}
func getPageWikidataItemId() -> String? {
return "Q1"
}
func getPageContentLanguage() -> String? {
return "zh"
}
func getGroupsAllowedToEditPage() -> [String]? {
return []
}
func getGroupsAllowedToMovePage() -> [String]? {
return []
}
// User
func getUserId() -> Int? {
return 1
}
func getUserName() -> String? {
return "TestUser"
}
func getUserGroups() -> [String]? {
return ["*"]
}
func getUserIsLoggedIn() -> Bool? {
return true
}
func getUserIsBot() -> Bool? {
return false
}
func getUserCanProbablyEditPage() -> Bool? {
return true
}
func getUserEditCount() -> Int? {
return 10
}
func getUserEditCountBucket() -> String? {
return "5-99 edits"
}
func getUserRegistrationTimestamp() -> Int? {
return 1427224089000