diff --git a/Sources/DataTransferObjects/Query/CustomQuery.swift b/Sources/DataTransferObjects/Query/CustomQuery.swift index 8a34e90..e180152 100644 --- a/Sources/DataTransferObjects/Query/CustomQuery.swift +++ b/Sources/DataTransferObjects/Query/CustomQuery.swift @@ -16,7 +16,7 @@ public struct CustomQuery: Codable, Hashable, Equatable { limit: Int? = nil, context: QueryContext? = nil, threshold: Int? = nil, metric: TopNMetricSpec? = nil, dimension: DimensionSpec? = nil, dimensions: [DimensionSpec]? = nil, - steps: [Filter]? = nil, stepNames: [String]? = nil) + steps: [FunnelStep]? = nil) { self.queryType = queryType self.compilationStatus = compilationStatus @@ -42,7 +42,6 @@ public struct CustomQuery: Codable, Hashable, Equatable { self.dimension = dimension self.dimensions = dimensions self.steps = steps - self.stepNames = stepNames } public init(queryType: CustomQuery.QueryType, @@ -59,7 +58,7 @@ public struct CustomQuery: Codable, Hashable, Equatable { limit: Int? = nil, context: QueryContext? = nil, threshold: Int? = nil, metric: TopNMetricSpec? = nil, dimension: DimensionSpec? = nil, dimensions: [DimensionSpec]? = nil, - steps: [Filter]? = nil, stepNames: [String]? = nil) + steps: [FunnelStep]? = nil) { self.queryType = queryType self.compilationStatus = compilationStatus @@ -81,7 +80,6 @@ public struct CustomQuery: Codable, Hashable, Equatable { self.dimension = dimension self.dimensions = dimensions self.steps = steps - self.stepNames = stepNames } public enum QueryType: String, Codable, CaseIterable, Identifiable { @@ -138,7 +136,7 @@ public struct CustomQuery: Codable, Hashable, Equatable { public var dimensions: [DimensionSpec]? /// Only for funnel Queries: A list of filters that form the steps of the funnel - public var steps: [Filter]? + public var steps: [FunnelStep]? /// Only for funnel Queries: An optional List of names for the funnel steps public var stepNames: [String]? @@ -192,7 +190,7 @@ public struct CustomQuery: Codable, Hashable, Equatable { self.dimension = try container.decodeIfPresent(DimensionSpec.self, forKey: CustomQuery.CodingKeys.dimension) self.metric = try container.decodeIfPresent(TopNMetricSpec.self, forKey: CustomQuery.CodingKeys.metric) self.dimensions = try container.decodeIfPresent([DimensionSpec].self, forKey: CustomQuery.CodingKeys.dimensions) - self.steps = try container.decodeIfPresent([Filter].self, forKey: CustomQuery.CodingKeys.steps) + self.steps = try container.decodeIfPresent([FunnelStep].self, forKey: CustomQuery.CodingKeys.steps) self.stepNames = try container.decodeIfPresent([String].self, forKey: CustomQuery.CodingKeys.stepNames) if let intervals = try? container.decode(QueryTimeIntervalsContainer.self, forKey: CustomQuery.CodingKeys.intervals) { diff --git a/Sources/DataTransferObjects/Query/FunnelStep.swift b/Sources/DataTransferObjects/Query/FunnelStep.swift new file mode 100644 index 0000000..0a9e35c --- /dev/null +++ b/Sources/DataTransferObjects/Query/FunnelStep.swift @@ -0,0 +1,11 @@ +import Foundation + +public struct FunnelStep: Codable, Hashable, Equatable { + public init(filter: Filter? = nil, name: String) { + self.filter = filter + self.name = name + } + + public let filter: Filter? + public let name: String +} diff --git a/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Funnel.swift b/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Funnel.swift index 9400226..47a8360 100644 --- a/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Funnel.swift +++ b/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Funnel.swift @@ -3,16 +3,18 @@ extension CustomQuery { var query = self guard let steps = steps else { throw QueryGenerationError.keyMissing(reason: "Missing key 'steps'") } - let stepNames = stepNames ?? [] + + let stepFilters = steps.compactMap({ $0.filter }) + let stepNames = steps.compactMap({ $0.name }) // Generate Filter Statement - let stepsFilters = Filter.or(.init(fields: steps)) + let stepsFilters = Filter.or(.init(fields: stepFilters)) let queryFilter = filter && stepsFilters // Generate Aggregations let aggregationNamePrefix = "_funnel_step_" var aggregations = [Aggregator]() - for (index, step) in steps.enumerated() { + for (index, step) in stepFilters.enumerated() { aggregations.append(.filtered(.init( filter: step, aggregator: .thetaSketch(.init( diff --git a/Tests/QueryGenerationTests/CompileDownTests.swift b/Tests/QueryGenerationTests/CompileDownTests.swift index 76dcf19..57bf2e3 100644 --- a/Tests/QueryGenerationTests/CompileDownTests.swift +++ b/Tests/QueryGenerationTests/CompileDownTests.swift @@ -10,13 +10,15 @@ final class CompileDownTests: XCTestCase { let appID2 = UUID() func testFunnel() throws { - let steps: [Filter] = [ - .selector(.init(dimension: "type", value: "appLaunchedRegularly")), - .selector(.init(dimension: "type", value: "dataEntered")), - .selector(.init(dimension: "type", value: "paywallSeen")), - .selector(.init(dimension: "type", value: "conversion")) + + + let steps: [FunnelStep] = [ + .init(filter: .selector(.init(dimension: "type", value: "appLaunchedRegularly")), name: "Regular Launch"), + .init(filter: .selector(.init(dimension: "type", value: "dataEntered")), name: "Data Entered"), + .init(filter: .selector(.init(dimension: "type", value: "paywallSeen")), name: "Paywall Presented"), + .init(filter: .selector(.init(dimension: "type", value: "conversion")), name: "Conversion"), ] - + let query = CustomQuery(queryType: .funnel, relativeIntervals: relativeIntervals, granularity: .all, steps: steps) let precompiledQuery = try query.precompile(organizationAppIDs: [appID1, appID2], isSuperOrg: false) diff --git a/Tests/QueryGenerationTests/FunnelQueryGenerationTests.swift b/Tests/QueryGenerationTests/FunnelQueryGenerationTests.swift index c0e15d5..3aced6b 100644 --- a/Tests/QueryGenerationTests/FunnelQueryGenerationTests.swift +++ b/Tests/QueryGenerationTests/FunnelQueryGenerationTests.swift @@ -2,20 +2,13 @@ import XCTest final class FunnelQueryGenerationTests: XCTestCase { - let steps: [Filter] = [ - .selector(.init(dimension: "type", value: "appLaunchedRegularly")), - .selector(.init(dimension: "type", value: "dataEntered")), - .selector(.init(dimension: "type", value: "paywallSeen")), - .selector(.init(dimension: "type", value: "conversion")) + let steps: [FunnelStep] = [ + .init(filter: .selector(.init(dimension: "type", value: "appLaunchedRegularly")), name: "Regular Launch"), + .init(filter: .selector(.init(dimension: "type", value: "dataEntered")), name: "Data Entered"), + .init(filter: .selector(.init(dimension: "type", value: "paywallSeen")), name: "Paywall Presented"), + .init(filter: .selector(.init(dimension: "type", value: "conversion")), name: "Conversion"), ] - - let stepNames: [String] = [ - "Regular Launch", - "Data Entered", - "Paywall Presented", - "Conversion" - ] - + let tinyQuery = CustomQuery( queryType: .groupBy, dataSource: "telemetry-signals", @@ -142,7 +135,7 @@ final class FunnelQueryGenerationTests: XCTestCase { ) func testExample() throws { - let startingQuery = CustomQuery(queryType: .funnel, granularity: .all, steps: steps, stepNames: stepNames) + let startingQuery = CustomQuery(queryType: .funnel, granularity: .all, steps: steps) let generatedTinyQuery = try startingQuery.precompiledFunnelQuery() XCTAssertEqual(tinyQuery.filter, generatedTinyQuery.filter) @@ -153,7 +146,7 @@ final class FunnelQueryGenerationTests: XCTestCase { func testWithAdditionalFilters() throws { let additionalFilter = Filter.selector(.init(dimension: "something", value: "other")) - let startingQuery = CustomQuery(queryType: .funnel, filter: additionalFilter, granularity: .all, steps: steps, stepNames: stepNames) + let startingQuery = CustomQuery(queryType: .funnel, filter: additionalFilter, granularity: .all, steps: steps) let generatedTinyQuery = try startingQuery.precompiledFunnelQuery() let expectedFilter = Filter.and(.init(fields: [ @@ -179,7 +172,7 @@ final class FunnelQueryGenerationTests: XCTestCase { ) ] - let startingQuery = CustomQuery(queryType: .funnel, relativeIntervals: relativeTimeIntervals, granularity: .all, steps: steps, stepNames: stepNames) + let startingQuery = CustomQuery(queryType: .funnel, relativeIntervals: relativeTimeIntervals, granularity: .all, steps: steps) let generatedTinyQuery = try startingQuery.precompiledFunnelQuery() XCTAssertEqual(startingQuery.relativeIntervals, generatedTinyQuery.relativeIntervals) diff --git a/Tests/QueryTests/CustomQueryTests.swift b/Tests/QueryTests/CustomQueryTests.swift index 87b1002..8ea37aa 100644 --- a/Tests/QueryTests/CustomQueryTests.swift +++ b/Tests/QueryTests/CustomQueryTests.swift @@ -259,10 +259,9 @@ final class CustomQueryTests: XCTestCase { queryType: .funnel, granularity: .all, steps: [ - .selector(.init(dimension: "something", value: "one")), - .selector(.init(dimension: "other", value: "two")), - ], - stepNames: ["Step One", "Step Two"] + .init(filter: .selector(.init(dimension: "something", value: "one")), name: "Step One"), + .init(filter: .selector(.init(dimension: "other", value: "two")), name: "Step Twp"), + ] ) } }