From 567afbaf498acebc34ac103d443c0a3e71f7b8dc Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 6 Feb 2022 19:21:59 -0500 Subject: [PATCH 1/7] feat: allow any dimension value for ParseAnalytics --- CHANGELOG.md | 8 +- ParseSwift.xcodeproj/project.pbxproj | 42 ++-- Sources/ParseSwift/Coding/ParseEncoder.swift | 2 +- Sources/ParseSwift/Objects/ParseRole.swift | 4 +- Sources/ParseSwift/ParseConstants.swift | 2 +- Sources/ParseSwift/Types/ParseACL.swift | 5 +- .../Types/ParseAnalytics+async.swift | 22 +- Sources/ParseSwift/Types/ParseAnalytics.swift | 194 ++++++++++-------- ...s.swift => ParseAnalyticsAsyncTests.swift} | 8 +- ...swift => ParseAnalyticsCombineTests.swift} | 4 +- .../ParseSwiftTests/ParseAnalyticsTests.swift | 130 ++++-------- 11 files changed, 202 insertions(+), 219 deletions(-) rename Tests/ParseSwiftTests/{ParseAnanlyticsAsyncTests.swift => ParseAnalyticsAsyncTests.swift} (96%) rename Tests/ParseSwiftTests/{ParseAnanlyticsCombineTests.swift => ParseAnalyticsCombineTests.swift} (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index dae8d7d32..b342c44b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ ### main -[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.1...main) +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.1.0...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +### 4.1.0 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.1...4.1.0) + +__Improvements__ +- Let the OS and developer decide if app tracking authorization is required when using ParseAnalytics. ParseAnalytics can now take any Codable value in its' dimensions instead of just strings. Added new methods, "setDimensions" and "updateDimensions" on ParseAnalytics. Also adds a new property, "date" to ParseAnalytics. The "at" property will be deprecated in ParseSwift 5.0.0, so developers should switch to "date". ParseAnalytics can now be properly decoded after encoding with a JSONEncoder. This is useful if ParseAnalytics need to be stored locally and sent to the server later ([#341](https://github.com/parse-community/Parse-Swift/pull/341)), thanks to @cbaker6. + ### 4.0.1 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.0...4.0.1) diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 431c68a43..baec42312 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -327,6 +327,10 @@ 707A3C2125B14BD0000D215C /* ParseApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707A3C1F25B14BCF000D215C /* ParseApple.swift */; }; 707A3C2225B14BD0000D215C /* ParseApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707A3C1F25B14BCF000D215C /* ParseApple.swift */; }; 707A3C2325B14BD0000D215C /* ParseApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707A3C1F25B14BCF000D215C /* ParseApple.swift */; }; + 7085DD9426CBF3A70033B977 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7085DD9326CBF3A70033B977 /* Documentation.docc */; }; + 7085DD9526CBF3A70033B977 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7085DD9326CBF3A70033B977 /* Documentation.docc */; }; + 7085DD9626CBF3A70033B977 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7085DD9326CBF3A70033B977 /* Documentation.docc */; }; + 7085DD9726CBF3A70033B977 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7085DD9326CBF3A70033B977 /* Documentation.docc */; }; 7085DDA326CC8A470033B977 /* ParseHealth+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDA226CC8A470033B977 /* ParseHealth+combine.swift */; }; 7085DDA426CC8A470033B977 /* ParseHealth+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDA226CC8A470033B977 /* ParseHealth+combine.swift */; }; 7085DDA526CC8A470033B977 /* ParseHealth+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDA226CC8A470033B977 /* ParseHealth+combine.swift */; }; @@ -334,10 +338,6 @@ 7085DDB326D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */; }; 7085DDB426D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */; }; 7085DDB526D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */; }; - 7085DD9426CBF3A70033B977 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7085DD9326CBF3A70033B977 /* Documentation.docc */; }; - 7085DD9526CBF3A70033B977 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7085DD9326CBF3A70033B977 /* Documentation.docc */; }; - 7085DD9626CBF3A70033B977 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7085DD9326CBF3A70033B977 /* Documentation.docc */; }; - 7085DD9726CBF3A70033B977 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7085DD9326CBF3A70033B977 /* Documentation.docc */; }; 708D035225215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; 708D035325215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; 708D035425215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; @@ -600,9 +600,9 @@ 917BA4262703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4252703DB4600F8D747 /* ParseQueryAsyncTests.swift */; }; 917BA4272703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4252703DB4600F8D747 /* ParseQueryAsyncTests.swift */; }; 917BA4282703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4252703DB4600F8D747 /* ParseQueryAsyncTests.swift */; }; - 917BA42A2703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4292703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift */; }; - 917BA42B2703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4292703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift */; }; - 917BA42C2703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4292703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift */; }; + 917BA42A2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4292703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift */; }; + 917BA42B2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4292703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift */; }; + 917BA42C2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA4292703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift */; }; 917BA42E2703E20E00F8D747 /* ParseCloudAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA42D2703E20E00F8D747 /* ParseCloudAsyncTests.swift */; }; 917BA42F2703E20E00F8D747 /* ParseCloudAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA42D2703E20E00F8D747 /* ParseCloudAsyncTests.swift */; }; 917BA4302703E20E00F8D747 /* ParseCloudAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917BA42D2703E20E00F8D747 /* ParseCloudAsyncTests.swift */; }; @@ -669,9 +669,9 @@ 91BB8FD42690D586005A6BA5 /* ParseQueryViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91BB8FD32690D586005A6BA5 /* ParseQueryViewModelTests.swift */; }; 91BB8FD52690D586005A6BA5 /* ParseQueryViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91BB8FD32690D586005A6BA5 /* ParseQueryViewModelTests.swift */; }; 91BB8FD62690D586005A6BA5 /* ParseQueryViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91BB8FD32690D586005A6BA5 /* ParseQueryViewModelTests.swift */; }; - 91CB9537265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB9536265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift */; }; - 91CB9538265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB9536265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift */; }; - 91CB9539265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB9536265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift */; }; + 91CB9537265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB9536265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift */; }; + 91CB9538265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB9536265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift */; }; + 91CB9539265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB9536265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift */; }; 91F346B9269B766C005727B6 /* CloudViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346B8269B766C005727B6 /* CloudViewModel.swift */; }; 91F346BA269B766D005727B6 /* CloudViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346B8269B766C005727B6 /* CloudViewModel.swift */; }; 91F346BB269B766D005727B6 /* CloudViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346B8269B766C005727B6 /* CloudViewModel.swift */; }; @@ -964,9 +964,9 @@ 707A3BF025B0A4F0000D215C /* ParseAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAuthentication.swift; sourceTree = ""; }; 707A3C1025B0A8E8000D215C /* ParseAnonymous.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnonymous.swift; sourceTree = ""; }; 707A3C1F25B14BCF000D215C /* ParseApple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseApple.swift; sourceTree = ""; }; + 7085DD9326CBF3A70033B977 /* Documentation.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Documentation.docc; sourceTree = ""; }; 7085DDA226CC8A470033B977 /* ParseHealth+combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseHealth+combine.swift"; sourceTree = ""; }; 7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAuthenticationCombineTests.swift; sourceTree = ""; }; - 7085DD9326CBF3A70033B977 /* Documentation.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Documentation.docc; sourceTree = ""; }; 708D035125215F9B00646C70 /* Deletable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deletable.swift; sourceTree = ""; }; 709B40C0268F999000ED2EAC /* IOS13Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOS13Tests.swift; sourceTree = ""; }; 709B98302556EC7400507778 /* ParseSwiftTeststvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParseSwiftTeststvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1053,7 +1053,7 @@ 91679D63268E596300F71809 /* ParseVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseVersion.swift; sourceTree = ""; }; 91679D68268F25EA00F71809 /* ParseVersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseVersionTests.swift; sourceTree = ""; }; 917BA4252703DB4600F8D747 /* ParseQueryAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseQueryAsyncTests.swift; sourceTree = ""; }; - 917BA4292703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnanlyticsAsyncTests.swift; sourceTree = ""; }; + 917BA4292703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnalyticsAsyncTests.swift; sourceTree = ""; }; 917BA42D2703E20E00F8D747 /* ParseCloudAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloudAsyncTests.swift; sourceTree = ""; }; 917BA4312703E36800F8D747 /* ParseConfigAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigAsyncTests.swift; sourceTree = ""; }; 917BA4352703E4CB00F8D747 /* ParseFileAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileAsyncTests.swift; sourceTree = ""; }; @@ -1075,7 +1075,7 @@ 91BB8FC92690AC99005A6BA5 /* QueryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryViewModel.swift; sourceTree = ""; }; 91BB8FCE2690BA70005A6BA5 /* QueryObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryObservable.swift; sourceTree = ""; }; 91BB8FD32690D586005A6BA5 /* ParseQueryViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseQueryViewModelTests.swift; sourceTree = ""; }; - 91CB9536265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnanlyticsCombineTests.swift; sourceTree = ""; }; + 91CB9536265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnalyticsCombineTests.swift; sourceTree = ""; }; 91F346B8269B766C005727B6 /* CloudViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudViewModel.swift; sourceTree = ""; }; 91F346BD269B77B5005727B6 /* CloudObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudObservable.swift; sourceTree = ""; }; 91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloudViewModelTests.swift; sourceTree = ""; }; @@ -1234,8 +1234,8 @@ 4AA8076E1F794C1C008CD551 /* KeychainStoreTests.swift */, 9194657724F16E330070296B /* ParseACLTests.swift */, 70170A4D2656EBA50070C905 /* ParseAnalyticsTests.swift */, - 917BA4292703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift */, - 91CB9536265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift */, + 917BA4292703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift */, + 91CB9536265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift */, 917BA4452703EEA700F8D747 /* ParseAnonymousAsyncTests.swift */, 7044C22C25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift */, 70A2D86A25B3ADB6001BEB7D /* ParseAnonymousTests.swift */, @@ -2362,7 +2362,7 @@ 917BA43A2703E6D800F8D747 /* ParseHealthAsyncTests.swift in Sources */, 917BA4462703EEA700F8D747 /* ParseAnonymousAsyncTests.swift in Sources */, 70F79A672639DE9700731C46 /* ParseHealthCombineTests.swift in Sources */, - 91CB9537265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift in Sources */, + 91CB9537265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift in Sources */, 703B092326BDFAB2005A112F /* ParseUserAsyncTests.swift in Sources */, 70C5504625B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */, 70110D5C2506ED0E0091CC1D /* ParseInstallationTests.swift in Sources */, @@ -2373,7 +2373,7 @@ 917BA4262703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */, 91B40651267A66ED00B129CD /* ParseErrorTests.swift in Sources */, 705727B12593FF8800F0ADD5 /* ParseFileTests.swift in Sources */, - 917BA42A2703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift in Sources */, + 917BA42A2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */, 70BC0B33251903D1001556DB /* ParseGeoPointTests.swift in Sources */, 7003957625A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, 705A99F9259807F900B3547F /* ParseFileManagerTests.swift in Sources */, @@ -2605,7 +2605,7 @@ 917BA43C2703E6D800F8D747 /* ParseHealthAsyncTests.swift in Sources */, 917BA4482703EEA700F8D747 /* ParseAnonymousAsyncTests.swift in Sources */, 70F79A692639DE9700731C46 /* ParseHealthCombineTests.swift in Sources */, - 91CB9539265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift in Sources */, + 91CB9539265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift in Sources */, 703B092526BDFAB2005A112F /* ParseUserAsyncTests.swift in Sources */, 70C5504825B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */, 709B98572556ECAA00507778 /* ParseACLTests.swift in Sources */, @@ -2616,7 +2616,7 @@ 917BA4282703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */, 91B40653267A66ED00B129CD /* ParseErrorTests.swift in Sources */, 705727BC2593FF8C00F0ADD5 /* ParseFileTests.swift in Sources */, - 917BA42C2703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift in Sources */, + 917BA42C2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */, 709B984F2556ECAA00507778 /* AnyCodableTests.swift in Sources */, 7003957825A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, 705A99FB259807F900B3547F /* ParseFileManagerTests.swift in Sources */, @@ -2699,7 +2699,7 @@ 917BA43B2703E6D800F8D747 /* ParseHealthAsyncTests.swift in Sources */, 917BA4472703EEA700F8D747 /* ParseAnonymousAsyncTests.swift in Sources */, 70F79A682639DE9700731C46 /* ParseHealthCombineTests.swift in Sources */, - 91CB9538265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift in Sources */, + 91CB9538265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift in Sources */, 703B092426BDFAB2005A112F /* ParseUserAsyncTests.swift in Sources */, 70C5504725B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */, 70F2E2BC254F283000B2EA5C /* ParseObjectTests.swift in Sources */, @@ -2710,7 +2710,7 @@ 917BA4272703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */, 91B40652267A66ED00B129CD /* ParseErrorTests.swift in Sources */, 705727BB2593FF8B00F0ADD5 /* ParseFileTests.swift in Sources */, - 917BA42B2703E03F00F8D747 /* ParseAnanlyticsAsyncTests.swift in Sources */, + 917BA42B2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */, 70F2E2BD254F283000B2EA5C /* AnyDecodableTests.swift in Sources */, 7003957725A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, 705A99FA259807F900B3547F /* ParseFileManagerTests.swift in Sources */, diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index 6e682af88..c42fcb7ce 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -191,7 +191,7 @@ public struct ParseEncoder { } // MARK: _ParseEncoder -private class _ParseEncoder: JSONEncoder, Encoder { +internal class _ParseEncoder: JSONEncoder, Encoder { var codingPath: [CodingKey] let dictionary: NSMutableDictionary let skippedKeys: Set diff --git a/Sources/ParseSwift/Objects/ParseRole.swift b/Sources/ParseSwift/Objects/ParseRole.swift index f487e95be..50e36d883 100644 --- a/Sources/ParseSwift/Objects/ParseRole.swift +++ b/Sources/ParseSwift/Objects/ParseRole.swift @@ -98,11 +98,11 @@ public extension ParseRole { } static func == (lhs: Self, rhs: Self) -> Bool { - lhs.name == rhs.name && lhs.className == rhs.className + lhs.debugDescription == rhs.debugDescription } func hash(into hasher: inout Hasher) { - hasher.combine("\(self.className)_\(String(describing: self.name))") + hasher.combine(self.debugDescription) } } diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index 722acb452..5ae0ce9c8 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -10,7 +10,7 @@ import Foundation enum ParseConstants { static let sdk = "swift" - static let version = "4.0.1" + static let version = "4.1.0" static let fileManagementDirectory = "parse/" static let fileManagementPrivateDocumentsDirectory = "Private Documents/" static let fileManagementLibraryDirectory = "Library/" diff --git a/Sources/ParseSwift/Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift index 936cfad0f..b3e68e705 100644 --- a/Sources/ParseSwift/Types/ParseACL.swift +++ b/Sources/ParseSwift/Types/ParseACL.swift @@ -32,7 +32,10 @@ public struct ParseACL: ParseType, case write public init(from decoder: Decoder) throws { - self = Access(rawValue: try decoder.singleValueContainer().decode(String.self))! + guard let decoded = Access(rawValue: try decoder.singleValueContainer().decode(String.self)) else { + throw ParseError(code: .unknownError, message: "Not able to decode ACL") + } + self = decoded } public func encode(to encoder: Encoder) throws { diff --git a/Sources/ParseSwift/Types/ParseAnalytics+async.swift b/Sources/ParseSwift/Types/ParseAnalytics+async.swift index 31e8d94ae..7056a979b 100644 --- a/Sources/ParseSwift/Types/ParseAnalytics+async.swift +++ b/Sources/ParseSwift/Types/ParseAnalytics+async.swift @@ -96,19 +96,19 @@ public extension ParseAnalytics { - parameter at: Explicitly set the time associated with a given event. If not provided the server time will be used. - parameter options: A set of header options sent to the server. Defaults to an empty set. - - warning: This method makes a copy of the current `ParseAnalytics` and then mutates - it. You will not have access to the mutated analytic after calling this method. - throws: An error of type `ParseError`. */ - func track(dimensions: [String: String]?, - at date: Date? = nil, - options: API.Options = []) async throws { - let _: Void = try await withCheckedThrowingContinuation { continuation in - var analytic = self - analytic.track(dimensions: dimensions, - at: date, - options: options, - completion: continuation.resume) + mutating func track(dimensions: [String: String]?, + at date: Date? = nil, + options: API.Options = []) async throws { + let result = try await withCheckedThrowingContinuation { continuation in + self.track(dimensions: dimensions, + at: date, + options: options, + completion: continuation.resume) + } + if case let .failure(error) = result { + throw error } } } diff --git a/Sources/ParseSwift/Types/ParseAnalytics.swift b/Sources/ParseSwift/Types/ParseAnalytics.swift index 717b79928..b6f1bce91 100644 --- a/Sources/ParseSwift/Types/ParseAnalytics.swift +++ b/Sources/ParseSwift/Types/ParseAnalytics.swift @@ -11,32 +11,47 @@ import Foundation import UIKit #endif -#if canImport(AppTrackingTransparency) -import AppTrackingTransparency -#endif - /** - `ParseAnalytics` provides an interface to Parse's logging and analytics - backend. - - - warning: For iOS 14.0, macOS 11.0, macCatalyst 14.0, tvOS 14.0, you - will need to request tracking authorization for ParseAnalytics to work. - See Apple's [documentation](https://developer.apple.com/documentation/apptrackingtransparency) for more for details. + `ParseAnalytics` provides an interface to Parse's logging and analytics backend. */ public struct ParseAnalytics: ParseType, Hashable { /// The name of the custom event to report to Parse as having happened. - public let name: String + public var name: String /// Explicitly set the time associated with a given event. If not provided the server /// time will be used. - public var at: Date? // swiftlint:disable:this identifier_name + /// - warning: This will be deprecated in ParseSwift 5.5 in favor of `date`. + public var at: Date? { // swiftlint:disable:this identifier_name + get { + date + } + set { + date = newValue + } + } + + /// Explicitly set the time associated with a given event. If not provided the server + /// time will be used. + public var date: Date? /// The dictionary of information by which to segment this event. - public var dimensions: [String: String]? + /// - warning: It is not recommended to set this directly. + public var dimensions: [String: String]? { + get { + convertToString(dimensionsCodable) + } + set { + dimensionsCodable = convertToAnyCodable(newValue) + } + } + + var dimensionsCodable: [String: AnyCodable]? enum CodingKeys: String, CodingKey { - case at, dimensions // swiftlint:disable:this identifier_name + case date = "at" + case dimensions + case name } /** @@ -47,11 +62,82 @@ public struct ParseAnalytics: ParseType, Hashable { time will be used. Defaults to `nil`. */ public init (name: String, - dimensions: [String: String]? = nil, - at: Date? = nil) { // swiftlint:disable:this identifier_name + dimensions: [String: Codable]? = nil, + at date: Date? = nil) { self.name = name - self.dimensions = dimensions - self.at = at + self.dimensionsCodable = convertToAnyCodable(dimensions) + self.date = date + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(date, forKey: .date) + try container.encodeIfPresent(dimensionsCodable, forKey: .dimensions) + if !(encoder is _ParseEncoder) { + try container.encode(name, forKey: .name) + } + } + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.debugDescription == rhs.debugDescription + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(self.debugDescription) + } + + // MARK: Helpers + func convertToAnyCodable(_ dimensions: [String: Codable]?) -> [String: AnyCodable]? { + guard let dimensions = dimensions else { + return nil + } + var convertedDimensions = [String: AnyCodable]() + for (key, value) in dimensions { + convertedDimensions[key] = AnyCodable(value) + } + return convertedDimensions + } + + func convertToString(_ dimensions: [String: AnyCodable]?) -> [String: String]? { + guard let dimensions = dimensions else { + return nil + } + var convertedDimensions = [String: String]() + for (key, value) in dimensions { + convertedDimensions[key] = "\(value.value)" + } + return convertedDimensions + } + + // MARK: Intents + + /** + Set the dimensions. + + - parameter dimensions: The dictionary of information by which to segment this + event. + */ + public mutating func setDimensions(_ dimensions: [String: Codable]) { + dimensionsCodable = convertToAnyCodable(dimensions) + } + + /** + Update the dimensions with additional data or replace specific key value pairs. + + - parameter dimensions: The dictionary of information by which to segment this + event. + */ + public mutating func updateDimensions(_ dimensions: [String: Codable]) { + guard let convertedDimensions = convertToAnyCodable(dimensions) else { + return + } + if dimensionsCodable == nil { + dimensionsCodable = convertedDimensions + } else { + for (key, value) in convertedDimensions { + dimensionsCodable?[key] = value + } + } } #if os(iOS) @@ -79,22 +165,6 @@ public struct ParseAnalytics: ParseType, Hashable { completion: @escaping (Result) -> Void) { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) - #if canImport(AppTrackingTransparency) - if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { - if !ParseSwift.configuration.isTestingSDK { - let status = ATTrackingManager.trackingAuthorizationStatus - if status != .authorized { - callbackQueue.async { - let error = ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "App tracking not authorized. Please request permission from user.") - completion(.failure(error)) - } - return - } - } - } - #endif var userInfo: [String: String]? if let remoteOptions = launchOptions?[.remoteNotification] as? [String: String] { userInfo = remoteOptions @@ -118,7 +188,7 @@ public struct ParseAnalytics: ParseType, Hashable { Tracks *asynchronously* this application being launched with additional dimensions. - parameter dimensions: The dictionary of information by which to segment this - event and can be empty or `nil`. + event. Can be empty or `nil`. - parameter at: Explicitly set the time associated with a given event. If not provided the server time will be used. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -135,22 +205,6 @@ public struct ParseAnalytics: ParseType, Hashable { completion: @escaping (Result) -> Void) { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) - #if canImport(AppTrackingTransparency) - if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { - if !ParseSwift.configuration.isTestingSDK { - let status = ATTrackingManager.trackingAuthorizationStatus - if status != .authorized { - callbackQueue.async { - let error = ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "App tracking not authorized. Please request permission from user.") - completion(.failure(error)) - } - return - } - } - } - #endif let appOppened = ParseAnalytics(name: "AppOpened", dimensions: dimensions, at: date) @@ -180,22 +234,6 @@ public struct ParseAnalytics: ParseType, Hashable { completion: @escaping (Result) -> Void) { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) - #if canImport(AppTrackingTransparency) - if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { - if !ParseSwift.configuration.isTestingSDK { - let status = ATTrackingManager.trackingAuthorizationStatus - if status != .authorized { - callbackQueue.async { - let error = ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "App tracking not authorized. Please request permission from user.") - completion(.failure(error)) - } - return - } - } - } - #endif self.saveCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in switch result { @@ -211,7 +249,7 @@ public struct ParseAnalytics: ParseType, Hashable { Tracks *asynchronously* the occurrence of a custom event with additional dimensions. - parameter dimensions: The dictionary of information by which to segment this - event and can be empty or `nil`. + event. Can be empty or `nil`. - parameter at: Explicitly set the time associated with a given event. If not provided the server time will be used. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -228,24 +266,8 @@ public struct ParseAnalytics: ParseType, Hashable { completion: @escaping (Result) -> Void) { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) - #if canImport(AppTrackingTransparency) - if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { - if !ParseSwift.configuration.isTestingSDK { - let status = ATTrackingManager.trackingAuthorizationStatus - if status != .authorized { - callbackQueue.async { - let error = ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "App tracking not authorized. Please request permission from user.") - completion(.failure(error)) - } - return - } - } - } - #endif - self.dimensions = dimensions - self.at = date + self.dimensionsCodable = convertToAnyCodable(dimensions) + self.date = date self.saveCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in switch result { diff --git a/Tests/ParseSwiftTests/ParseAnanlyticsAsyncTests.swift b/Tests/ParseSwiftTests/ParseAnalyticsAsyncTests.swift similarity index 96% rename from Tests/ParseSwiftTests/ParseAnanlyticsAsyncTests.swift rename to Tests/ParseSwiftTests/ParseAnalyticsAsyncTests.swift index 0eb8786de..639a5f37d 100644 --- a/Tests/ParseSwiftTests/ParseAnanlyticsAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseAnalyticsAsyncTests.swift @@ -1,5 +1,5 @@ // -// ParseAnanlyticsAsyncTests.swift +// ParseAnalyticsAsyncTests.swift // ParseSwift // // Created by Corey Baker on 9/28/21. @@ -14,7 +14,7 @@ import FoundationNetworking import XCTest @testable import ParseSwift -class ParseAnanlyticsAsyncTests: XCTestCase { // swiftlint:disable:this type_body_length +class ParseAnalyticsAsyncTests: XCTestCase { // swiftlint:disable:this type_body_length override func setUpWithError() throws { try super.setUpWithError() guard let url = URL(string: "http://localhost:1337/1") else { @@ -192,7 +192,7 @@ class ParseAnanlyticsAsyncTests: XCTestCase { // swiftlint:disable:this type_bod MockURLProtocol.mockRequests { _ in return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let event = ParseAnalytics(name: "hello") + var event = ParseAnalytics(name: "hello") _ = try await event.track(dimensions: ["stop": "drop"]) } @@ -209,7 +209,7 @@ class ParseAnanlyticsAsyncTests: XCTestCase { // swiftlint:disable:this type_bod MockURLProtocol.mockRequests { _ in return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let event = ParseAnalytics(name: "hello") + var event = ParseAnalytics(name: "hello") do { _ = try await event.track(dimensions: ["stop": "drop"]) XCTFail("Should have thrown error") diff --git a/Tests/ParseSwiftTests/ParseAnanlyticsCombineTests.swift b/Tests/ParseSwiftTests/ParseAnalyticsCombineTests.swift similarity index 97% rename from Tests/ParseSwiftTests/ParseAnanlyticsCombineTests.swift rename to Tests/ParseSwiftTests/ParseAnalyticsCombineTests.swift index 1a745152d..31dfffabd 100644 --- a/Tests/ParseSwiftTests/ParseAnanlyticsCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseAnalyticsCombineTests.swift @@ -1,5 +1,5 @@ // -// ParseAnanlyticsCombineTests.swift +// ParseAnalyticsCombineTests.swift // ParseSwift // // Created by Corey Baker on 5/22/21. @@ -13,7 +13,7 @@ import XCTest import Combine @testable import ParseSwift -class ParseAnanlyticsCombineTests: XCTestCase { // swiftlint:disable:this type_body_length +class ParseAnalyticsCombineTests: XCTestCase { // swiftlint:disable:this type_body_length override func setUpWithError() throws { try super.setUpWithError() diff --git a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift index c8d18485b..e10f0b573 100644 --- a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift +++ b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift @@ -56,11 +56,50 @@ class ParseAnalyticsTests: XCTestCase { event2.at = nil //Clear date for comparison let decoded = event2.debugDescription - let expected = "{\"dimensions\":{\"stop\":\"drop\"}}" + let expected = "{\"dimensions\":{\"stop\":\"drop\"},\"name\":\"hello\"}" XCTAssertEqual(decoded, expected) let decoded2 = event2.description - let expected2 = "{\"dimensions\":{\"stop\":\"drop\"}}" + let expected2 = "{\"dimensions\":{\"stop\":\"drop\"},\"name\":\"hello\"}" XCTAssertEqual(decoded2, expected2) + let encoded3 = try ParseCoding.parseEncoder().encode(event2) + let decoded3 = String(data: encoded3, encoding: .utf8) + let expected3 = "{\"dimensions\":{\"stop\":\"drop\"}}" + XCTAssertEqual(decoded3, expected3) + } + + func testSetDimensions() throws { + let name = "hello" + let dimensions = ["stop": "drop"] + let dimensions2 = ["open": "up shop"] + var event = ParseAnalytics(name: name, dimensions: dimensions) + XCTAssertEqual(event.dimensions, dimensions) + event.setDimensions(dimensions2) + XCTAssertEqual(event.dimensions, dimensions2) + } + + func testUpdateDimensions() throws { + let name = "hello" + let dimensions = ["stop": "drop"] + let dimensions2 = ["open": "up shop"] + var dimensions3 = dimensions + for (key, value) in dimensions2 { + dimensions3[key] = value + } + var event = ParseAnalytics(name: name, dimensions: dimensions) + XCTAssertEqual(event.dimensions, dimensions) + event.updateDimensions(dimensions2) + XCTAssertNotEqual(event.dimensions, dimensions) + XCTAssertNotEqual(event.dimensions, dimensions2) + XCTAssertEqual(event.dimensions, dimensions3) + } + + func testUpdateDimensionsNonInitially() throws { + let name = "hello" + let dimensions = ["stop": "drop"] + var event = ParseAnalytics(name: name) + XCTAssertNil(event.dimensions) + event.updateDimensions(dimensions) + XCTAssertEqual(event.dimensions, dimensions) } #if os(iOS) @@ -115,28 +154,6 @@ class ParseAnalyticsTests: XCTestCase { } wait(for: [expectation], timeout: 10.0) } - - #if canImport(AppTrackingTransparency) - func testTrackAppOpenedUIKitNotAuthorized() { - if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { - ParseSwift.configuration.isTestingSDK = false // Allow authorization check - let expectation = XCTestExpectation(description: "Analytics save") - let options = [UIApplication.LaunchOptionsKey.remoteNotification: ["stop": "drop"]] - ParseAnalytics.trackAppOpened(launchOptions: options) { result in - - switch result { - - case .success: - XCTFail("Should have failed with not authorized.") - case .failure(let error): - XCTAssertTrue(error.message.contains("request permission")) - } - expectation.fulfill() - } - wait(for: [expectation], timeout: 10.0) - } - } - #endif #endif func testTrackAppOpened() { @@ -189,27 +206,6 @@ class ParseAnalyticsTests: XCTestCase { wait(for: [expectation], timeout: 10.0) } - #if canImport(AppTrackingTransparency) - func testTrackAppOpenedNotAuthorized() { - if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { - ParseSwift.configuration.isTestingSDK = false // Allow authorization check - let expectation = XCTestExpectation(description: "Analytics save") - ParseAnalytics.trackAppOpened(dimensions: ["stop": "drop"]) { result in - - switch result { - - case .success: - XCTFail("Should have failed with not authorized.") - case .failure(let error): - XCTAssertTrue(error.message.contains("request permission")) - } - expectation.fulfill() - } - wait(for: [expectation], timeout: 10.0) - } - } - #endif - func testTrackEvent() { let serverResponse = NoBody() let encoded: Data! @@ -313,48 +309,4 @@ class ParseAnalyticsTests: XCTestCase { } wait(for: [expectation], timeout: 10.0) } - - #if canImport(AppTrackingTransparency) - func testTrackEventNotAuthorized() { - if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { - ParseSwift.configuration.isTestingSDK = false // Allow authorization check - - let expectation = XCTestExpectation(description: "Analytics save") - let event = ParseAnalytics(name: "hello") - event.track { result in - - switch result { - - case .success: - XCTFail("Should have failed with not authorized.") - case .failure(let error): - XCTAssertTrue(error.message.contains("request permission")) - } - expectation.fulfill() - } - wait(for: [expectation], timeout: 10.0) - } - } - - func testTrackEventNotAuthorizedMutated() { - if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { - ParseSwift.configuration.isTestingSDK = false // Allow authorization check - - let expectation = XCTestExpectation(description: "Analytics save") - var event = ParseAnalytics(name: "hello") - event.track(dimensions: nil) { result in - - switch result { - - case .success: - XCTFail("Should have failed with not authorized.") - case .failure(let error): - XCTAssertTrue(error.message.contains("request permission")) - } - expectation.fulfill() - } - wait(for: [expectation], timeout: 10.0) - } - } - #endif } From 77fbe8b8cb7d3e6ce06c01042146e99959561242 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 6 Feb 2022 19:24:05 -0500 Subject: [PATCH 2/7] nit changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b342c44b8..7211fec23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.1...4.1.0) __Improvements__ -- Let the OS and developer decide if app tracking authorization is required when using ParseAnalytics. ParseAnalytics can now take any Codable value in its' dimensions instead of just strings. Added new methods, "setDimensions" and "updateDimensions" on ParseAnalytics. Also adds a new property, "date" to ParseAnalytics. The "at" property will be deprecated in ParseSwift 5.0.0, so developers should switch to "date". ParseAnalytics can now be properly decoded after encoding with a JSONEncoder. This is useful if ParseAnalytics need to be stored locally and sent to the server later ([#341](https://github.com/parse-community/Parse-Swift/pull/341)), thanks to @cbaker6. +- Let the OS and developer decide if app tracking authorization is required when using ParseAnalytics. ParseAnalytics can now take any Codable value in its' dimensions instead of just strings. Added new methods, "setDimensions" and "updateDimensions" on ParseAnalytics. Also adds a new property, "date" to ParseAnalytics. The "at" property will be deprecated in ParseSwift 5.0.0, so developers should switch to "date". ParseAnalytics can now be properly decoded after encoding with a JSONEncoder. This is useful if ParseAnalytics need to be stored locally and sent to the server later ([#341](https://github.com/parse-community/Parse-Swift/pull/341)), thanks to [Corey Baker](https://github.com/cbaker6). ### 4.0.1 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.0...4.0.1) @@ -27,7 +27,7 @@ __New features__ - Add DocC for SDK documentation ([#209](https://github.com/parse-community/Parse-Swift/pull/214)), thanks to [Corey Baker](https://github.com/cbaker6). __Improvements__ -- (Breaking Change) Make ParseRelation conform to Codable and add methods to make decoded stored ParseRelations "usable". ParseObjects can now contain properties of ParseRelation. In addition, ParseRelations can now be made from ParseObject pointers. For ParseRole, the computed properties: users and roles, are now optional. The queryRoles property has been changed to queryRoles() to improve the handling of thrown errors ([#328](https://github.com/parse-community/Parse-Swift/pull/328)), thanks to @cbaker6. +- (Breaking Change) Make ParseRelation conform to Codable and add methods to make decoded stored ParseRelations "usable". ParseObjects can now contain properties of ParseRelation. In addition, ParseRelations can now be made from ParseObject pointers. For ParseRole, the computed properties: users and roles, are now optional. The queryRoles property has been changed to queryRoles() to improve the handling of thrown errors ([#328](https://github.com/parse-community/Parse-Swift/pull/328)), thanks to [Corey Baker](https://github.com/cbaker6). - (Breaking Change) Change the following method parameter names: isUsingMongoDB -> usingMongoDB, isIgnoreCustomObjectIdConfig -> ignoringCustomObjectIdConfig, isUsingEQ -> usingEqComparator ([#321](https://github.com/parse-community/Parse-Swift/pull/321)), thanks to [Corey Baker](https://github.com/cbaker6). - (Breaking Change) Change the following method parameter names: isUsingMongoDB -> usingMongoDB, isIgnoreCustomObjectIdConfig -> ignoringCustomObjectIdConfig, isUsingEQ -> usingEqComparator ([#321](https://github.com/parse-community/Parse-Swift/pull/321)), thanks to [Corey Baker](https://github.com/cbaker6). - (Breaking Change) Change the following method parameter names: isUsingTransactions -> usingTransactions, isAllowingCustomObjectIds -> allowingCustomObjectIds, isUsingEqualQueryConstraint -> usingEqualQueryConstraint, isMigratingFromObjcSDK -> migratingFromObjcSDK, isDeletingKeychainIfNeeded -> deletingKeychainIfNeeded ([#323](https://github.com/parse-community/Parse-Swift/pull/323)), thanks to [Corey Baker](https://github.com/cbaker6). From 818bfd1ad73936f00d4e25e5299627c01f990a83 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 6 Feb 2022 19:32:28 -0500 Subject: [PATCH 3/7] nits --- Sources/ParseSwift/Types/ParseAnalytics.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/ParseSwift/Types/ParseAnalytics.swift b/Sources/ParseSwift/Types/ParseAnalytics.swift index b6f1bce91..9ed4d16e1 100644 --- a/Sources/ParseSwift/Types/ParseAnalytics.swift +++ b/Sources/ParseSwift/Types/ParseAnalytics.swift @@ -36,7 +36,8 @@ public struct ParseAnalytics: ParseType, Hashable { public var date: Date? /// The dictionary of information by which to segment this event. - /// - warning: It is not recommended to set this directly. + /// - warning: It is not recommended to set this directly. Use `setDimensions()` + /// or `updateDimensions()` instead. public var dimensions: [String: String]? { get { convertToString(dimensionsCodable) @@ -113,7 +114,6 @@ public struct ParseAnalytics: ParseType, Hashable { /** Set the dimensions. - - parameter dimensions: The dictionary of information by which to segment this event. */ @@ -123,7 +123,6 @@ public struct ParseAnalytics: ParseType, Hashable { /** Update the dimensions with additional data or replace specific key value pairs. - - parameter dimensions: The dictionary of information by which to segment this event. */ From 56109e73d846b27f61a2704675fe6a9ddc385db7 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 6 Feb 2022 19:53:02 -0500 Subject: [PATCH 4/7] Update .codecov.yml --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 728915380..bc61112b3 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,7 +6,7 @@ coverage: status: patch: default: - target: auto + target: 80 changes: false project: default: From 238ceee8446333990f04522c703e06ff0117b458 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 7 Feb 2022 01:10:19 -0500 Subject: [PATCH 5/7] coverage --- .codecov.yml | 2 +- CHANGELOG.md | 2 +- .../LiveQuery/ParseLiveQuery+async.swift | 2 +- .../LiveQuery/ParseLiveQuery+combine.swift | 2 +- .../ParseSwift/LiveQuery/ParseLiveQuery.swift | 1 - Sources/ParseSwift/Types/ParseACL.swift | 2 +- Sources/ParseSwift/Types/ParseAnalytics.swift | 55 +++++++------------ Tests/ParseSwiftTests/ParseACLTests.swift | 16 ++++++ .../ParseSwiftTests/ParseAnalyticsTests.swift | 39 +++++++++++-- 9 files changed, 74 insertions(+), 47 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index bc61112b3..728915380 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,7 +6,7 @@ coverage: status: patch: default: - target: 80 + target: auto changes: false project: default: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7211fec23..634e07cba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.1...4.1.0) __Improvements__ -- Let the OS and developer decide if app tracking authorization is required when using ParseAnalytics. ParseAnalytics can now take any Codable value in its' dimensions instead of just strings. Added new methods, "setDimensions" and "updateDimensions" on ParseAnalytics. Also adds a new property, "date" to ParseAnalytics. The "at" property will be deprecated in ParseSwift 5.0.0, so developers should switch to "date". ParseAnalytics can now be properly decoded after encoding with a JSONEncoder. This is useful if ParseAnalytics need to be stored locally and sent to the server later ([#341](https://github.com/parse-community/Parse-Swift/pull/341)), thanks to [Corey Baker](https://github.com/cbaker6). +- Let the OS and developer decide if app tracking authorization is required when using ParseAnalytics. ParseAnalytics can now take any Codable value in its' dimensions instead of just strings. Added new properties, "dimensionsCodable" and "date" to ParseAnalytics. The "at" property will be deprecated in ParseSwift 5.0.0, so developers should switch to "date". ParseAnalytics can now be properly decoded after encoding with a JSONEncoder. This is useful if ParseAnalytics need to be stored locally and sent to the server later ([#341](https://github.com/parse-community/Parse-Swift/pull/341)), thanks to [Corey Baker](https://github.com/cbaker6). ### 4.0.1 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.0...4.0.1) diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift index 3d10cc93b..e98191070 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift @@ -10,7 +10,7 @@ import Foundation extension ParseLiveQuery { - // MARK: Async/Await + // MARK: Connection - Async/Await /** Manually establish a connection to the `ParseLiveQuery` Server. diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift index f1892c4bf..0e34cf452 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift @@ -11,7 +11,7 @@ import Foundation import Combine extension ParseLiveQuery { - // MARK: Combine + // MARK: Connection - Combine /** Manually establish a connection to the `ParseLiveQuery` Server. Publishes when established. diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift index c91ebe063..82d70c269 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift @@ -223,7 +223,6 @@ Not attempting to open ParseLiveQuery socket anymore } } -// MARK: Helpers extension ParseLiveQuery { /// Current LiveQuery client. diff --git a/Sources/ParseSwift/Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift index b3e68e705..b58fc3cbb 100644 --- a/Sources/ParseSwift/Types/ParseACL.swift +++ b/Sources/ParseSwift/Types/ParseACL.swift @@ -33,7 +33,7 @@ public struct ParseACL: ParseType, public init(from decoder: Decoder) throws { guard let decoded = Access(rawValue: try decoder.singleValueContainer().decode(String.self)) else { - throw ParseError(code: .unknownError, message: "Not able to decode ACL") + throw ParseError(code: .unknownError, message: "Not able to decode ParseACL Access") } self = decoded } diff --git a/Sources/ParseSwift/Types/ParseAnalytics.swift b/Sources/ParseSwift/Types/ParseAnalytics.swift index 9ed4d16e1..905f92f99 100644 --- a/Sources/ParseSwift/Types/ParseAnalytics.swift +++ b/Sources/ParseSwift/Types/ParseAnalytics.swift @@ -21,7 +21,7 @@ public struct ParseAnalytics: ParseType, Hashable { /// Explicitly set the time associated with a given event. If not provided the server /// time will be used. - /// - warning: This will be deprecated in ParseSwift 5.5 in favor of `date`. + /// - warning: This will be deprecated in ParseSwift 5.0.0 in favor of `date`. public var at: Date? { // swiftlint:disable:this identifier_name get { date @@ -36,18 +36,28 @@ public struct ParseAnalytics: ParseType, Hashable { public var date: Date? /// The dictionary of information by which to segment this event. - /// - warning: It is not recommended to set this directly. Use `setDimensions()` - /// or `updateDimensions()` instead. + /// - warning: This will be changed to [String: Codable] in ParseSwift 5.0.0. public var dimensions: [String: String]? { get { - convertToString(dimensionsCodable) + convertToString(dimensionsAnyCodable) } set { - dimensionsCodable = convertToAnyCodable(newValue) + dimensionsAnyCodable = convertToAnyCodable(newValue) } } - var dimensionsCodable: [String: AnyCodable]? + /// The dictionary of information by which to segment this event. + /// - warning: This will be deprecated in ParseSwift 5.0.0 in favor of `dimensions`. + public var dimensionsCodable: [String: Codable]? { + get { + convertToString(dimensionsAnyCodable) + } + set { + dimensionsAnyCodable = convertToAnyCodable(newValue) + } + } + + var dimensionsAnyCodable: [String: AnyCodable]? enum CodingKeys: String, CodingKey { case date = "at" @@ -66,14 +76,14 @@ public struct ParseAnalytics: ParseType, Hashable { dimensions: [String: Codable]? = nil, at date: Date? = nil) { self.name = name - self.dimensionsCodable = convertToAnyCodable(dimensions) + self.dimensionsAnyCodable = convertToAnyCodable(dimensions) self.date = date } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(date, forKey: .date) - try container.encodeIfPresent(dimensionsCodable, forKey: .dimensions) + try container.encodeIfPresent(dimensionsAnyCodable, forKey: .dimensions) if !(encoder is _ParseEncoder) { try container.encode(name, forKey: .name) } @@ -112,33 +122,6 @@ public struct ParseAnalytics: ParseType, Hashable { // MARK: Intents - /** - Set the dimensions. - - parameter dimensions: The dictionary of information by which to segment this - event. - */ - public mutating func setDimensions(_ dimensions: [String: Codable]) { - dimensionsCodable = convertToAnyCodable(dimensions) - } - - /** - Update the dimensions with additional data or replace specific key value pairs. - - parameter dimensions: The dictionary of information by which to segment this - event. - */ - public mutating func updateDimensions(_ dimensions: [String: Codable]) { - guard let convertedDimensions = convertToAnyCodable(dimensions) else { - return - } - if dimensionsCodable == nil { - dimensionsCodable = convertedDimensions - } else { - for (key, value) in convertedDimensions { - dimensionsCodable?[key] = value - } - } - } - #if os(iOS) /** Tracks *asynchronously* this application being launched. If this happened as the result of the @@ -265,7 +248,7 @@ public struct ParseAnalytics: ParseType, Hashable { completion: @escaping (Result) -> Void) { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) - self.dimensionsCodable = convertToAnyCodable(dimensions) + self.dimensionsAnyCodable = convertToAnyCodable(dimensions) self.date = date self.saveCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in diff --git a/Tests/ParseSwiftTests/ParseACLTests.swift b/Tests/ParseSwiftTests/ParseACLTests.swift index d6b0baa1c..22fef2283 100644 --- a/Tests/ParseSwiftTests/ParseACLTests.swift +++ b/Tests/ParseSwiftTests/ParseACLTests.swift @@ -215,6 +215,22 @@ class ParseACLTests: XCTestCase { } } + func testCodingAccess() throws { + let access = ParseACL.Access.read + let encoded = try ParseCoding.jsonEncoder().encode(access) + let decoded = try ParseCoding.jsonDecoder().decode(ParseACL.Access.self, from: encoded) + XCTAssertEqual(access, decoded) + let access2 = ParseACL.Access.write + let encoded2 = try ParseCoding.jsonEncoder().encode(access2) + let decoded2 = try ParseCoding.jsonDecoder().decode(ParseACL.Access.self, from: encoded2) + XCTAssertEqual(access2, decoded2) + guard let data = "hello".data(using: .utf8) else { + XCTFail("Should have unwrapped") + return + } + XCTAssertThrowsError(try ParseCoding.jsonDecoder().decode(ParseACL.Access.self, from: data)) + } + func testDebugString() { var acl = ParseACL() acl.setReadAccess(objectId: "a", value: false) diff --git a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift index e10f0b573..c20be2965 100644 --- a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift +++ b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift @@ -67,14 +67,43 @@ class ParseAnalyticsTests: XCTestCase { XCTAssertEqual(decoded3, expected3) } + func testEquatable() throws { + let name = "hello" + let event = ParseAnalytics(name: name) + let event2 = ParseAnalytics(name: name, + dimensions: ["stop": "drop"], + at: Date()) + XCTAssertEqual(event, event) + XCTAssertNotEqual(event, event2) + XCTAssertEqual(event2, event2) + } + + func testHashable() throws { + let name = "hello" + let event = ParseAnalytics(name: name) + let event2 = ParseAnalytics(name: name, + dimensions: ["stop": "drop"], + at: Date()) + let event3 = ParseAnalytics(name: "world") + let events = [event: 1, event2: 2] + XCTAssertEqual(events[event], 1) + XCTAssertEqual(events[event2], 2) + XCTAssertNil(events[event3]) + } + func testSetDimensions() throws { let name = "hello" let dimensions = ["stop": "drop"] let dimensions2 = ["open": "up shop"] var event = ParseAnalytics(name: name, dimensions: dimensions) XCTAssertEqual(event.dimensions, dimensions) - event.setDimensions(dimensions2) + event.dimensionsCodable = dimensions2 XCTAssertEqual(event.dimensions, dimensions2) + let encoded = try ParseCoding.jsonEncoder().encode(event.dimensions) + let encodedExpected = try ParseCoding.jsonEncoder().encode(dimensions2) + XCTAssertEqual(encoded, encodedExpected) + let encoded2 = try ParseCoding.jsonEncoder().encode(AnyCodable(event.dimensionsCodable)) + XCTAssertEqual(encoded2, encodedExpected) } func testUpdateDimensions() throws { @@ -87,10 +116,10 @@ class ParseAnalyticsTests: XCTestCase { } var event = ParseAnalytics(name: name, dimensions: dimensions) XCTAssertEqual(event.dimensions, dimensions) - event.updateDimensions(dimensions2) + event.dimensionsCodable = dimensions2 XCTAssertNotEqual(event.dimensions, dimensions) - XCTAssertNotEqual(event.dimensions, dimensions2) - XCTAssertEqual(event.dimensions, dimensions3) + XCTAssertNotEqual(event.dimensions, dimensions3) + XCTAssertEqual(event.dimensions, dimensions2) } func testUpdateDimensionsNonInitially() throws { @@ -98,7 +127,7 @@ class ParseAnalyticsTests: XCTestCase { let dimensions = ["stop": "drop"] var event = ParseAnalytics(name: name) XCTAssertNil(event.dimensions) - event.updateDimensions(dimensions) + event.dimensionsCodable = dimensions XCTAssertEqual(event.dimensions, dimensions) } From 33e2e41f2baee791fb8d7c453e0191f79e4a24ba Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 7 Feb 2022 01:27:17 -0500 Subject: [PATCH 6/7] more coverage --- Tests/ParseSwiftTests/ParseAnalyticsTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift index c20be2965..22bdcdd0a 100644 --- a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift +++ b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift @@ -97,6 +97,8 @@ class ParseAnalyticsTests: XCTestCase { let dimensions2 = ["open": "up shop"] var event = ParseAnalytics(name: name, dimensions: dimensions) XCTAssertEqual(event.dimensions, dimensions) + event.dimensions = dimensions2 + XCTAssertEqual(event.dimensions, dimensions2) event.dimensionsCodable = dimensions2 XCTAssertEqual(event.dimensions, dimensions2) let encoded = try ParseCoding.jsonEncoder().encode(event.dimensions) From 4caeb51e09cc6ae06c015e755be4069a9ce3ae69 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 7 Feb 2022 01:48:36 -0500 Subject: [PATCH 7/7] improve --- CHANGELOG.md | 2 +- Sources/ParseSwift/Types/ParseAnalytics.swift | 14 +-------- .../ParseSwiftTests/ParseAnalyticsTests.swift | 29 ++++++++++--------- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 634e07cba..62814513f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.1...4.1.0) __Improvements__ -- Let the OS and developer decide if app tracking authorization is required when using ParseAnalytics. ParseAnalytics can now take any Codable value in its' dimensions instead of just strings. Added new properties, "dimensionsCodable" and "date" to ParseAnalytics. The "at" property will be deprecated in ParseSwift 5.0.0, so developers should switch to "date". ParseAnalytics can now be properly decoded after encoding with a JSONEncoder. This is useful if ParseAnalytics need to be stored locally and sent to the server later ([#341](https://github.com/parse-community/Parse-Swift/pull/341)), thanks to [Corey Baker](https://github.com/cbaker6). +- Let the OS and developer decide if app tracking authorization is required when using ParseAnalytics. ParseAnalytics can now take any Codable value in its' dimensions instead of just strings. Added a new property "date" to ParseAnalytics. The "at" property will be deprecated in ParseSwift 5.0.0, so developers should switch to "date". ParseAnalytics can now be properly decoded after encoding with a JSONEncoder. This is useful if ParseAnalytics need to be stored locally and sent to the server later ([#341](https://github.com/parse-community/Parse-Swift/pull/341)), thanks to [Corey Baker](https://github.com/cbaker6). ### 4.0.1 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.0...4.0.1) diff --git a/Sources/ParseSwift/Types/ParseAnalytics.swift b/Sources/ParseSwift/Types/ParseAnalytics.swift index 905f92f99..7b505af3b 100644 --- a/Sources/ParseSwift/Types/ParseAnalytics.swift +++ b/Sources/ParseSwift/Types/ParseAnalytics.swift @@ -36,19 +36,7 @@ public struct ParseAnalytics: ParseType, Hashable { public var date: Date? /// The dictionary of information by which to segment this event. - /// - warning: This will be changed to [String: Codable] in ParseSwift 5.0.0. - public var dimensions: [String: String]? { - get { - convertToString(dimensionsAnyCodable) - } - set { - dimensionsAnyCodable = convertToAnyCodable(newValue) - } - } - - /// The dictionary of information by which to segment this event. - /// - warning: This will be deprecated in ParseSwift 5.0.0 in favor of `dimensions`. - public var dimensionsCodable: [String: Codable]? { + public var dimensions: [String: Codable]? { get { convertToString(dimensionsAnyCodable) } diff --git a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift index 22bdcdd0a..e92d7b5fc 100644 --- a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift +++ b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift @@ -52,7 +52,7 @@ class ParseAnalyticsTests: XCTestCase { XCTAssertEqual(command2.method, API.Method.POST) XCTAssertNotNil(command2.body) XCTAssertEqual(command2.body?.at, date) - XCTAssertEqual(command2.body?.dimensions, dimensions) + XCTAssertNotNil(command2.body?.dimensions) event2.at = nil //Clear date for comparison let decoded = event2.debugDescription @@ -96,15 +96,15 @@ class ParseAnalyticsTests: XCTestCase { let dimensions = ["stop": "drop"] let dimensions2 = ["open": "up shop"] var event = ParseAnalytics(name: name, dimensions: dimensions) - XCTAssertEqual(event.dimensions, dimensions) + let encodedDimensions = try ParseCoding.jsonEncoder().encode(AnyCodable(event.dimensions)) + let decodedDictionary = try ParseCoding.jsonDecoder().decode([String: String].self, + from: encodedDimensions) + XCTAssertEqual(decodedDictionary, dimensions) event.dimensions = dimensions2 - XCTAssertEqual(event.dimensions, dimensions2) - event.dimensionsCodable = dimensions2 - XCTAssertEqual(event.dimensions, dimensions2) - let encoded = try ParseCoding.jsonEncoder().encode(event.dimensions) + let encoded = try ParseCoding.jsonEncoder().encode(AnyCodable(event.dimensions)) let encodedExpected = try ParseCoding.jsonEncoder().encode(dimensions2) XCTAssertEqual(encoded, encodedExpected) - let encoded2 = try ParseCoding.jsonEncoder().encode(AnyCodable(event.dimensionsCodable)) + let encoded2 = try ParseCoding.jsonEncoder().encode(event.dimensionsAnyCodable) XCTAssertEqual(encoded2, encodedExpected) } @@ -117,11 +117,10 @@ class ParseAnalyticsTests: XCTestCase { dimensions3[key] = value } var event = ParseAnalytics(name: name, dimensions: dimensions) - XCTAssertEqual(event.dimensions, dimensions) - event.dimensionsCodable = dimensions2 - XCTAssertNotEqual(event.dimensions, dimensions) - XCTAssertNotEqual(event.dimensions, dimensions3) - XCTAssertEqual(event.dimensions, dimensions2) + event.dimensions = dimensions2 + let encoded = try ParseCoding.jsonEncoder().encode(AnyCodable(event.dimensions)) + let encodedExpected = try ParseCoding.jsonEncoder().encode(dimensions2) + XCTAssertEqual(encoded, encodedExpected) } func testUpdateDimensionsNonInitially() throws { @@ -129,8 +128,10 @@ class ParseAnalyticsTests: XCTestCase { let dimensions = ["stop": "drop"] var event = ParseAnalytics(name: name) XCTAssertNil(event.dimensions) - event.dimensionsCodable = dimensions - XCTAssertEqual(event.dimensions, dimensions) + event.dimensions = dimensions + let encoded = try ParseCoding.jsonEncoder().encode(AnyCodable(event.dimensions)) + let encodedExpected = try ParseCoding.jsonEncoder().encode(dimensions) + XCTAssertEqual(encoded, encodedExpected) } #if os(iOS)