-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refactor query compilation * Fix failing tests * Some Tests * Finish test for compiledown methods * Remove a ton of whitespace * Use isEmpty instead of count == 0 * More Linter Cleanup * add compilationstatus property * Whitespace * Throw error when intervals not set * QueryGenerationError Should be string based * Revert "QueryGenerationError Should be string based" This reverts commit e7e0ced. * Move funnel creation into CustomQuery, keeping the original query * Remove nil checks for properties. Instead, we use the compilation status property * Remove an unused swiftlint rule
- Loading branch information
Showing
22 changed files
with
528 additions
and
211 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
Sources/DataTransferObjects/Query/CustomQuery+CompileDown.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import Foundation | ||
|
||
public extension CustomQuery { | ||
enum QueryGenerationError: Error { | ||
case notAllowed(reason: String) | ||
case notImplemented(reason: String) | ||
case keyMissing(reason: String) | ||
case compilationStatusError | ||
} | ||
|
||
/// Compiles almost all TelemetryDeck-properties down into a regular query that can be enqueued in the Query Task Runner. | ||
/// | ||
/// Will not compile the relativeTimeIntervals property into intervals. These need to be calculated directly before running the query. | ||
/// | ||
/// @warn Both precompile AND compileToRunnableQuery need to be run before a query can safely be handed to Druid! | ||
/// | ||
/// @see compileToRunnableQuery | ||
func precompile(organizationAppIDs: [UUID], isSuperOrg: Bool) throws -> CustomQuery { | ||
guard (self.compilationStatus ?? .notCompiled) == .notCompiled else { | ||
throw QueryGenerationError.compilationStatusError | ||
} | ||
|
||
// Make an editable copy of self | ||
var query = self | ||
|
||
// Make sure either intervals or relative intervals are set | ||
guard query.intervals != nil || query.relativeIntervals != nil else { | ||
throw QueryGenerationError.keyMissing(reason: "Either 'relativeIntervals' or 'intervals' need to be set") | ||
} | ||
|
||
// Custom Query Types | ||
if query.queryType == .funnel { | ||
query = try self.precompiledFunnelQuery() | ||
} | ||
|
||
// Apply base filters and data source | ||
query = try Self.applyBaseFilters(query: query, organizationAppIDs: organizationAppIDs, isSuperOrg: isSuperOrg) | ||
|
||
// Update compilationStatus so the next steps in the pipeline are sure the query has been precompiled | ||
query.compilationStatus = .precompiled | ||
|
||
return query | ||
} | ||
|
||
/// Compiles all TelemetryDeck additions down into a regular query that can be run on Apache Druid. | ||
/// | ||
/// Since this includes the `relativeTimeIntervals` property, this should only be called directly before actually running the query. | ||
/// | ||
/// @warn Both precompile AND compileToRunnableQuery need to be run before a query can safely be handed to Druid! | ||
/// | ||
/// @see precompile | ||
func compileToRunnableQuery() throws -> CustomQuery { | ||
guard self.compilationStatus == .precompiled else { | ||
throw QueryGenerationError.compilationStatusError | ||
} | ||
|
||
// Make an editable copy of self | ||
var query = self | ||
|
||
// Compile relative Time intervals | ||
if let relativeIntervals = query.relativeIntervals { | ||
query.intervals = relativeIntervals.map { QueryTimeInterval.from(relativeTimeInterval: $0) } | ||
} | ||
|
||
guard query.intervals != nil && !query.intervals!.isEmpty else { | ||
throw QueryGenerationError.keyMissing(reason: "Either 'relativeIntervals' or 'intervals' need to be set") | ||
} | ||
|
||
// Update compilationStatus so the next steps in the pipeline are sure the query has been compiled | ||
query.compilationStatus = .compiled | ||
|
||
return query | ||
} | ||
} | ||
extension CustomQuery { | ||
static func applyBaseFilters(query: CustomQuery, organizationAppIDs: [UUID]?, isSuperOrg: Bool) throws -> CustomQuery { | ||
// make an editable copy of the query | ||
var query = query | ||
|
||
// Throw if noFilter is requested by an ord that is not super | ||
let baseFilters = query.baseFilters ?? .thisOrganization | ||
if baseFilters == .noFilter { | ||
guard isSuperOrg else { | ||
throw QueryGenerationError.notAllowed(reason: "The noFilter base filter is not implemented.") | ||
} | ||
} else { | ||
query.dataSource = .init("telemetry-signals") | ||
query.context = QueryContext(timeout: "200000", skipEmptyBuckets: false) | ||
} | ||
|
||
// Apply filters according to the basefilters property | ||
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) | ||
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) | ||
return query | ||
|
||
case .exampleData: | ||
let appIDFilter = Filter.selector(.init(dimension: "appID", value: "B97579B6-FFB8-4AC5-AAA7-DA5796CC5DCE")) | ||
query.filter = query.filter && appIDFilter && testModeFilter(for: query) | ||
return query | ||
|
||
case .noFilter: | ||
return query | ||
} | ||
} | ||
|
||
/// 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")")) | ||
} | ||
|
||
// Given a list of app UUIDs, generates a Filter object that restricts a query to only apps with either of the given IDs | ||
static func appIDFilter(for organizationAppIDs: [UUID]) throws -> Filter { | ||
guard !organizationAppIDs.isEmpty else { | ||
throw QueryGenerationError.keyMissing(reason: "Missing organization app IDs") | ||
} | ||
|
||
guard organizationAppIDs.count != 1 else { | ||
return Filter.selector(.init(dimension: "appID", value: organizationAppIDs.first!.uuidString)) | ||
} | ||
|
||
let filters = organizationAppIDs.compactMap { | ||
Filter.selector(.init(dimension: "appID", value: $0.uuidString)) | ||
} | ||
|
||
return Filter.or(.init(fields: filters)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.