mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-09 22:35:56 +05:45
other: send fallback_original_step_id and always send blob_ref on workflow events (#7878)
* Send fallback_original_step_id and always send blob_ref on workflow events khepri publishes ~f fallback copies of screens for audience-targeted experiments (WFL-626). Workflow events now carry the copy's fallback_original_step_id and send blob_ref whenever the SDK has one, so khepri resolves each step against its exact blob. Experiment params are read from step metadata first, then param_values. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Trim workflow event comments Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Drop legacy blob_ref decoding for queued workflow events Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Revert unrelated Package.resolved change Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Rename experimentParam to experimentValueFromMetadataOrParams Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f9d0e4642a
commit
4a87bf64f3
9 files changed
+244
-36
No files matched your search
@@ -114,6 +114,8 @@ final class WorkflowStepEventTracker {
|
||||
entryReason: entryReason,
|
||||
isFirstStep: step.id == (self.resolvedInitialStepId ?? self.workflow.initialStepId),
|
||||
isLastStep: Self.isTerminalStep(step),
|
||||
workflowBlobRef: self.workflowBlobRef,
|
||||
fallbackOriginalStepId: step.fallbackOriginalStepId,
|
||||
experiment: self.experimentData(for: step)
|
||||
)
|
||||
}
|
||||
@@ -121,15 +123,11 @@ final class WorkflowStepEventTracker {
|
||||
private func experimentData(for step: WorkflowStep) -> WorkflowEvent.ExperimentData? {
|
||||
guard let experimentId = step.experimentId,
|
||||
let experimentVariant = step.experimentVariant,
|
||||
let workflowBlobRef = self.workflowBlobRef else {
|
||||
self.workflowBlobRef != nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return .init(
|
||||
experimentId: experimentId,
|
||||
experimentVariant: experimentVariant,
|
||||
workflowBlobRef: workflowBlobRef
|
||||
)
|
||||
return .init(experimentId: experimentId, experimentVariant: experimentVariant)
|
||||
}
|
||||
|
||||
/// A step is terminal when none of its trigger actions navigate to another step.
|
||||
|
||||
@@ -240,18 +240,18 @@ private extension CustomPaywallEvent {
|
||||
|
||||
private extension WorkflowEvent {
|
||||
|
||||
func workflowEventMap() -> [String: Any] {
|
||||
let typeName: String = {
|
||||
switch self {
|
||||
case .stepStarted: return "workflow_step_started"
|
||||
case .stepCompleted: return "workflow_step_completed"
|
||||
case .close: return "workflow_close"
|
||||
}
|
||||
}()
|
||||
var typeName: String {
|
||||
switch self {
|
||||
case .stepStarted: return "workflow_step_started"
|
||||
case .stepCompleted: return "workflow_step_completed"
|
||||
case .close: return "workflow_close"
|
||||
}
|
||||
}
|
||||
|
||||
func workflowEventMap() -> [String: Any] {
|
||||
var result: [String: Any] = [
|
||||
"discriminator": "workflows",
|
||||
"type": typeName,
|
||||
"type": self.typeName,
|
||||
"id": self.creationData.id.uuidString,
|
||||
"timestamp": self.creationData.date.millisecondsSince1970,
|
||||
"workflow_id": self.data.workflowId,
|
||||
@@ -265,10 +265,13 @@ private extension WorkflowEvent {
|
||||
if let entryReason = self.data.entryReason { result["entry_reason"] = entryReason }
|
||||
if let isFirstStep = self.data.isFirstStep { result["is_first_step"] = isFirstStep }
|
||||
if let isLastStep = self.data.isLastStep { result["is_last_step"] = isLastStep }
|
||||
if let workflowBlobRef = self.data.workflowBlobRef { result["blob_ref"] = workflowBlobRef }
|
||||
if let fallbackOriginalStepId = self.data.fallbackOriginalStepId {
|
||||
result["fallback_original_step_id"] = fallbackOriginalStepId
|
||||
}
|
||||
if let experiment = self.data.experiment {
|
||||
result["experiment_id"] = experiment.experimentId
|
||||
result["experiment_variant"] = experiment.experimentVariant
|
||||
result["blob_ref"] = experiment.workflowBlobRef
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -45,6 +45,7 @@ extension FeatureEventsRequest {
|
||||
let experimentId: String?
|
||||
let experimentVariant: String?
|
||||
let workflowBlobRef: String?
|
||||
let fallbackOriginalStepId: String?
|
||||
}
|
||||
// swiftlint:enable nesting
|
||||
|
||||
@@ -86,7 +87,8 @@ extension FeatureEventsRequest.WorkflowEvent {
|
||||
isLastStep: event.data.isLastStep,
|
||||
experimentId: event.data.experiment?.experimentId,
|
||||
experimentVariant: event.data.experiment?.experimentVariant,
|
||||
workflowBlobRef: event.data.experiment?.workflowBlobRef
|
||||
workflowBlobRef: event.data.workflowBlobRef,
|
||||
fallbackOriginalStepId: event.data.fallbackOriginalStepId
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
@@ -181,6 +183,7 @@ extension FeatureEventsRequest.WorkflowEvent.Properties: Encodable {
|
||||
try container.encodeIfPresent(experimentId, forKey: .experimentId)
|
||||
try container.encodeIfPresent(experimentVariant, forKey: .experimentVariant)
|
||||
try container.encodeIfPresent(workflowBlobRef, forKey: .workflowBlobRef)
|
||||
try container.encodeIfPresent(fallbackOriginalStepId, forKey: .fallbackOriginalStepId)
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
@@ -196,6 +199,7 @@ extension FeatureEventsRequest.WorkflowEvent.Properties: Encodable {
|
||||
case experimentId = "experiment_id"
|
||||
case experimentVariant = "experiment_variant"
|
||||
case workflowBlobRef = "blob_ref"
|
||||
case fallbackOriginalStepId = "fallback_original_step_id"
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -99,17 +99,26 @@ import Foundation
|
||||
}
|
||||
}
|
||||
|
||||
public var experimentId: String? { self.stringParam(Self.experimentIdParam) }
|
||||
/// Set on khepri's `<id>~f` fallback copies: the id of the step they were copied from.
|
||||
public var fallbackOriginalStepId: String? { Self.string(self.metadata?[Self.fallbackOriginalStepIdKey]) }
|
||||
|
||||
public var experimentVariant: String? { self.stringParam(Self.experimentVariantParam) }
|
||||
/// `metadata` first, `param_values` for blobs published before khepri moves them to `metadata`.
|
||||
public var experimentId: String? { self.experimentValueFromMetadataOrParams(Self.experimentIdParam) }
|
||||
|
||||
private func stringParam(_ key: String) -> String? {
|
||||
guard case let .string(value)? = self.paramValues[key] else { return nil }
|
||||
return value
|
||||
public var experimentVariant: String? { self.experimentValueFromMetadataOrParams(Self.experimentVariantParam) }
|
||||
|
||||
private func experimentValueFromMetadataOrParams(_ key: String) -> String? {
|
||||
return Self.string(self.metadata?[key]) ?? Self.string(self.paramValues[key])
|
||||
}
|
||||
|
||||
private static func string(_ value: AnyDecodable?) -> String? {
|
||||
guard case let .string(string)? = value else { return nil }
|
||||
return string
|
||||
}
|
||||
|
||||
private static let experimentIdParam = "experiment_id"
|
||||
private static let experimentVariantParam = "experiment_variant"
|
||||
private static let fallbackOriginalStepIdKey = "fallback_original_step_id"
|
||||
|
||||
// `paramValues`, `outputs`, and `metadata` carry backend step config that the renderer doesn't
|
||||
// read directly (`metadata` via `stepScreenType`, `paramValues` via the experiment
|
||||
|
||||
@@ -66,6 +66,8 @@ extension WorkflowEvent {
|
||||
public var entryReason: String?
|
||||
public var isFirstStep: Bool?
|
||||
public var isLastStep: Bool?
|
||||
public var workflowBlobRef: String?
|
||||
public var fallbackOriginalStepId: String?
|
||||
public var experiment: ExperimentData?
|
||||
|
||||
public init(
|
||||
@@ -81,6 +83,8 @@ extension WorkflowEvent {
|
||||
entryReason: String? = nil,
|
||||
isFirstStep: Bool? = nil,
|
||||
isLastStep: Bool? = nil,
|
||||
workflowBlobRef: String? = nil,
|
||||
fallbackOriginalStepId: String? = nil,
|
||||
experiment: ExperimentData? = nil
|
||||
) {
|
||||
self.workflowId = workflowId
|
||||
@@ -95,6 +99,8 @@ extension WorkflowEvent {
|
||||
self.entryReason = entryReason
|
||||
self.isFirstStep = isFirstStep
|
||||
self.isLastStep = isLastStep
|
||||
self.workflowBlobRef = workflowBlobRef
|
||||
self.fallbackOriginalStepId = fallbackOriginalStepId
|
||||
self.experiment = experiment
|
||||
}
|
||||
|
||||
@@ -128,12 +134,10 @@ extension WorkflowEvent {
|
||||
|
||||
public var experimentId: String
|
||||
public var experimentVariant: String
|
||||
public var workflowBlobRef: String
|
||||
|
||||
public init(experimentId: String, experimentVariant: String, workflowBlobRef: String) {
|
||||
public init(experimentId: String, experimentVariant: String) {
|
||||
self.experimentId = experimentId
|
||||
self.experimentVariant = experimentVariant
|
||||
self.workflowBlobRef = workflowBlobRef
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -195,11 +195,58 @@ final class WorkflowStepEventTrackerTests: TestCase {
|
||||
tracker.trackInitialStep(step2)
|
||||
|
||||
expect(self.recorded).to(haveCount(3))
|
||||
expect(self.recorded[0].data.experiment?.workflowBlobRef) == "blob-ref-1"
|
||||
expect(self.recorded[1].data.experiment?.workflowBlobRef) == "blob-ref-1"
|
||||
expect(self.recorded[0].data.workflowBlobRef) == "blob-ref-1"
|
||||
expect(self.recorded[1].data.workflowBlobRef) == "blob-ref-1"
|
||||
expect(self.recorded[2].data.experiment).to(beNil())
|
||||
}
|
||||
|
||||
func testStepEventsCarryTheWorkflowBlobRefOutsideAnExperiment() throws {
|
||||
let workflow = try Self.makeWorkflow()
|
||||
let tracker = self.makeTracker(workflow: workflow, workflowBlobRef: "blob-ref-1")
|
||||
let step1 = try XCTUnwrap(workflow.steps["step_1"])
|
||||
let step2 = try XCTUnwrap(workflow.steps["step_2"])
|
||||
|
||||
tracker.trackInitialStep(step1)
|
||||
tracker.trackNavigation(from: step1, to: step2, entryReason: .forward)
|
||||
tracker.trackClose(step2)
|
||||
|
||||
expect(self.recorded).to(haveCount(4))
|
||||
expect(self.recorded.map(\.data.workflowBlobRef)) == Array(repeating: "blob-ref-1", count: 4)
|
||||
expect(self.recorded.allSatisfy { $0.data.experiment == nil }) == true
|
||||
}
|
||||
|
||||
// MARK: - Fallback copies
|
||||
|
||||
func testFallbackCopyEventsReportTheRawIdAndTheOriginalStepId() throws {
|
||||
let workflow = try Self.makeFallbackCopyWorkflow()
|
||||
let tracker = self.makeTracker(workflow: workflow, workflowBlobRef: "blob-ref-1")
|
||||
let entry = try XCTUnwrap(workflow.steps["entry"])
|
||||
let copy = try XCTUnwrap(workflow.steps["paywall_a~f"])
|
||||
|
||||
tracker.trackInitialStep(entry)
|
||||
tracker.trackNavigation(from: entry, to: copy, entryReason: .forward)
|
||||
tracker.trackClose(copy)
|
||||
|
||||
expect(self.recorded).to(haveCount(4))
|
||||
let startedEntry = try XCTUnwrap(Self.startedData(self.recorded[0]))
|
||||
expect(startedEntry.fallbackOriginalStepId).to(beNil())
|
||||
|
||||
let completedEntry = try XCTUnwrap(Self.completedData(self.recorded[1]))
|
||||
expect(completedEntry.toStepId) == "paywall_a~f"
|
||||
expect(completedEntry.fallbackOriginalStepId).to(beNil())
|
||||
|
||||
let startedCopy = try XCTUnwrap(Self.startedData(self.recorded[2]))
|
||||
expect(startedCopy.stepId) == "paywall_a~f"
|
||||
expect(startedCopy.fromStepId) == "entry"
|
||||
expect(startedCopy.fallbackOriginalStepId) == "paywall_a"
|
||||
expect(startedCopy.experiment).to(beNil())
|
||||
expect(startedCopy.workflowBlobRef) == "blob-ref-1"
|
||||
|
||||
let closedCopy = self.recorded[3].data
|
||||
expect(closedCopy.stepId) == "paywall_a~f"
|
||||
expect(closedCopy.fallbackOriginalStepId) == "paywall_a"
|
||||
}
|
||||
|
||||
func testExperimentIsNotReportedWithoutTheWorkflowBlobRef() throws {
|
||||
let workflow = try Self.makeWorkflow(
|
||||
step1ParamValuesJSON: #"{ "experiment_id": "exp_abc", "experiment_variant": "b" }"#
|
||||
@@ -308,6 +355,46 @@ private extension WorkflowStepEventTrackerTests {
|
||||
return try JSONDecoder.default.decode(PublishedWorkflow.self, from: data)
|
||||
}
|
||||
|
||||
/// `entry` navigates to `paywall_a~f`, the fallback copy of `paywall_a`.
|
||||
static func makeFallbackCopyWorkflow() throws -> PublishedWorkflow {
|
||||
let json = """
|
||||
{
|
||||
"id": "wf_test",
|
||||
"display_name": "Test Workflow",
|
||||
"initial_step_id": "entry",
|
||||
"steps": {
|
||||
"entry": {
|
||||
"id": "entry",
|
||||
"type": "screen",
|
||||
"triggers": [
|
||||
{"name":"Button","type":"on_press","action_id":"btn","component_id":"btn"}
|
||||
],
|
||||
"trigger_actions": { "btn": {"type":"step","step_id":"paywall_a~f"} }
|
||||
},
|
||||
"paywall_a": {
|
||||
"id": "paywall_a",
|
||||
"type": "screen",
|
||||
"screen_id": "pw_123",
|
||||
"param_values": { "experiment_id": "exp_abc", "experiment_variant": "b" }
|
||||
},
|
||||
"paywall_a~f": {
|
||||
"id": "paywall_a~f",
|
||||
"type": "screen",
|
||||
"screen_id": "pw_123",
|
||||
"metadata": { "fallback_original_step_id": "paywall_a" }
|
||||
}
|
||||
},
|
||||
"screens": {},
|
||||
"ui_config": {
|
||||
"app": { "colors": {}, "fonts": {} },
|
||||
"localizations": {}
|
||||
}
|
||||
}
|
||||
"""
|
||||
let data = try XCTUnwrap(json.data(using: .utf8))
|
||||
return try JSONDecoder.default.decode(PublishedWorkflow.self, from: data)
|
||||
}
|
||||
|
||||
static func startedData(_ event: WorkflowEvent) -> WorkflowEvent.Data? {
|
||||
guard case let .stepStarted(_, data) = event else { return nil }
|
||||
return data
|
||||
|
||||
@@ -631,10 +631,11 @@ class EventsManagerTests: TestCase {
|
||||
entryReason: "start",
|
||||
isFirstStep: true,
|
||||
isLastStep: false,
|
||||
workflowBlobRef: "blob-ref-1",
|
||||
fallbackOriginalStepId: "step-1-original",
|
||||
experiment: .init(
|
||||
experimentId: "exp-1",
|
||||
experimentVariant: "variant-a",
|
||||
workflowBlobRef: "blob-ref-1"
|
||||
experimentVariant: "variant-a"
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -648,6 +649,7 @@ class EventsManagerTests: TestCase {
|
||||
expect(map["experiment_id"] as? String) == "exp-1"
|
||||
expect(map["experiment_variant"] as? String) == "variant-a"
|
||||
expect(map["blob_ref"] as? String) == "blob-ref-1"
|
||||
expect(map["fallback_original_step_id"] as? String) == "step-1-original"
|
||||
}
|
||||
|
||||
func testWorkflowEventToMapOmitsNilOptionalFields() {
|
||||
@@ -666,6 +668,7 @@ class EventsManagerTests: TestCase {
|
||||
expect(map["experiment_id"]).to(beNil())
|
||||
expect(map["experiment_variant"]).to(beNil())
|
||||
expect(map["blob_ref"]).to(beNil())
|
||||
expect(map["fallback_original_step_id"]).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: - flushAllEvents
|
||||
|
||||
@@ -796,6 +796,85 @@ class WorkflowResponseTests: TestCase {
|
||||
expect(step.experimentVariant).to(beNil())
|
||||
}
|
||||
|
||||
func testDecodeWorkflowStepExperimentParamsFromMetadata() throws {
|
||||
let json = """
|
||||
{
|
||||
"id": "step_1",
|
||||
"type": "screen",
|
||||
"metadata": { "experiment_id": "exp_abc", "experiment_variant": "holdout" }
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let step = try JSONDecoder.default.decode(WorkflowStep.self, from: json)
|
||||
|
||||
expect(step.experimentId) == "exp_abc"
|
||||
expect(step.experimentVariant) == "holdout"
|
||||
}
|
||||
|
||||
func testDecodeWorkflowStepExperimentParamsPreferMetadataOverParamValues() throws {
|
||||
let json = """
|
||||
{
|
||||
"id": "step_1",
|
||||
"type": "screen",
|
||||
"param_values": { "experiment_id": "exp_old", "experiment_variant": "a" },
|
||||
"metadata": { "experiment_id": "exp_new", "experiment_variant": "b" }
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let step = try JSONDecoder.default.decode(WorkflowStep.self, from: json)
|
||||
|
||||
expect(step.experimentId) == "exp_new"
|
||||
expect(step.experimentVariant) == "b"
|
||||
}
|
||||
|
||||
func testDecodeWorkflowWithFallbackCopyStep() throws {
|
||||
let json = """
|
||||
{
|
||||
"id": "wf_test",
|
||||
"display_name": "Test",
|
||||
"initial_step_id": "entry",
|
||||
"steps": {
|
||||
"entry": {
|
||||
"id": "entry",
|
||||
"type": "screen",
|
||||
"triggers": [
|
||||
{"name":"Button","type":"on_press","action_id":"btn","component_id":"btn"}
|
||||
],
|
||||
"trigger_actions": { "btn": {"type":"step","step_id":"paywall_a~f"} }
|
||||
},
|
||||
"paywall_a": {
|
||||
"id": "paywall_a",
|
||||
"type": "screen",
|
||||
"screen_id": "pw_123",
|
||||
"param_values": { "experiment_id": "exp_abc", "experiment_variant": "b" },
|
||||
"metadata": { "screen_type": ["paywall"] }
|
||||
},
|
||||
"paywall_a~f": {
|
||||
"id": "paywall_a~f",
|
||||
"type": "screen",
|
||||
"screen_id": "pw_123",
|
||||
"param_values": {},
|
||||
"metadata": { "screen_type": ["paywall"], "fallback_original_step_id": "paywall_a" }
|
||||
}
|
||||
},
|
||||
"screens": {},
|
||||
"ui_config": { "app": { "colors": {}, "fonts": {} }, "localizations": {} }
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let workflow = try JSONDecoder.default.decode(PublishedWorkflow.self, from: json)
|
||||
|
||||
let original = try XCTUnwrap(workflow.steps["paywall_a"])
|
||||
let copy = try XCTUnwrap(workflow.steps["paywall_a~f"])
|
||||
expect(workflow.steps["entry"]?.triggerActions["btn"]) == .step(stepId: "paywall_a~f")
|
||||
expect(copy.id) == "paywall_a~f"
|
||||
expect(copy.screenId) == original.screenId
|
||||
expect(copy.fallbackOriginalStepId) == "paywall_a"
|
||||
expect(copy.experimentId).to(beNil())
|
||||
expect(copy.stepScreenType) == ["paywall"]
|
||||
expect(original.fallbackOriginalStepId).to(beNil())
|
||||
}
|
||||
|
||||
func testDecodeWorkflowStepScreenTypeNilWhenMetadataNull() throws {
|
||||
let json = """
|
||||
{
|
||||
|
||||
@@ -168,7 +168,7 @@ class WorkflowEventsRequestTests: TestCase {
|
||||
.init(
|
||||
workflowId: "wfl_abc",
|
||||
stepId: "step-1",
|
||||
experiment: .init(experimentId: "exp-1", experimentVariant: "variant-a", workflowBlobRef: "blob-ref-1")
|
||||
workflowBlobRef: "blob-ref-1"
|
||||
)
|
||||
)
|
||||
let stored = try XCTUnwrap(storedEvent(from: event))
|
||||
@@ -177,6 +177,26 @@ class WorkflowEventsRequestTests: TestCase {
|
||||
|
||||
expect(request.properties.workflowBlobRef) == "blob-ref-1"
|
||||
expect(json).to(contain("\"blob_ref\":\"blob-ref-1\""))
|
||||
expect(json).toNot(contain("experiment_id"))
|
||||
}
|
||||
|
||||
func testFallbackOriginalStepIdInWireFormat() throws {
|
||||
let event = WorkflowEvent.stepStarted(
|
||||
.init(id: id, date: date),
|
||||
.init(
|
||||
workflowId: "wfl_abc",
|
||||
stepId: "paywall_a~f",
|
||||
fallbackOriginalStepId: "paywall_a"
|
||||
)
|
||||
)
|
||||
let stored = try XCTUnwrap(storedEvent(from: event))
|
||||
let request = try XCTUnwrap(FeatureEventsRequest.WorkflowEvent(storedEvent: stored))
|
||||
let json = try encodedJSON(from: event)
|
||||
|
||||
expect(request.properties.stepId) == "paywall_a~f"
|
||||
expect(request.properties.fallbackOriginalStepId) == "paywall_a"
|
||||
expect(json).to(contain("\"step_id\":\"paywall_a~f\""))
|
||||
expect(json).to(contain("\"fallback_original_step_id\":\"paywall_a\""))
|
||||
}
|
||||
|
||||
func testCloseKeepsExperimentPropertiesInWireFormat() throws {
|
||||
@@ -185,10 +205,10 @@ class WorkflowEventsRequestTests: TestCase {
|
||||
.init(
|
||||
workflowId: "wfl_abc",
|
||||
stepId: "step-1",
|
||||
workflowBlobRef: "blob-ref-1",
|
||||
experiment: .init(
|
||||
experimentId: "exp-1",
|
||||
experimentVariant: "variant-a",
|
||||
workflowBlobRef: "blob-ref-1"
|
||||
experimentVariant: "variant-a"
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -256,6 +276,7 @@ class WorkflowEventsRequestTests: TestCase {
|
||||
expect(json).toNot(contain("experiment_id"))
|
||||
expect(json).toNot(contain("experiment_variant"))
|
||||
expect(json).toNot(contain("blob_ref"))
|
||||
expect(json).toNot(contain("fallback_original_step_id"))
|
||||
}
|
||||
|
||||
func testExperimentPropertiesInWireFormat() throws {
|
||||
@@ -264,10 +285,10 @@ class WorkflowEventsRequestTests: TestCase {
|
||||
.init(
|
||||
workflowId: "wfl_abc",
|
||||
stepId: "step-1",
|
||||
workflowBlobRef: "blob-ref-1",
|
||||
experiment: .init(
|
||||
experimentId: "exp-1",
|
||||
experimentVariant: "variant-a",
|
||||
workflowBlobRef: "blob-ref-1"
|
||||
experimentVariant: "variant-a"
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -284,10 +305,10 @@ class WorkflowEventsRequestTests: TestCase {
|
||||
.init(
|
||||
workflowId: "wfl_abc",
|
||||
stepId: "step-1",
|
||||
workflowBlobRef: "blob-ref-1",
|
||||
experiment: .init(
|
||||
experimentId: "exp-1",
|
||||
experimentVariant: "variant-a",
|
||||
workflowBlobRef: "blob-ref-1"
|
||||
experimentVariant: "variant-a"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user