Skip to content

Commit

Permalink
Add CustomQuery.stableHashValue property
Browse files Browse the repository at this point in the history
  • Loading branch information
winsmith committed Dec 11, 2023
1 parent 3f4ca6d commit 313e322
Show file tree
Hide file tree
Showing 46 changed files with 292 additions and 258 deletions.
11 changes: 10 additions & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
{
"object": {
"pins": [
{
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto.git",
"state": {
"branch": null,
"revision": "60f13f60c4d093691934dc6cfdf5f508ada1f894",
"version": "2.6.0"
}
},
{
"package": "DateOperations",
"repositoryURL": "git@github.com:TelemetryDeck/SwiftDateOperations.git",
"repositoryURL": "https://github.com/TelemetryDeck/SwiftDateOperations.git",
"state": {
"branch": null,
"revision": "9baf08c95057688deffe1f14dfcc871ba2ef38d2",
Expand Down
6 changes: 5 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ let package = Package(
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/TelemetryDeck/SwiftDateOperations.git", from: "1.0.3"),
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "DataTransferObjects",
dependencies: [.product(name: "DateOperations", package: "SwiftDateOperations")]
dependencies: [
.product(name: "DateOperations", package: "SwiftDateOperations"),
.product(name: "Crypto", package: "swift-crypto"),
]
),
.testTarget(
name: "DataTransferObjectsTests",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// ChartDefinitionDTO.swift
//
//
// Created by Daniel Jilg on 10.11.21.
Expand Down
2 changes: 1 addition & 1 deletion Sources/DataTransferObjects/DTOs/Aggregate.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// Aggregate.swift
//
//
// Created by Daniel Jilg on 15.04.21.
Expand Down
8 changes: 4 additions & 4 deletions Sources/DataTransferObjects/DTOs/DTOv2.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// InsightDTOs.swift
// DTOv2.swift
// InsightDTOs
//
// Created by Daniel Jilg on 17.08.21.
Expand Down Expand Up @@ -204,10 +204,10 @@ public enum DTOv2 {
case app
case website
}

/// If true, the app should display demo content instead of
public var showExampleData: Bool?

/// What colors should charts be in?
///
/// This should be formatted as
Expand Down Expand Up @@ -660,7 +660,7 @@ public extension DTOv2.Insight {
intervals: [],
granularity: .all,
aggregations: [
.longSum(.init(type: .longSum, name: "total_usage", fieldName: "count"))
.longSum(.init(type: .longSum, name: "total_usage", fieldName: "count")),
]
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// InsightCalculationResult.swift
//
//
// Created by Daniel Jilg on 09.04.21.
Expand Down
2 changes: 1 addition & 1 deletion Sources/DataTransferObjects/DTOs/InsightData.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// InsightData.swift
//
//
// Created by Daniel Jilg on 09.04.21.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// KafkaSignalStructure.swift
//
//
// Created by Daniel Jilg on 05.06.21.
Expand Down
2 changes: 1 addition & 1 deletion Sources/DataTransferObjects/DTOs/LexiconSignalDTO.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// LexiconSignalDTO.swift
//
//
// Created by Daniel Jilg on 12.05.21.
Expand Down
2 changes: 1 addition & 1 deletion Sources/DataTransferObjects/DTOs/OrganizationDTO.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// OrganizationDTO.swift
//
//
// Created by Daniel Jilg on 09.04.21.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// RegistrationRequestBody.swift
//
//
// Created by Daniel Jilg on 14.05.21.
Expand Down
2 changes: 1 addition & 1 deletion Sources/DataTransferObjects/DTOs/UserDTO.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// UserDTO.swift
//
//
// Created by Daniel Jilg on 09.04.21.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public extension String {
var camelCaseToWords: String {
unicodeScalars.reduce("") {
if CharacterSet.uppercaseLetters.contains($1) {
return ($0 + " " + String($1))
return $0 + " " + String($1)
}

return $0 + String($1)
Expand Down
58 changes: 29 additions & 29 deletions Sources/DataTransferObjects/Query/Aggregator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,57 +132,57 @@ public indirect enum Aggregator: Codable, Hashable, Equatable {

switch type {
case "count":
self = .count(try CountAggregator(from: decoder))
self = try .count(CountAggregator(from: decoder))
case "cardinality":
self = .cardinality(try CardinalityAggregator(from: decoder))
self = try .cardinality(CardinalityAggregator(from: decoder))
case "longSum":
self = .longSum(try GenericAggregator(from: decoder))
self = try .longSum(GenericAggregator(from: decoder))
case "doubleSum":
self = .doubleSum(try GenericAggregator(from: decoder))
self = try .doubleSum(GenericAggregator(from: decoder))
case "floatSum":
self = .floatSum(try GenericAggregator(from: decoder))
self = try .floatSum(GenericAggregator(from: decoder))
case "doubleMin":
self = .doubleMin(try GenericAggregator(from: decoder))
self = try .doubleMin(GenericAggregator(from: decoder))
case "doubleMax":
self = .doubleMax(try GenericAggregator(from: decoder))
self = try .doubleMax(GenericAggregator(from: decoder))
case "floatMin":
self = .floatMin(try GenericAggregator(from: decoder))
self = try .floatMin(GenericAggregator(from: decoder))
case "floatMax":
self = .floatMax(try GenericAggregator(from: decoder))
self = try .floatMax(GenericAggregator(from: decoder))
case "longMin":
self = .longMin(try GenericAggregator(from: decoder))
self = try .longMin(GenericAggregator(from: decoder))
case "longMax":
self = .longMax(try GenericAggregator(from: decoder))
self = try .longMax(GenericAggregator(from: decoder))
case "doubleMean":
self = .doubleMean(try GenericAggregator(from: decoder))
self = try .doubleMean(GenericAggregator(from: decoder))
case "doubleFirst":
self = .doubleFirst(try GenericTimeColumnAggregator(from: decoder))
self = try .doubleFirst(GenericTimeColumnAggregator(from: decoder))
case "doubleLast":
self = .doubleLast(try GenericTimeColumnAggregator(from: decoder))
self = try .doubleLast(GenericTimeColumnAggregator(from: decoder))
case "floatFirst":
self = .floatFirst(try GenericTimeColumnAggregator(from: decoder))
self = try .floatFirst(GenericTimeColumnAggregator(from: decoder))
case "floatLast":
self = .floatLast(try GenericTimeColumnAggregator(from: decoder))
self = try .floatLast(GenericTimeColumnAggregator(from: decoder))
case "longFirst":
self = .longFirst(try GenericTimeColumnAggregator(from: decoder))
self = try .longFirst(GenericTimeColumnAggregator(from: decoder))
case "longLast":
self = .longLast(try GenericTimeColumnAggregator(from: decoder))
self = try .longLast(GenericTimeColumnAggregator(from: decoder))
case "stringFirst":
self = .stringFirst(try GenericTimeColumnAggregator(from: decoder))
self = try .stringFirst(GenericTimeColumnAggregator(from: decoder))
case "stringLast":
self = .stringLast(try GenericTimeColumnAggregator(from: decoder))
self = try .stringLast(GenericTimeColumnAggregator(from: decoder))
case "doubleAny":
self = .doubleAny(try GenericAggregator(from: decoder))
self = try .doubleAny(GenericAggregator(from: decoder))
case "floatAny":
self = .floatAny(try GenericAggregator(from: decoder))
self = try .floatAny(GenericAggregator(from: decoder))
case "longAny":
self = .longAny(try GenericAggregator(from: decoder))
self = try .longAny(GenericAggregator(from: decoder))
case "stringAny":
self = .stringAny(try GenericAggregator(from: decoder))
self = try .stringAny(GenericAggregator(from: decoder))
case "thetaSketch":
self = .thetaSketch(try GenericAggregator(from: decoder))
self = try .thetaSketch(GenericAggregator(from: decoder))
case "filtered":
self = .filtered(try FilteredAggregator(from: decoder))
self = try .filtered(FilteredAggregator(from: decoder))

default:
throw EncodingError.invalidValue("Invalid type", .init(codingPath: [CodingKeys.type], debugDescription: "Invalid Type", underlyingError: nil))
Expand Down Expand Up @@ -277,7 +277,7 @@ public indirect enum Aggregator: Codable, Hashable, Equatable {

public struct CountAggregator: Codable, Hashable {
public init(name: String) {
self.type = .count
type = .count
self.name = name
}

Expand All @@ -290,7 +290,7 @@ public struct CountAggregator: Codable, Hashable {
/// Calcluate the cardinality of a dimension (deprecated)
public struct CardinalityAggregator: Codable, Hashable {
public init(name: String, fields: [String], byRow: Bool = false, round: Bool = true) {
self.type = .cardinality
type = .cardinality
self.name = name
self.fields = fields
self.byRow = byRow
Expand Down Expand Up @@ -382,7 +382,7 @@ public enum AggregatorType: String, Codable, Hashable {
/// Note: If only the filtered results are required, consider putting the filter on the query itself, which will be much faster since it does not require scanning all the data.
public struct FilteredAggregator: Codable, Hashable {
public init(filter: Filter, aggregator: Aggregator) {
self.type = .filtered
type = .filtered
self.filter = filter
self.aggregator = aggregator
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ extension CustomQuery {
switch baseFilters {
case .thisOrganization:
guard let organizationAppIDs = organizationAppIDs else { throw QueryGenerationError.keyMissing(reason: "Missing organization app IDs") }
query.filter = query.filter && (try appIDFilter(for: organizationAppIDs)) && testModeFilter(for: query)
query.filter = try query.filter && appIDFilter(for: organizationAppIDs) && testModeFilter(for: query)
return query

case .thisApp:
guard let appID = query.appID else { throw QueryGenerationError.keyMissing(reason: "Missing key 'appID'") }
query.filter = query.filter && (try appIDFilter(for: [appID])) && testModeFilter(for: query)
query.filter = try query.filter && appIDFilter(for: [appID]) && testModeFilter(for: query)
return query

case .exampleData:
Expand All @@ -121,7 +121,7 @@ extension CustomQuery {

/// Returns a filter according to the query objects `testMode` property.
static func testModeFilter(for query: CustomQuery) -> Filter {
return Filter.selector(.init(dimension: "isTestMode", value: "\(query.testMode ?? false ? "true" : "false")"))
Filter.selector(.init(dimension: "isTestMode", value: "\(query.testMode ?? false ? "true" : "false")"))
}

// Given a list of app UUIDs, generates a Filter object that restricts a query to only apps with either of the given IDs
Expand Down
67 changes: 39 additions & 28 deletions Sources/DataTransferObjects/Query/CustomQuery.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Crypto
import Foundation

/// Custom JSON based query
Expand Down Expand Up @@ -148,21 +149,31 @@ public struct CustomQuery: Codable, Hashable, Equatable {

/// Only for groupBy Queries: A list of dimensions to do the groupBy over, if queryType is groupBy
public var dimensions: [DimensionSpec]?

/// Only for funnel Queries: A list of filters that form the steps of the funnel
public var steps: [NamedFilter]?

/// Only for experiment Queries: The control cohort for the experiment
public var sample1: NamedFilter?

/// Only for experiment Queries: The experiment cohort for the experiment
public var sample2: NamedFilter?

/// Only for experiment Queries: A named filter that defines the successful cohort in the experiment.
///
/// Will be intersected with cohort 1 for success 1 and cohort 2 for success 2
public var successCriterion: NamedFilter?

public var stableHashValue: String {
// We've tried various hashing functions here and they are just not stable.
// So instead, let's convert to JSON and hash that.

guard let jsonData = try? JSONEncoder.telemetryEncoder.encode(self) else { return "nothashed" }
let digest = SHA256.hash(data: jsonData)

return digest.compactMap { String(format: "%02x", $0) }.joined()
}

public func hash(into hasher: inout Hasher) {
hasher.combine(queryType)
hasher.combine(compilationStatus)
Expand Down Expand Up @@ -197,34 +208,34 @@ public struct CustomQuery: Codable, Hashable, Equatable {
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CustomQuery.CodingKeys> = try decoder.container(keyedBy: CustomQuery.CodingKeys.self)

self.queryType = try container.decode(CustomQuery.QueryType.self, forKey: CustomQuery.CodingKeys.queryType)
self.compilationStatus = try container.decodeIfPresent(CompilationStatus.self, forKey: CustomQuery.CodingKeys.compilationStatus)
self.restrictions = try container.decodeIfPresent([QueryTimeInterval].self, forKey: CustomQuery.CodingKeys.restrictions)
self.dataSource = try container.decodeIfPresent(DataSource.self, forKey: CustomQuery.CodingKeys.dataSource)
self.descending = try container.decodeIfPresent(Bool.self, forKey: CustomQuery.CodingKeys.descending)
self.baseFilters = try container.decodeIfPresent(BaseFilters.self, forKey: CustomQuery.CodingKeys.baseFilters)
self.testMode = try container.decodeIfPresent(Bool.self, forKey: CustomQuery.CodingKeys.testMode)
self.filter = try container.decodeIfPresent(Filter.self, forKey: CustomQuery.CodingKeys.filter)
self.appID = try container.decodeIfPresent(UUID.self, forKey: CustomQuery.CodingKeys.appID)
self.relativeIntervals = try container.decodeIfPresent([RelativeTimeInterval].self, forKey: CustomQuery.CodingKeys.relativeIntervals)
self.granularity = try container.decode(QueryGranularity.self, forKey: CustomQuery.CodingKeys.granularity)
self.aggregations = try container.decodeIfPresent([Aggregator].self, forKey: CustomQuery.CodingKeys.aggregations)
self.postAggregations = try container.decodeIfPresent([PostAggregator].self, forKey: CustomQuery.CodingKeys.postAggregations)
self.limit = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.limit)
self.context = try container.decodeIfPresent(QueryContext.self, forKey: CustomQuery.CodingKeys.context)
self.threshold = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.threshold)
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([NamedFilter].self, forKey: CustomQuery.CodingKeys.steps)
self.sample1 = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.sample1)
self.sample2 = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.sample2)
self.successCriterion = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.successCriterion)
queryType = try container.decode(CustomQuery.QueryType.self, forKey: CustomQuery.CodingKeys.queryType)
compilationStatus = try container.decodeIfPresent(CompilationStatus.self, forKey: CustomQuery.CodingKeys.compilationStatus)
restrictions = try container.decodeIfPresent([QueryTimeInterval].self, forKey: CustomQuery.CodingKeys.restrictions)
dataSource = try container.decodeIfPresent(DataSource.self, forKey: CustomQuery.CodingKeys.dataSource)
descending = try container.decodeIfPresent(Bool.self, forKey: CustomQuery.CodingKeys.descending)
baseFilters = try container.decodeIfPresent(BaseFilters.self, forKey: CustomQuery.CodingKeys.baseFilters)
testMode = try container.decodeIfPresent(Bool.self, forKey: CustomQuery.CodingKeys.testMode)
filter = try container.decodeIfPresent(Filter.self, forKey: CustomQuery.CodingKeys.filter)
appID = try container.decodeIfPresent(UUID.self, forKey: CustomQuery.CodingKeys.appID)
relativeIntervals = try container.decodeIfPresent([RelativeTimeInterval].self, forKey: CustomQuery.CodingKeys.relativeIntervals)
granularity = try container.decode(QueryGranularity.self, forKey: CustomQuery.CodingKeys.granularity)
aggregations = try container.decodeIfPresent([Aggregator].self, forKey: CustomQuery.CodingKeys.aggregations)
postAggregations = try container.decodeIfPresent([PostAggregator].self, forKey: CustomQuery.CodingKeys.postAggregations)
limit = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.limit)
context = try container.decodeIfPresent(QueryContext.self, forKey: CustomQuery.CodingKeys.context)
threshold = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.threshold)
dimension = try container.decodeIfPresent(DimensionSpec.self, forKey: CustomQuery.CodingKeys.dimension)
metric = try container.decodeIfPresent(TopNMetricSpec.self, forKey: CustomQuery.CodingKeys.metric)
dimensions = try container.decodeIfPresent([DimensionSpec].self, forKey: CustomQuery.CodingKeys.dimensions)
steps = try container.decodeIfPresent([NamedFilter].self, forKey: CustomQuery.CodingKeys.steps)
sample1 = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.sample1)
sample2 = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.sample2)
successCriterion = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.successCriterion)

if let intervals = try? container.decode(QueryTimeIntervalsContainer.self, forKey: CustomQuery.CodingKeys.intervals) {
self.intervals = intervals.intervals
} else {
self.intervals = try container.decodeIfPresent([QueryTimeInterval].self, forKey: CustomQuery.CodingKeys.intervals)
intervals = try container.decodeIfPresent([QueryTimeInterval].self, forKey: CustomQuery.CodingKeys.intervals)
}
}
}
Loading

0 comments on commit 313e322

Please sign in to comment.