From a2df6249db2ebdf27d8bff8e48e7d07a50c6bbe7 Mon Sep 17 00:00:00 2001 From: Harris Borawski Date: Wed, 19 Oct 2022 12:00:43 -0700 Subject: [PATCH] relax requirements for sending beacon metadata --- ios/packages/core/Sources/Types/Beacon.swift | 27 ++++++++++++-- .../Sources/SwiftUI/ActionAsset.swift | 5 ++- .../Sources/SwiftUIBeaconPlugin.swift | 37 ++++++++++++++++++- .../ViewInspector/BeaconPluginTests.swift | 33 ++++++++++++++++- 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/ios/packages/core/Sources/Types/Beacon.swift b/ios/packages/core/Sources/Types/Beacon.swift index b3dc182e4..2dda5e956 100644 --- a/ios/packages/core/Sources/Types/Beacon.swift +++ b/ios/packages/core/Sources/Types/Beacon.swift @@ -42,6 +42,12 @@ public struct AssetBeacon: Codable { } } +/// MetaData requirements for `BaseBeaconPlugin` and its extensions +public protocol BeaconableMetaData { + /// Additional data to include when beaconing this asset + var beacon: AnyType? { get set } +} + /// Container Object for matching the data type for the JS Beacon Plugin public struct BeaconableAsset: Codable { /// The ID of the asset that fired the beacon @@ -58,17 +64,32 @@ public struct BeaconableAsset: Codable { /// - id: The ID of the asset that fired the beacon /// - type: The type of the asset that fired the beacon /// - metaData: Beacon applicable metaData from the asset that fired the beacon - public init( + public init( id: String, type: String? = nil, - metaData: MetaData? = nil + metaData: MetaDataType? = nil ) { self.id = id self.type = type - self.metaData = metaData + self.metaData = MetaData(beacon: metaData?.beacon) + } + + /// Constructs a BeaconableAsset + /// - Parameters: + /// - id: The ID of the asset that fired the beacon + /// - type: The type of the asset that fired the beacon + public init( + id: String, + type: String? = nil + ) { + self.id = id + self.type = type + self.metaData = nil } } +extension MetaData: BeaconableMetaData {} + /** All potential Beacon Element types */ diff --git a/ios/packages/reference-assets/Sources/SwiftUI/ActionAsset.swift b/ios/packages/reference-assets/Sources/SwiftUI/ActionAsset.swift index e60be7324..6d92b41d7 100644 --- a/ios/packages/reference-assets/Sources/SwiftUI/ActionAsset.swift +++ b/ios/packages/reference-assets/Sources/SwiftUI/ActionAsset.swift @@ -13,6 +13,9 @@ struct ActionData: AssetData { var label: WrappedAsset? /// A function to run in the core when this action is invoked var run: WrappedFunction? + + /// Additional metaData for beaconing + var metaData: MetaData? } /** @@ -40,7 +43,7 @@ struct ActionAssetView: View { var body: some View { Button( action: { - beaconContext?.beacon(action: "clicked", element: "button", id: model.data.id) + beaconContext?.beacon(action: "clicked", element: "button", id: model.data.id, metaData: model.data.metaData) self.model.data.run?() }, label: { diff --git a/ios/plugins/BeaconPlugin/Sources/SwiftUIBeaconPlugin.swift b/ios/plugins/BeaconPlugin/Sources/SwiftUIBeaconPlugin.swift index 77ea432a1..0650342c2 100644 --- a/ios/plugins/BeaconPlugin/Sources/SwiftUIBeaconPlugin.swift +++ b/ios/plugins/BeaconPlugin/Sources/SwiftUIBeaconPlugin.swift @@ -54,14 +54,47 @@ public class BeaconContext: ObservableObject { - action: The type of action that occurred - element: The type of element in the asset that triggered the beacon - id: The ID of the asset that triggered the beacon + - metaData: `BeaconableMetaData` to include as extra data to the core BeaconPlugin - data: Additional arbitrary data to include in the beacon */ - public func beacon(action: String, element: String, id: String, type: String? = nil, metaData: MetaData? = nil, data: AnyType? = nil) { + public func beacon( + action: String, + element: String, + id: String, + type: String? = nil, + metaData: MetaDataType? = nil, + data: AnyType? = nil + ) { self.beaconFn( AssetBeacon( action: action, element: element, - asset: BeaconableAsset(id: id, type: type, metaData: metaData), + asset: BeaconableAsset(id: id, type: type, metaData: metaData.map { MetaData(beacon: $0.beacon) }), + data: data + ) + ) + } + + /** + Sends a beacon through the JavaScript beacon plugin + - parameters: + - action: The type of action that occurred + - element: The type of element in the asset that triggered the beacon + - id: The ID of the asset that triggered the beacon + - data: Additional arbitrary data to include in the beacon + */ + public func beacon( + action: String, + element: String, + id: String, + type: String? = nil, + data: AnyType? = nil + ) { + self.beaconFn( + AssetBeacon( + action: action, + element: element, + asset: BeaconableAsset(id: id, type: type), data: data ) ) diff --git a/ios/plugins/BeaconPlugin/ViewInspector/BeaconPluginTests.swift b/ios/plugins/BeaconPlugin/ViewInspector/BeaconPluginTests.swift index facde6f21..0d327eeb9 100644 --- a/ios/plugins/BeaconPlugin/ViewInspector/BeaconPluginTests.swift +++ b/ios/plugins/BeaconPlugin/ViewInspector/BeaconPluginTests.swift @@ -46,6 +46,32 @@ class BeaconPluginTests: XCTestCase { wait(for: [exp, expect], timeout: 10) } + func testBeaconContextWithMetaData() throws { + let expect = expectation(description: "Beacon Called") + let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in + guard + beaconObj.action == "clicked", + beaconObj.element == "button", + beaconObj.asset.id == "test", + case .dictionary(let data) = beaconObj.asset.metaData?.beacon, + data["field"] == "value" + else { return XCTFail("incorrect beacon information") } + expect.fulfill() + } + + let context = BeaconContext(beacon) + let data = MetaData(beacon: .dictionary(data: ["field": "value"])) + var tree = TestButton(metaData: data) + + let exp = tree.on(\.didAppear) { view in + try view.button().tap() + } + + ViewHosting.host(view: tree.environment(\.beaconContext, context)) + + wait(for: [exp, expect], timeout: 10) + } + func testSendsViewBeacon() { let beaconed = expectation(description: "View beacon called") let plugin = BeaconPlugin(plugins: []) { (beacon) in @@ -65,10 +91,15 @@ class BeaconPluginTests: XCTestCase { extension TestButton: Inspectable {} struct TestButton: View { @Environment(\.beaconContext) var beaconContext + var metaData: MetaData? internal var didAppear: ((Self) -> Void)? var body: some View { Button(action: { - beaconContext?.beacon(action: "clicked", element: "button", id: "test") + if let data = metaData { + beaconContext?.beacon(action: "clicked", element: "button", id: "test", metaData: data) + } else { + beaconContext?.beacon(action: "clicked", element: "button", id: "test") + } }, label: {Text("Beacon")}) .onAppear { self.didAppear?(self) } }