diff --git a/Cluster/QueryRunner.swift b/Cluster/QueryRunner.swift index 9fcbbd2..4962b2f 100644 --- a/Cluster/QueryRunner.swift +++ b/Cluster/QueryRunner.swift @@ -17,15 +17,28 @@ struct QueryRunner: View { @State var queryResultWrapper: QueryResultWrapper? @State var isLoading: Bool = false + @State var taskID: String = "" var body: some View { VStack(spacing: 10){ if let queryResult = queryResultWrapper?.result { ClusterChart(query: query, result: queryResult, type: type) + .frame(height: 135) + .id(queryResultWrapper) .padding(.horizontal) + } else { + SondrineAnimation() + .frame(width: 100, height: 100) + .opacity(0.5) + .padding() } HStack(spacing: 3){ + if isLoading { + ProgressView() + .scaleEffect(0.75) + .frame(height: 5) + } Spacer() if let queryResultWrapper = queryResultWrapper { Text("Updated") @@ -50,6 +63,33 @@ struct QueryRunner: View { } } } + .onChange(of: queryService.isTestingMode) { + Task { + do { + try await getQueryResult() + } catch { + print(error) + } + } + } + .onChange(of: queryService.timeWindowBeginning) { + Task { + do { + try await getQueryResult() + } catch { + print(error) + } + } + } + .onChange(of: queryService.timeWindowEnd) { + Task { + do { + try await getQueryResult() + } catch { + print(error) + } + } + } } private func getQueryResult() async throws { @@ -58,7 +98,7 @@ struct QueryRunner: View { isLoading = false } - let taskID = try await beginAsyncCalcV2() + taskID = try await beginAsyncCalcV2() try await getLastSuccessfulValue(taskID) @@ -84,6 +124,9 @@ extension QueryRunner { queryCopy.relativeIntervals = [RelativeTimeInterval(beginningDate: queryService.timeWindowBeginning.toRelativeDate(), endDate: queryService.timeWindowEnd.toRelativeDate())] } } + if queryCopy.testMode == nil { + queryCopy.testMode = queryService.isTestingMode + } let response: [String: String] = try await api.post(data: queryCopy, url: queryBeginURL) guard let taskID = response["queryTaskID"] else { diff --git a/Services/QueryService.swift b/Services/QueryService.swift index e842e39..72c5cd3 100644 --- a/Services/QueryService.swift +++ b/Services/QueryService.swift @@ -72,88 +72,4 @@ class QueryService: ObservableObject { } } - func getInsightQuery(ofInsightWithID insightID: DTOv2.Insight.ID) async throws -> CustomQuery { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - let url = api.urlForPath(apiVersion: .v3, "insights", insightID.uuidString, "query") - - struct ProduceQueryBody: Codable { - /// Is Test Mode enabled? (nil means false) - public var testMode: Bool? - /// Which time intervals are we looking at? - public var relativeInterval: RelativeTimeInterval? - public var interval: QueryTimeInterval? - } - - let produceQueryBody = ProduceQueryBody(testMode: isTestingMode, interval: .init(beginningDate: timeWindowBeginningDate, endDate: timeWindowEndDate)) - - api.post(produceQueryBody, to: url) { (result: Result) in - switch result { - case .success(let query): - continuation.resume(returning: query) - - case .failure(let error): - self.errorService.handle(transferError: error) - continuation.resume(throwing: error) - } - } - } - } - - func createTask(forQuery query: CustomQuery) async throws -> [String: String] { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[String: String], Error>) in - - // If the query has no specified interval, give it the default interval - var query = query - if query.relativeIntervals == nil && query.intervals == nil { - query.intervals = [.init(beginningDate: timeWindowBeginningDate, endDate: timeWindowEndDate)] - } - - let url = api.urlForPath(apiVersion: .v3, "query", "calculate-async") - api.post(query, to: url) { (result: Result<[String: String], TransferError>) in - switch result { - case .success(let taskID): - - continuation.resume(returning: taskID) - - case .failure(let error): - self.errorService.handle(transferError: error) - continuation.resume(throwing: error) - } - } - } - } - - func getTaskResult(forTaskID taskID: String) async throws -> QueryResultWrapper { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - let url = api.urlForPath(apiVersion: .v3, "task", taskID, "lastSuccessfulValue") - api.get(url) { (result: Result) in - switch result { - case .success(let queryResult): - - continuation.resume(returning: queryResult) - - case .failure(let error): - self.errorService.handle(transferError: error) - continuation.resume(throwing: error) - } - } - } - } - - func getTaskStatus(forTaskID taskID: String) async throws -> QueryTaskStatus { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - let url = api.urlForPath(apiVersion: .v3, "task", taskID, "status") - api.get(url) { (result: Result) in - switch result { - case .success(let queryStatusStruct): - let queryStatus = queryStatusStruct.status - continuation.resume(returning: queryStatus) - - case .failure(let error): - self.errorService.handle(transferError: error) - continuation.resume(throwing: error) - } - } - } - } } diff --git a/Shared/Insight and Groups/QueryView.swift b/Shared/Insight and Groups/QueryView.swift deleted file mode 100644 index d6e7102..0000000 --- a/Shared/Insight and Groups/QueryView.swift +++ /dev/null @@ -1,349 +0,0 @@ -// -// QueryView.swift -// Telemetry Viewer -// -// Created by Charlotte Böhm on 22.02.22. -// - -import DataTransferObjects -import SwiftUI -import Charts - -// @State var customQuery: CustomQuery - -@MainActor -class QueryViewModel: ObservableObject { - let queryService: QueryService - let customQuery: CustomQuery - let displayMode: InsightDisplayMode - let isSelected: Bool - - public let runningTimer = Timer.publish( - every: 0.5, // seconds - on: .main, - in: .common - ).autoconnect() - - public let successTimer = Timer.publish( - every: 60, // seconds - on: .main, - in: .common - ).autoconnect() - - init(queryService: QueryService, customQuery: CustomQuery, displayMode: InsightDisplayMode, isSelected: Bool) { - self.queryService = queryService - self.customQuery = customQuery - self.displayMode = displayMode - self.isSelected = isSelected - } - - @Published var loadingState: LoadingState = .loading - @Published var queryTaskStatus: QueryTaskStatus = .running - - var taskID: [String: String] = ["queryTaskID": ""] - @Published var queryResult: QueryResultWrapper? - @Published var chartDataSet: ChartDataSet? - - // func that posts the query on load and loads the last result - - func retrieveResults() async { - loadingState = .loading - - do { - taskID = try await queryService.createTask(forQuery: customQuery) - let queryTaskID = taskID["queryTaskID"] - let result = try await queryService.getTaskResult(forTaskID: queryTaskID!) - if result.result != nil { - let chartDataSet = try ChartDataSet(fromQueryResultWrapper: result) - DispatchQueue.main.async { - self.queryResult = result - self.chartDataSet = chartDataSet - self.loadingState = .finished(Date()) - } - } - - } catch { - print(error.localizedDescription) - - DispatchQueue.main.async { - if let transferError = error as? TransferError { - switch transferError { - case .transferFailed, .decodeFailed: - self.loadingState = .error(transferError.localizedDescription, Date()) - case .serverError(let message): - if message == "Not Found" { - self.loadingState = .loading - } else { - self.loadingState = .error(transferError.localizedDescription, Date()) - } - } - } else if let chartDataSetError = error as? ChartDataSetError { - self.loadingState = .error(chartDataSetError.localizedDescription, Date()) - } else { - self.loadingState = .error(error.localizedDescription, Date()) - } - } - } - } - - /// Asks for the status every 0.5 seconds if current status is running and loads the result if status is successful - @MainActor - func checkIfStillRunning() async { - switch loadingState { - case .idle, .loading, .finished: - break - case .error: - return - } - - if queryTaskStatus == .running { - loadingState = .loading - - do { - let queryTaskID = taskID["queryTaskID"] - let taskStatus = try await queryService.getTaskStatus(forTaskID: queryTaskID!) - - switch taskStatus { - case .successful: - DispatchQueue.main.async { - self.queryTaskStatus = taskStatus - } - let queryTaskID = taskID["queryTaskID"] - let result = try await queryService.getTaskResult(forTaskID: queryTaskID!) - let chartDataSet = try ChartDataSet(fromQueryResultWrapper: result) - DispatchQueue.main.async { - self.queryResult = result - self.chartDataSet = chartDataSet - self.loadingState = .finished(Date()) - } - case .error: - DispatchQueue.main.async { - self.loadingState = .error("string", Date()) - self.queryTaskStatus = taskStatus - } - - case .running: - DispatchQueue.main.async { - self.loadingState = .finished(Date()) - self.queryTaskStatus = taskStatus - } - } - } catch { - print(error.localizedDescription) - DispatchQueue.main.async { - if let transferError = error as? TransferError { - switch transferError { - case .transferFailed, .decodeFailed: - self.loadingState = .error(transferError.localizedDescription, Date()) - case .serverError(let message): - if message == "Not Found" { - self.loadingState = .loading - } else { - self.loadingState = .error(transferError.localizedDescription, Date()) - } - } - } else if let chartDataSetError = error as? ChartDataSetError { - self.loadingState = .error(chartDataSetError.localizedDescription, Date()) - } else { - self.loadingState = .error(error.localizedDescription, Date()) - } - } - } - } - } - - /// Ask for the status every 10 seconds - func checkStatus() async { - switch loadingState { - case .idle, .loading, .finished: - break - case .error: - return - } - - if queryTaskStatus == .successful { - loadingState = .loading - do { - let queryTaskID = taskID["queryTaskID"] - let taskStatus = try await queryService.getTaskStatus(forTaskID: queryTaskID!) - switch taskStatus { - case .successful: - DispatchQueue.main.async { - self.loadingState = .finished(Date()) - self.queryTaskStatus = taskStatus - } - case .error: - DispatchQueue.main.async { - self.loadingState = .error("string", Date()) - self.queryTaskStatus = taskStatus - } - - case .running: - DispatchQueue.main.async { - self.loadingState = .finished(Date()) - self.queryTaskStatus = taskStatus - } - } - } catch { - print(error.localizedDescription) - - DispatchQueue.main.async { - if let transferError = error as? TransferError { - switch transferError { - case .transferFailed, .decodeFailed: - self.loadingState = .error(transferError.localizedDescription, Date()) - case .serverError(let message): - if message == "Not Found" { - self.loadingState = .loading - } else { - self.loadingState = .error(transferError.localizedDescription, Date()) - } - } - } else if let chartDataSetError = error as? ChartDataSetError { - self.loadingState = .error(chartDataSetError.localizedDescription, Date()) - } else { - self.loadingState = .error(error.localizedDescription, Date()) - } - } - } - } - } -} - -struct QueryView: View { - @StateObject var viewModel: QueryViewModel - - var body: some View { - VStack { -// Text("asdf") -// viewModel.chartDataSet.map { -// LineChart(chartDataSet: $0, isSelected: false) -// } - if let chartDataSet = viewModel.chartDataSet { - switch viewModel.displayMode { - case .raw: - RawChartView(chartDataSet: chartDataSet, isSelected: viewModel.isSelected) - case .pieChart: - DonutChartView(chartDataset: chartDataSet, isSelected: viewModel.isSelected) - .padding(.bottom) - .padding(.horizontal) - case .lineChart: - LineChart(chartDataSet: chartDataSet, isSelected: viewModel.isSelected) - case .barChart: - BarChartView(chartDataSet: chartDataSet, isSelected: viewModel.isSelected) - default: - Text("\(viewModel.displayMode.rawValue.capitalized) is not supported in this version.") - .font(.footnote) - .foregroundColor(.grayColor) - .padding(.vertical) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) - } - } else { - SondrineLoadingStateIndicator(loadingState: viewModel.loadingState) - .onTapGesture { - Task { - await viewModel.retrieveResults() - } - } - } - } - .task { - await viewModel.retrieveResults() - } - .onReceive(viewModel.runningTimer) { _ in - Task { - await viewModel.checkIfStillRunning() - } - } - .onReceive(viewModel.successTimer) { _ in - Task { - await viewModel.checkStatus() - } - } - } -} - -struct QueryViewV2: View { - @StateObject var viewModel: QueryViewModel - - //let displayMode: String - - var body: some View { - VStack { - if let queryResult = viewModel.queryResult?.result, let chartDataSet = viewModel.chartDataSet{ - switch viewModel.displayMode { - case .raw: - RawChartView(chartDataSet: chartDataSet, isSelected: viewModel.isSelected) - case .pieChart: - DonutChartView(chartDataset: chartDataSet, isSelected: viewModel.isSelected) - .padding(.bottom) - .padding(.horizontal) - case .lineChart: - ClusterLineChart(query: viewModel.customQuery, result: queryResult) - case .barChart: - ClusterBarChart(query: viewModel.customQuery, result: queryResult) - default: - Text("\(viewModel.displayMode.rawValue.capitalized) is not supported in this version.") - .font(.footnote) - .foregroundColor(.grayColor) - .padding(.vertical) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) - } - } else { - SondrineLoadingStateIndicator(loadingState: viewModel.loadingState) - .onTapGesture { - Task { - await viewModel.retrieveResults() - } - } - } - - /*if let chartDataSet = viewModel.chartDataSet { - switch viewModel.displayMode { - case .raw: - RawChartView(chartDataSet: chartDataSet, isSelected: viewModel.isSelected) - case .pieChart: - DonutChartView(chartDataset: chartDataSet, isSelected: viewModel.isSelected) - .padding(.bottom) - .padding(.horizontal) - case .lineChart: - LineChart(chartDataSet: chartDataSet, isSelected: viewModel.isSelected) - case .barChart: - BarChartView(chartDataSet: chartDataSet, isSelected: viewModel.isSelected) - default: - Text("\(viewModel.displayMode.rawValue.capitalized) is not supported in this version.") - .font(.footnote) - .foregroundColor(.grayColor) - .padding(.vertical) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) - } - } else { - SondrineLoadingStateIndicator(loadingState: viewModel.loadingState) - .onTapGesture { - Task { - await viewModel.retrieveResults() - } - } - }*/ - } - .task { - await viewModel.retrieveResults() - } - .onReceive(viewModel.runningTimer) { _ in - Task { - await viewModel.checkIfStillRunning() - } - } - .onReceive(viewModel.successTimer) { _ in - Task { - await viewModel.checkStatus() - } - } - } -} - -// struct QueryView_Previews: PreviewProvider { -// static var previews: some View { -// QueryView() -// } -// } diff --git a/Telemetry Viewer.xcodeproj/project.pbxproj b/Telemetry Viewer.xcodeproj/project.pbxproj index adb875c..31fa610 100644 --- a/Telemetry Viewer.xcodeproj/project.pbxproj +++ b/Telemetry Viewer.xcodeproj/project.pbxproj @@ -233,8 +233,6 @@ C5551E5327C3CBEB005847B6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5551E5127C3CBEB005847B6 /* Assets.xcassets */; }; C5551E5427C3CBEB005847B6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5551E5127C3CBEB005847B6 /* Assets.xcassets */; }; C5551E5527C3CBEB005847B6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5551E5127C3CBEB005847B6 /* Assets.xcassets */; }; - C56B91F927C536D60085839A /* QueryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56B91F827C536D60085839A /* QueryView.swift */; }; - C56B91FA27C536D60085839A /* QueryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56B91F827C536D60085839A /* QueryView.swift */; }; C581F4E0271B22FD0031E99C /* Color+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B00509027035C85009C609C /* Color+Hex.swift */; }; C581F4E5271B29470031E99C /* InsightService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B1D46A026CC52DD008814A9 /* InsightService.swift */; }; C581F4E6271B29780031E99C /* ErrorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B1D469726CC4C5D008814A9 /* ErrorService.swift */; }; @@ -537,7 +535,6 @@ C5551E5727C3CC78005847B6 /* ThreeCirclesInATrenchcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeCirclesInATrenchcode.swift; sourceTree = ""; }; C5551E5827C3CC78005847B6 /* SondrineAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SondrineAnimation.swift; sourceTree = ""; }; C5551E5927C3CC78005847B6 /* AnimatedCircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedCircle.swift; sourceTree = ""; }; - C56B91F827C536D60085839A /* QueryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryView.swift; sourceTree = ""; }; C598487627CFBB7A00026772 /* QueryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryService.swift; sourceTree = ""; }; C5A8D863270C5D7A0032560A /* TelemetryDeckWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TelemetryDeckWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; C5A8D867270C5D7B0032560A /* TelemetryDeckWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryDeckWidget.swift; sourceTree = ""; }; @@ -1026,7 +1023,6 @@ 2B21FCD626FDC38500A8A55B /* NewInsightMenu.swift */, 2B21FCDE26FDCAE200A8A55B /* InsightsList.swift */, 6351A788277C9ED8003AF559 /* InsightDisplayMode+Extensions.swift */, - C56B91F827C536D60085839A /* QueryView.swift */, ); path = "Insight and Groups"; sourceTree = ""; @@ -1752,7 +1748,6 @@ 80AD3EA52BFF33FF00BBD7EB /* LineChartTimeSeries.swift in Sources */, 8083DD6D2C05EA0100596926 /* Extensions.swift in Sources */, 2B1D469E26CC51A8008814A9 /* GroupService.swift in Sources */, - C56B91F927C536D60085839A /* QueryView.swift in Sources */, 2B1D468926CC4423008814A9 /* OrgService.swift in Sources */, 2B21FCDF26FDCAE200A8A55B /* InsightsList.swift in Sources */, 2BBFCA11267D05E40013DC74 /* DetailSidebar.swift in Sources */, @@ -1839,7 +1834,6 @@ C5F4D32B28ACF48000EBB667 /* ChartHoverLabel.swift in Sources */, C51CB73627565EB5005A3FB9 /* TelemetryDeckWidget.intentdefinition in Sources */, 2BC522EB2625FCEF00E643AC /* HelpAndFeedbackView.swift in Sources */, - C56B91FA27C536D60085839A /* QueryView.swift in Sources */, DC5F3A8A25A364C00057AA59 /* EmptyInsightGroupView.swift in Sources */, 2B46280C2728699E00515530 /* StatusMessageContainer.swift in Sources */, 2BBFCA1E267D05E40013DC74 /* URL+Open.swift in Sources */, @@ -2539,8 +2533,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/AppTelemetry/SwiftClient"; requirement = { - branch = feature/metricKit; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Telemetry Viewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Telemetry Viewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8ff7842..04cd91a 100644 --- a/Telemetry Viewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Telemetry Viewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "c77feac7351e9360f94da77ea6664ee97c0a4c27540c7ad522fe8a1013e98df7", + "originHash" : "f8e3bc17593c91188fe60d4ec5bfc7210d4af2746c843213d20918d38b8a21ca", "pins" : [ { "identity" : "collectionconcurrencykit", @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/AppTelemetry/SwiftClient", "state" : { - "branch" : "feature/metricKit", - "revision" : "d6f99f022477f544c363a4bd7a097970a3bdbd7f" + "revision" : "0c5dcdd868dc92a4c528c0e7a42d33f8c5b32506", + "version" : "2.0.0" } }, { @@ -96,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/markiv/SwiftUI-Shimmer", "state" : { - "revision" : "5659a623567cefe258d1e3e67cb65585fbb6ecb6", - "version" : "1.4.2" + "revision" : "e3aa4226b0fafe345ca1c920f516b6a2f3e0aacc", + "version" : "1.5.0" } }, {