diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 7e34acabe0..a92f2bb363 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -260,6 +260,8 @@ 6172472725D673D7007085B3 /* CrashContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6172472625D673D7007085B3 /* CrashContextTests.swift */; }; 617247AF25DA9BEA007085B3 /* CrashReportingObjcHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 617247AE25DA9BEA007085B3 /* CrashReportingObjcHelpers.m */; }; 617247B825DAB0E2007085B3 /* DDCrashReportBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617247B725DAB0E2007085B3 /* DDCrashReportBuilder.swift */; }; + 61776CED273BEA5500F93802 /* DebugRUMSessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61776CEC273BEA5500F93802 /* DebugRUMSessionViewController.swift */; }; + 61776D4E273E6D9F00F93802 /* SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61776D4D273E6D9F00F93802 /* SwiftUI.swift */; }; 61786F7724FCDE05009E6BAB /* RUMDebuggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61786F7624FCDE04009E6BAB /* RUMDebuggingTests.swift */; }; 6179FFD3254ADB1700556A0B /* ObjcAppLaunchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 6179FFD2254ADB1100556A0B /* ObjcAppLaunchHandler.m */; }; 6179FFDE254ADBEF00556A0B /* ObjcAppLaunchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 6179FFD1254ADB1100556A0B /* ObjcAppLaunchHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -914,6 +916,8 @@ 617247AD25DA9BEA007085B3 /* CrashReportingObjcHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CrashReportingObjcHelpers.h; sourceTree = ""; }; 617247AE25DA9BEA007085B3 /* CrashReportingObjcHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CrashReportingObjcHelpers.m; sourceTree = ""; }; 617247B725DAB0E2007085B3 /* DDCrashReportBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDCrashReportBuilder.swift; sourceTree = ""; }; + 61776CEC273BEA5500F93802 /* DebugRUMSessionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugRUMSessionViewController.swift; sourceTree = ""; }; + 61776D4D273E6D9F00F93802 /* SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI.swift; sourceTree = ""; }; 61786F7624FCDE04009E6BAB /* RUMDebuggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMDebuggingTests.swift; sourceTree = ""; }; 6179FFD1254ADB1100556A0B /* ObjcAppLaunchHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjcAppLaunchHandler.h; sourceTree = ""; }; 6179FFD2254ADB1100556A0B /* ObjcAppLaunchHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjcAppLaunchHandler.m; sourceTree = ""; }; @@ -2235,6 +2239,8 @@ 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */, 61F74AF326F20E4600E5F5ED /* DebugCrashReportingWithRUMViewController.swift */, 618236882710560900125326 /* DebugWebviewViewController.swift */, + 61776CEC273BEA5500F93802 /* DebugRUMSessionViewController.swift */, + 61776D4C273E6D8100F93802 /* Helpers */, ); path = Debugging; sourceTree = ""; @@ -2425,6 +2431,14 @@ path = ../Tests/DatadogCrashReportingTests; sourceTree = ""; }; + 61776D4C273E6D8100F93802 /* Helpers */ = { + isa = PBXGroup; + children = ( + 61776D4D273E6D9F00F93802 /* SwiftUI.swift */, + ); + path = Helpers; + sourceTree = ""; + }; 61786F7524FCDDE2009E6BAB /* Debugging */ = { isa = PBXGroup; children = ( @@ -4256,7 +4270,9 @@ 6193DCE1251B692C009B8011 /* RUMTASTableViewController.swift in Sources */, 6111544825C9A88B007C84C9 /* PersistenceHelper.swift in Sources */, 61D50C462580EF19006038A3 /* TracingScenarios.swift in Sources */, + 61776CED273BEA5500F93802 /* DebugRUMSessionViewController.swift in Sources */, 618DCFE324C766FB00589570 /* SendRUMFixture3ViewController.swift in Sources */, + 61776D4E273E6D9F00F93802 /* SwiftUI.swift in Sources */, 61441C962461A649003D8BB8 /* UIButton+Disabling.swift in Sources */, 611EA12D2580F42600BC0E56 /* TrackingConsentScenarios.swift in Sources */, 614CADD72510BAC000B93D2D /* Environment.swift in Sources */, diff --git a/Datadog/Example/Base.lproj/Main.storyboard b/Datadog/Example/Base.lproj/Main.storyboard index 69ba745349..3c78110e7e 100644 --- a/Datadog/Example/Base.lproj/Main.storyboard +++ b/Datadog/Example/Base.lproj/Main.storyboard @@ -18,7 +18,7 @@ - + @@ -85,9 +85,29 @@ - + + + + + + + + + + + + + + + @@ -106,7 +126,7 @@ - + @@ -1374,6 +1394,34 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Datadog/Example/Debugging/DebugRUMSessionViewController.swift b/Datadog/Example/Debugging/DebugRUMSessionViewController.swift new file mode 100644 index 0000000000..dbc9b8b300 --- /dev/null +++ b/Datadog/Example/Debugging/DebugRUMSessionViewController.swift @@ -0,0 +1,275 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import SwiftUI +import Datadog + +@available(iOS 13, *) +internal class DebugRUMSessionViewController: UIHostingController { + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder, rootView: DebugRUMSessionView()) + } +} + +private enum SessionItemType { + case view + case resource + case action + case error +} + +@available(iOS 13.0, *) +private class DebugRUMSessionViewModel: ObservableObject { + struct SessionItem: Identifiable { + let label: String + let type: SessionItemType + var isPending: Bool + var stopAction: (() -> Void)? + + var id: UUID = UUID() + } + + @Published var sessionItems: [SessionItem] = [] + + @Published var viewKey: String = "" + @Published var actionName: String = "" + @Published var errorMessage: String = "" + @Published var resourceKey: String = "" + + func startView() { + guard !viewKey.isEmpty else { + return + } + + let viewKey = viewKey + sessionItems.append( + SessionItem( + label: viewKey, + type: .view, + isPending: true, + stopAction: { [weak self] in + self?.modifySessionItem(type: .view, label: viewKey) { mutableSessionItem in + mutableSessionItem.isPending = false + mutableSessionItem.stopAction = nil + Global.rum.stopView(key: viewKey) + } + } + ) + ) + + Global.rum.startView(key: viewKey) + self.viewKey = "" + } + + func addAction() { + guard !actionName.isEmpty else { + return + } + + sessionItems.append( + SessionItem(label: actionName, type: .action, isPending: false, stopAction: nil) + ) + + Global.rum.addUserAction(type: .custom, name: actionName) + self.actionName = "" + } + + func addError() { + guard !errorMessage.isEmpty else { + return + } + + sessionItems.append( + SessionItem(label: errorMessage, type: .error, isPending: false, stopAction: nil) + ) + + Global.rum.addError(message: errorMessage) + self.errorMessage = "" + } + + func startResource() { + guard !resourceKey.isEmpty else { + return + } + + let resourceKey = self.resourceKey + sessionItems.append( + SessionItem( + label: resourceKey, + type: .resource, + isPending: true, + stopAction: { [weak self] in + self?.modifySessionItem(type: .resource, label: resourceKey) { mutableSessionItem in + mutableSessionItem.isPending = false + mutableSessionItem.stopAction = nil + Global.rum.stopResourceLoading(resourceKey: resourceKey, statusCode: nil, kind: .other) + } + } + ) + ) + + Global.rum.startResourceLoading(resourceKey: resourceKey, url: mockURL()) + self.resourceKey = "" + } + + // MARK: - Private + + private func modifySessionItem(type: SessionItemType, label: String, change: (inout SessionItem) -> Void) { + sessionItems = sessionItems.map { item in + var item = item + if item.type == type, item.label == label { + change(&item) + } + return item + } + } + + private func mockURL() -> URL { + return URL(string: "https://foo.com/\(UUID().uuidString)")! + } +} + +@available(iOS 13.0, *) +internal struct DebugRUMSessionView: View { + @ObservedObject private var viewModel = DebugRUMSessionViewModel() + + var body: some View { + VStack() { + HStack { + FormItemView( + title: "RUM View", placeholder: "view key", accent: .rumViewColor, value: $viewModel.viewKey + ) + Button("START") { viewModel.startView() } + } + HStack { + FormItemView( + title: "RUM Action", placeholder: "name", accent: .rumActionColor, value: $viewModel.actionName + ) + Button("ADD") { viewModel.addAction() } + } + HStack { + FormItemView( + title: "RUM Error", placeholder: "message", accent: .rumErrorColor, value: $viewModel.errorMessage + ) + Button("ADD") { viewModel.addError() } + } + HStack { + FormItemView( + title: "RUM Resource", placeholder: "key", accent: .rumResourceColor, value: $viewModel.resourceKey + ) + Button("START") { viewModel.startResource() } + } + Divider() + Text("RUM Session:") + .bold() + .font(.footnote) + List(viewModel.sessionItems) { sessionItem in + SessionItemView(item: sessionItem) + .listRowInsets(EdgeInsets()) + .padding(4) + } + .listStyle(.plain) + } + .buttonStyle(DatadogButtonStyle()) + .padding() + } +} + +@available(iOS 13.0, *) +private struct FormItemView: View { + let title: String + let placeholder: String + let accent: Color + + @Binding var value: String + + var body: some View { + HStack { + Text(title) + .bold() + .font(.system(size: 10)) + .padding(8) + .background(accent) + .foregroundColor(Color.white) + .cornerRadius(8) + TextField(placeholder, text: $value) + .font(.system(size: 12)) + .padding(8) + .background(Color(UIColor.secondarySystemFill)) + .cornerRadius(8) + } + .padding(8) + .background(Color(UIColor.systemFill)) + .foregroundColor(Color.secondary) + .cornerRadius(8) + } +} + +@available(iOS 13.0, *) +private struct SessionItemView: View { + let item: DebugRUMSessionViewModel.SessionItem + + var body: some View { + HStack() { + HStack() { + Text(label(for: item.type)) + .bold() + .font(.system(size: 10)) + .padding(8) + .background(color(for: item.type)) + .foregroundColor(Color.white) + .cornerRadius(8) + Text(item.label) + .bold() + .font(.system(size: 14)) + Spacer() + } + .padding(8) + .frame(maxWidth: .infinity) + .background(Color(UIColor.systemFill)) + .foregroundColor(Color.secondary) + .cornerRadius(8) + + if item.isPending { + Button("STOP") { item.stopAction?() } + } + } + } + + private func color(for sessionItemType: SessionItemType) -> Color { + switch sessionItemType { + case .view: return .rumViewColor + case .resource: return .rumResourceColor + case .action: return .rumActionColor + case .error: return .rumErrorColor + } + } + + private func label(for sessionItemType: SessionItemType) -> String { + switch sessionItemType { + case .view: return "RUM View" + case .resource: return "RUM Resource" + case .action: return "RUM Action" + case .error: return "RUM Error" + } + } +} + +// MARK - Preview + +@available(iOS 13.0, *) +struct DebugRUMSessionViewController_Previews: PreviewProvider { + static var previews: some View { + Group { + DebugRUMSessionView() + .previewLayout(.fixed(width: 400, height: 500)) + .preferredColorScheme(.light) + DebugRUMSessionView() + .previewLayout(.fixed(width: 400, height: 500)) + .preferredColorScheme(.dark) + } + } +} diff --git a/Datadog/Example/Debugging/Helpers/SwiftUI.swift b/Datadog/Example/Debugging/Helpers/SwiftUI.swift new file mode 100644 index 0000000000..56879ec2f9 --- /dev/null +++ b/Datadog/Example/Debugging/Helpers/SwiftUI.swift @@ -0,0 +1,43 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import SwiftUI + +@available(iOS 13.0, *) +extension Color { + /// Datadog purple. + static var datadogPurple: Color { + return Color(UIColor(red: 99/256, green: 44/256, blue: 166/256, alpha: 1)) + } + + static var rumViewColor: Color { + return Color(UIColor(red: 0/256, green: 107/256, blue: 194/256, alpha: 1)) + } + + static var rumResourceColor: Color { + return Color(UIColor(red: 113/256, green: 184/256, blue: 231/256, alpha: 1)) + } + + static var rumActionColor: Color { + return Color(UIColor(red: 150/256, green: 95/256, blue: 204/256, alpha: 1)) + } + + static var rumErrorColor: Color { + return Color(UIColor(red: 235/256, green: 54/256, blue: 7/256, alpha: 1)) + } +} + +@available(iOS 13.0, *) +internal struct DatadogButtonStyle: ButtonStyle { + func makeBody(configuration: DatadogButtonStyle.Configuration) -> some View { + return configuration.label + .font(.system(size: 14, weight: .medium)) + .padding(10) + .background(Color.datadogPurple) + .foregroundColor(.white) + .cornerRadius(8) + } +}