Skip to content
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

[iOS] relax requirements for sending beacon metadata #88

Merged
merged 1 commit into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions ios/packages/core/Sources/Types/Beacon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<MetaDataType: BeaconableMetaData>(
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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void>?

/// Additional metaData for beaconing
var metaData: MetaData?
}

/**
Expand Down Expand Up @@ -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: {
Expand Down
37 changes: 35 additions & 2 deletions ios/plugins/BeaconPlugin/Sources/SwiftUIBeaconPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<MetaDataType: BeaconableMetaData>(
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
)
)
Expand Down
33 changes: 32 additions & 1 deletion ios/plugins/BeaconPlugin/ViewInspector/BeaconPluginTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<DefaultBeacon>(plugins: []) { (beacon) in
Expand All @@ -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) }
}
Expand Down