From 3c379282ecb19cfde9870da1bb90c8c7a648f563 Mon Sep 17 00:00:00 2001 From: "Thuy.Copeland" Date: Fri, 27 Jul 2018 14:17:47 -0500 Subject: [PATCH 001/112] Temporariliy point to new Authenticator changes --- Podfile | 2 +- Podfile.lock | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Podfile b/Podfile index 512c2601b49..f2eaa60c014 100644 --- a/Podfile +++ b/Podfile @@ -18,7 +18,7 @@ target 'WooCommerce' do # pod 'Automattic-Tracks-iOS', :git => 'https://github.com/Automattic/Automattic-Tracks-iOS.git', :tag => '0.2.3' pod 'Gridicons', '0.15' - pod 'WordPressAuthenticator', '1.0.4' + pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :branch => 'feature/more-configurations' pod 'WordPressShared', '1.0.8' diff --git a/Podfile.lock b/Podfile.lock index 581dc1c680a..e27b472995f 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -72,7 +72,7 @@ DEPENDENCIES: - Crashlytics (~> 3.10) - Gridicons (= 0.15) - KeychainAccess (~> 3.1) - - WordPressAuthenticator (= 1.0.4) + - WordPressAuthenticator (from `https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git`, branch `feature/more-configurations`) - WordPressShared (= 1.0.8) SPEC REPOS: @@ -93,7 +93,6 @@ SPEC REPOS: - Reachability - SVProgressHUD - UIDeviceIdentifier - - WordPressAuthenticator - WordPressKit - WordPressShared - WordPressUI @@ -103,11 +102,17 @@ EXTERNAL SOURCES: Automattic-Tracks-iOS: :git: https://github.com/Automattic/Automattic-Tracks-iOS.git :tag: 0.2.3 + WordPressAuthenticator: + :branch: feature/more-configurations + :git: https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git CHECKOUT OPTIONS: Automattic-Tracks-iOS: :git: https://github.com/Automattic/Automattic-Tracks-iOS.git :tag: 0.2.3 + WordPressAuthenticator: + :commit: 07fd291b0ad628b1eb3415044843a91e13d77f29 + :git: https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git SPEC CHECKSUMS: 1PasswordExtension: 0e95bdea64ec8ff2f4f693be5467a09fac42a83d @@ -133,6 +138,6 @@ SPEC CHECKSUMS: WordPressUI: af141587ec444f9af753a00605bd0d3f14d8d8a3 wpxmlrpc: bfc572f62ce7ee897f6f38b098d2ba08732ecef4 -PODFILE CHECKSUM: c627b723e165dabc83c6716871f3199190464f2c +PODFILE CHECKSUM: 9227f0f9d64792cf9f874a21f7ce9f2943065b29 COCOAPODS: 1.5.3 From 50ad6f93b7882e5eb75759126008867891bc8129 Mon Sep 17 00:00:00 2001 From: "Thuy.Copeland" Date: Fri, 27 Jul 2018 14:18:10 -0500 Subject: [PATCH 002/112] Add new color definitions from Woo handbook --- WooCommerce/Classes/Styles/Style.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/WooCommerce/Classes/Styles/Style.swift b/WooCommerce/Classes/Styles/Style.swift index d6f5e31d36a..5b139c6f4af 100644 --- a/WooCommerce/Classes/Styles/Style.swift +++ b/WooCommerce/Classes/Styles/Style.swift @@ -31,6 +31,8 @@ protocol Style { var subheadlineFont: UIFont { get } var tableViewBackgroundColor: UIColor { get } var wooCommerceBrandColor: UIColor { get } + var wooAccent: UIColor { get } + var wooGreyLight: UIColor { get } var wooGreyMid: UIColor { get } var wooGreyTextMin: UIColor { get } var wooGreyBorder: UIColor { get } @@ -67,6 +69,8 @@ class DefaultStyle: Style { let subheadlineFont = UIFont.font(forStyle: .subheadline, weight: .regular) let tableViewBackgroundColor = UIColor(red: 247.0/255.0, green: 247.0/255.0, blue: 247.0/255.0, alpha: 1.0) let wooCommerceBrandColor = UIColor(red: 0x96/255.0, green: 0x58/255.0, blue: 0x8A/255.0, alpha: 0xFF/255.0) + let wooAccent = UIColor(red: 113.0/255.0, green: 176.0/255.0, blue: 47.0/255.0, alpha: 1.0) + let wooGreyLight = UIColor(red: 247.0/255.0, green: 247.0/255.0, blue: 247.0/255.0, alpha: 1.0) let wooGreyMid = UIColor(red: 150.0/255.0, green: 150.0/255.0, blue: 150.0/255.0, alpha: 1.0) let wooGreyTextMin = UIColor(red: 89.0/255.0, green: 89.0/255.0, blue: 89.0/255.0, alpha: 1.0) let wooGreyBorder = UIColor(red: 230.0/255.0, green: 230.0/255.0, blue: 230.0/255.0, alpha: 1.0) @@ -199,6 +203,14 @@ class StyleManager { return active.wooCommerceBrandColor } + static var wooAccent: UIColor { + return active.wooAccent + } + + static var wooGreyLight: UIColor { + return active.wooGreyLight + } + static var wooGreyMid: UIColor { return active.wooGreyMid } From 53e1aaf5df9a4170295a6dcb89af719aa79d3002 Mon Sep 17 00:00:00 2001 From: "Thuy.Copeland" Date: Fri, 27 Jul 2018 15:37:18 -0500 Subject: [PATCH 003/112] Add the Woo logo to assets --- .../Authentication/AuthenticationManager.swift | 4 +++- WooCommerce/Classes/Styles/Style.swift | 6 ++++++ .../woo-logo.imageset/Contents.json | 12 ++++++++++++ .../woo-logo.imageset/woo-logo.pdf | Bin 0 -> 5387 bytes 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 WooCommerce/Resources/Images.xcassets/woo-logo.imageset/Contents.json create mode 100644 WooCommerce/Resources/Images.xcassets/woo-logo.imageset/woo-logo.pdf diff --git a/WooCommerce/Classes/Authentication/AuthenticationManager.swift b/WooCommerce/Classes/Authentication/AuthenticationManager.swift index bb1f5d39033..42bb92fcf1e 100644 --- a/WooCommerce/Classes/Authentication/AuthenticationManager.swift +++ b/WooCommerce/Classes/Authentication/AuthenticationManager.swift @@ -34,7 +34,9 @@ class AuthenticationManager { primaryTitleColor: StyleManager.buttonPrimaryTitleColor, secondaryTitleColor: StyleManager.buttonSecondaryTitleColor, disabledTitleColor: StyleManager.buttonDisabledTitleColor, - subheadlineColor: StyleManager.wooCommerceBrandColor) + subheadlineColor: StyleManager.wooCommerceBrandColor, + viewControllerBackgroundColor: StyleManager.wooGreyLight, + navBarImage: StyleManager.navBarImage) WordPressAuthenticator.initialize(configuration: configuration, style: style) WordPressAuthenticator.shared.delegate = self diff --git a/WooCommerce/Classes/Styles/Style.swift b/WooCommerce/Classes/Styles/Style.swift index 5b139c6f4af..875a403a978 100644 --- a/WooCommerce/Classes/Styles/Style.swift +++ b/WooCommerce/Classes/Styles/Style.swift @@ -18,6 +18,7 @@ protocol Style { var cellSeparatorColor: UIColor { get } var defaultTextColor: UIColor { get } var destructiveActionColor: UIColor { get } + var navBarImage: UIImage { get } var sectionBackgroundColor: UIColor { get } var sectionTitleColor: UIColor { get } var statusDangerColor: UIColor { get } @@ -56,6 +57,7 @@ class DefaultStyle: Style { let cellSeparatorColor = UIColor.lightGray let defaultTextColor = UIColor.black let destructiveActionColor = UIColor(red: 197.0/255.0, green: 60.0/255.0, blue: 53.0/255.0, alpha: 1.0) + let navBarImage = UIImage(named: "woo-logo")! let sectionBackgroundColor = UIColor(red: 239.0/255.0, green: 239.0/255.0, blue: 244.0/255.0, alpha: 1.0) let sectionTitleColor = UIColor.darkGray let statusDangerColor = UIColor(red: 255.0/255.0, green: 230.0/255.0, blue: 229.0/255.0, alpha: 1.0) @@ -151,6 +153,10 @@ class StyleManager { return active.destructiveActionColor } + static var navBarImage: UIImage { + return active.navBarImage + } + static var sectionBackgroundColor: UIColor { return active.sectionBackgroundColor } diff --git a/WooCommerce/Resources/Images.xcassets/woo-logo.imageset/Contents.json b/WooCommerce/Resources/Images.xcassets/woo-logo.imageset/Contents.json new file mode 100644 index 00000000000..d669e7e23c2 --- /dev/null +++ b/WooCommerce/Resources/Images.xcassets/woo-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "woo-logo.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WooCommerce/Resources/Images.xcassets/woo-logo.imageset/woo-logo.pdf b/WooCommerce/Resources/Images.xcassets/woo-logo.imageset/woo-logo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d897497bfacfb2dd20e84cb6cc029086d7d5b9de GIT binary patch literal 5387 zcmb_g2{cr1*tbk!s8p6nxyf3Zg_%LN7(0dRvNXon8I8%BP?l_2B1=h@>|0TZ?0ZCZ zEl3Q>E?JXrM*Qnv-#PztzVqEPXYRef`@GNdKFj;w`~3K{6qL@uA);WudeS0kD)Z~Z zH}#ER1ONl@7It7MDFCX0bFwDd07x=)5r8V$IuLOL^4S4H#3|sgcuO20BLj9J5^xwt zusii>U3y(X-8TnR&Zu2DBPevfOO@J$bRhDqm1A6UyFdp z>fngLC9+WkB><}GNH#6Mb4Kr+RRL1~YN%;phr<#9O4L*x;egn-RT-Jz6jPvG#XEr& z-@bZb`yROhM%aof>ta{$!9){>kA1OkAn;B2jJh=7O~07c^+@B|%a3>F8L_^Czb zJjTTyfK%Kf`zvotbkV{Q(0E5@yb~o*;{Tn%ndnY18cvVi*pjVakf-l_H{_{o{wydo zT%hxAI9$fSXLOmy0KZl1>{V_4awKg)dHzSz3Rb79p@bwUOkHOuIm{P*pSzsiI*e93 z(`>*`Q8DZYs8xg7S3VVNsv?o7%_5;hRs6C+xtgmM>Dxm}oevPV_n?_@c7#YY-sk(s zb}gXkpd#0+e1tVaHEGXRldqo>+O^x9_wloexj7elhay_yuw4FvEb+BB^IFe7Hwijs znWDg0r=kL9$A*3zh;3tup8?7gRO9+1?{ddcl~WbpDx0O5Vn=1sFCf7mhtFR3TPHYa*dEEzp15_6r(3Y3q@JH4Bwe~BTHScWF5kP*`5MzK z=k&M2t!rM%Pgf-FrLP}kI2pJZ%m%gx-Z?SOZdEQ6WJW#nQ45gGMI?AtXj%=_;@mRz3N%K20g@J zobAwL1nHIv(o(%TB{J9HK}ylJw!U)OwYw|mkY@c-UJ|qSa{M9FuB_3@9GgkJlEp;7?| zEFY_JdY$$3f_0Ix+DPg;Qj5s_EZZUAb>J&Vsf9PhZ_GYYKyoBy{VeG_PCR>2&N-x6 z&vAUus#$~F{upD)>wEfaG<&u32+;f8yj^?MMI9^T?Qe1lb>@ezVt&k(N1j@g>qu#R zJPHq79coc;m5Od+zM5t{*xi-+Ua zX$K8uUoaS4IJVb5-)V5@@DtfNNCMbB=iRF6D{MQ&>r-&9!(7sFAFjcPr|(aHQ-96Y z%E{EH&a9t4Wh=!HipYBC#v|b%UZ#sT7&wRw|9$|Z=t1@O8rUvwO2H$=e=UOFx9FXn z4ZgE4Um$PLP&vwb!-=xd?iBS!W%4HaU$2v-yKcHVZ`p!6D#R+@9O41^mN-2bfG$ZD zR*-hEs;?GJpg+H>!X71-gF^b2y4BRmK|EMR7RH-5EczK1nY9{gv=tOdgJJdzbNR@x zLy4PBM#FC>p7+1Cp7ZW6qtg0*15`^ir1nLm7_!Vx)tDSQ+*-dvbA*|Czae$?h?Nyc zc~ya?=X-^8LPC~GWAXAV#MiR?y>OKlzf0eiPZW3b5G$ zdxrH`2q;+9BI96xye;W-v(@@SvCf(W{B%s+`H@@h8XZd|Ot)d|zJ>U^iqw2YjtMqV zGx=OIGe&*mHfvRHhmT*^*_3Yf+|XO#pYC`d7xutt*kL*kTERxO%xiq_SaD^n9#x6s z-gBgpJk2?PYH<%$$vvL>RTQU4%E-)6S7>Wv+8@_pxw*o;HCioU`?{r0_gEhhT&s1V$r# zc^{Xrs59*lK;`eN!c2RY7NSC9dk*wOPG*1#EC(N;cD-rmOx5G(`37|12FiJ_3CN&& zpEDzquktx?V2wJAddSa~{>D5rYpPsMBwPA{1o=MZqsH_eXgMu$7TN}Ma3pi|xwSX^ zSw~LY%t=9ONiUrwa+cAX-dul3_Xa@h-K6&RKNwHbjoCLq_00Eekt7$ra&7BJ1$Wl_ zbnMmdN7PE$WcEL=Wgj^@PbC~+UVDfBTr)jwr;;K4(FFzhNJC!ggD32xc+zRI6%!-( zryqS0t`c=+jCOBO3MT#tTO-G4kWtu(g+8XwLJig%kB!&YU>`?WF^l@G-FRft`#8f; zAWznn0d??R_?deB%Mq6^6~#TK9l%UWa_w)b=J>?re7*FDiHH-j0QCGoe^|NiRNcN2 z{ss4!r-oSOZXBp_nYXxhq7v-G<#T*Eg1fOwAdKaAu&4&|vR~vCN+|KuRkhv$Jz?|)OqH12EMXiNT`+$j6lsW>k_&6xf;w>Wc1vVi#sAGIGD-%Dwdcf!iUBEBNVUv!Li zyhjzIO7nFNRC9O#Fj)-;sm0p#KxHM zDae>)Jn_^F7Mn2JrfbDl94e8LUoew-rc)+;FjqPsn^kORopzzP{jg~?3mS>BXd^ufp|PpR*r zM4eRgwKR`=Yo{5dLk&Zd*aq3mg=d6y&P)k&8wnZhF?1~XQnX(8u0GM;6zkm-Xg7*2 zZqFDTf100tX4tiA##=Jcv}lm6{6cxh^8RI>4gL){opg|UP{sb00ivs5xxaVs!Ku$9 zrR@u0qjUmZ0*|{@Gl!Fm+J)NpwWl#MvCp&joH;`1yfRYCMTmk}Lc+}5ol|=B-p7s_ zPp!9|?+>4Ft#_;+9FTsyS-Nrhfck+b<>%sUgD9gJqT;tCC-3<{q|uUO34&1K4* zl?j#!mpNSNTAAw&UBztDY`U%we(j&1SnuD^1c`(47(Rp3K}sNfIvGY0hC_R+Wu#GZu;p==yS%h~}T!m`M#W=FVa8!VhBO=ZQC2TrS3>qm*PwdP==3M> z=qyCkckn@!RvSTcX%MgsNUDG%C+o@hKh z5x!u$iu)b~it~Ts->DE+ZyJnMEl_1u9i`Efd;0O}6Z1vCvFw=0m^2~L?9S}hw*Don zCEfCTn2@5=rlQgU70^0F8Vp_b%C>h_?{Mn5&L|B*7bC0k_}f1;zNv)q-s8Jz#8_m;6e*?cn&*SmKyd>#>%vEgRR7R!Fz2)|IY50>9TFXYKdr2lN$~ynk`Q&9tU$ zIMx z&bNCX$79y{&7$>F$R|y{57AS@M&1T%gKf+HGZl}PA8ojObftIuFgRKE)iA-r$n5Q# zf{&*8q_)M>#pmS))0O&|OII!*u#K>cC=tneQlVC8r`((QCH;#ZXGG$7#TT#VGfD57 z-uG7xJNk4}htoFD$sA|ldg;@=qGg{i=h%SxFp^i-F_rqItjgNQ+(%(XWg)6VIY9YQ zQ*Ff6Udf(*u`IC|RBd>4k=p;I8@fb=%3ObB-00&c&N|NAqu2?(v>jH{E~^Eth$dZf_+Wr48M?J`1Vz z+A{kfJ?TBP*-4juz)ZHyQ+(5A6FWVcDgf3z^&m$!f3tVB-zvLeY5HOR<&NT8))9fF zXy{h#TA^m$o{sE}(@G^uUzGEeJHpn&20b4yHO#k$#dVJcR0;ifwi4T!n|$4Eqr%#v zrGDPNb1FerZ78c!V!dzKal-QRSht()Tt;TdP;RB{vfpOP26m|_YI0h0RWmj@T-NOx z!}mpZen+eE;T8X{8~@AaD&;eDyJthe|6vp;{W1k3clD}hv^>TIX9>V5->??})7{2y zcO0dk|HV4E{K+GbwE|)iB4h#pjlW86vf%(!-PY2DOc?Cw`>p?n|IV!Z)QQFrF%Ed^ zpOl8nE|;N?Be>Y&od75tg7|&>E;k~09)krmbpS(hvk!rbLQsJARSbdX36MF8ZQ8?~ zD75Pbe3#A;)UtKQIXGg7cmhDCH2^pS4iWh!wo6V3Dx4L8!Np->B4Tg}Q5Xzm41)=f zf4?MmyI{dz(65006F*81yxSL(%~@W>l9?m;4v~Z=6GoJIY)_8NRczmrJyxK!2=JfO z3G_VP@?ZDrI1e{G!O{f`-(gMU3?V8)F6=*SAW(l}NHWFrryTO{7$8uTu>YYC zg(4^aPwe-xL;}Xv0Y}(o7OC&e0++UO5pzi D%J0#T literal 0 HcmV?d00001 From 321821341ba14665ceb38064165e4ec9cc5c5fd7 Mon Sep 17 00:00:00 2001 From: "Thuy.Copeland" Date: Fri, 27 Jul 2018 16:07:28 -0500 Subject: [PATCH 004/112] `pod update` --- Podfile.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index e27b472995f..b026d39253f 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -13,7 +13,7 @@ PODS: - CocoaLumberjack/Default - CocoaLumberjack/Swift (3.4.2): - CocoaLumberjack/Default - - Crashlytics (3.10.4): + - Crashlytics (3.10.5): - Fabric (~> 1.7.9) - Fabric (1.7.9) - FormatterKit/Resources (1.8.2) @@ -52,7 +52,7 @@ PODS: - WordPressShared (~> 1.0) - WordPressUI (~> 1.0) - wpxmlrpc (~> 0.8) - - WordPressKit (1.1): + - WordPressKit (1.2): - Alamofire (~> 4.7) - CocoaLumberjack (= 3.4.2) - NSObject-SafeExpectations (= 0.0.3) @@ -111,7 +111,7 @@ CHECKOUT OPTIONS: :git: https://github.com/Automattic/Automattic-Tracks-iOS.git :tag: 0.2.3 WordPressAuthenticator: - :commit: 07fd291b0ad628b1eb3415044843a91e13d77f29 + :commit: 07d999ab6c731288eeef369f66d8d73ed1f187e6 :git: https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git SPEC CHECKSUMS: @@ -119,7 +119,7 @@ SPEC CHECKSUMS: Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223 Automattic-Tracks-iOS: d8c6c6c1351b1905a73e45f431b15598d71963b5 CocoaLumberjack: db7cc9e464771f12054c22ff6947c5a58d43a0fd - Crashlytics: 915a7787b84f635fb2a81f92a90e265c2c413f76 + Crashlytics: 7f2e38d302d9da96475b3d64d86fb29e31a542b7 Fabric: a2917d3895e4c1569b9c3170de7320ea1b1e6661 FormatterKit: 4b8f29acc9b872d5d12a63efb560661e8f2e1b98 GoogleSignInRepacked: d357702618c555f38923576924661325eb1ef22b @@ -133,7 +133,7 @@ SPEC CHECKSUMS: SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 UIDeviceIdentifier: a959a6d4f51036b4180dd31fb26483a820f1cc46 WordPressAuthenticator: 2825f0c56f83a17470564dbec427991fa5cac5af - WordPressKit: a24baaa783c3a221f2d9a51c19318cbb27333373 + WordPressKit: 68eaa8df5ceedeed03ba796afc4b825f0bed4fe2 WordPressShared: 063e1e8b1a7aaf635abf17f091a2d235a068abdc WordPressUI: af141587ec444f9af753a00605bd0d3f14d8d8a3 wpxmlrpc: bfc572f62ce7ee897f6f38b098d2ba08732ecef4 From d4574bf6eeea688e499e39edb3a6515020105020 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 30 Jul 2018 14:45:34 -0500 Subject: [PATCH 005/112] WIP: OrderStats models and remote --- .../Networking.xcodeproj/project.pbxproj | 8 ++ .../Networking/Model/OrderStatItem.swift | 118 ++++++++++++++++++ .../Networking/Remote/OrderStatsRemote.swift | 45 +++++++ .../Settings/WordPressAPIVersion.swift | 4 + Yosemite/Yosemite/Model/Model.swift | 2 + 5 files changed, 177 insertions(+) create mode 100644 Networking/Networking/Model/OrderStatItem.swift create mode 100644 Networking/Networking/Remote/OrderStatsRemote.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 1dbbfe83535..d504aa69604 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 21DB5B99C4107CF69C0A57EC /* Pods_NetworkingTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */; }; 6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; }; 741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */; }; + 748D4248210F89ED00CF7D1B /* OrderStatsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */; }; + 748D424A210F92EA00CF7D1B /* OrderStatItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */; }; 74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */; }; 74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06520EEB76400B6EDC9 /* order-notes.json */; }; 74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */; }; @@ -78,6 +80,8 @@ /* Begin PBXFileReference section */ 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NetworkingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponLine.swift; sourceTree = ""; }; + 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsRemote.swift; sourceTree = ""; }; + 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatItem.swift; sourceTree = ""; }; 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNote.swift; sourceTree = ""; }; 74C8F06520EEB76400B6EDC9 /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = ""; }; 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapper.swift; sourceTree = ""; }; @@ -275,6 +279,7 @@ B557DA0020975500005962F4 /* Remote.swift */, B505F6D020BEE39600BB1B69 /* AccountRemote.swift */, B557DA0120975500005962F4 /* OrdersRemote.swift */, + 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */, ); path = Remote; sourceTree = ""; @@ -310,6 +315,7 @@ B5C6FCCE20A3592900A4F8E4 /* OrderItem.swift */, 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */, B5BB1D1120A255EC00112D92 /* OrderStatus.swift */, + 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */, B56C1EB720EA76F500D749F9 /* Site.swift */, ); path = Model; @@ -572,6 +578,7 @@ B5BB1D0C20A2050300112D92 /* DateFormatter+Woo.swift in Sources */, B567AF2520A0CCA300AB6C62 /* AuthenticatedRequest.swift in Sources */, B505F6EA20BEFC3700BB1B69 /* MockupNetwork.swift in Sources */, + 748D4248210F89ED00CF7D1B /* OrderStatsRemote.swift in Sources */, B557DA0220975500005962F4 /* JetpackRequest.swift in Sources */, B56C1EB820EA76F500D749F9 /* Site.swift in Sources */, B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */, @@ -584,6 +591,7 @@ B557DA1820979D51005962F4 /* Credentials.swift in Sources */, CE583A0E2109154500D73C1C /* OrderNoteMapper.swift in Sources */, B5C6FCCF20A3592900A4F8E4 /* OrderItem.swift in Sources */, + 748D424A210F92EA00CF7D1B /* OrderStatItem.swift in Sources */, B505F6EC20BEFDC200BB1B69 /* Loader.swift in Sources */, B5BB1D1220A255EC00112D92 /* OrderStatus.swift in Sources */, 74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */, diff --git a/Networking/Networking/Model/OrderStatItem.swift b/Networking/Networking/Model/OrderStatItem.swift new file mode 100644 index 00000000000..6ea8c945d11 --- /dev/null +++ b/Networking/Networking/Model/OrderStatItem.swift @@ -0,0 +1,118 @@ +import Foundation + +/// Represents the granularity of a given stat +/// +public enum OrderStatGranularity: String { + case day = "day" + case week = "week" + case month = "month" + case year = "year" +} + +/// Represents an single order stat during a specific period. +/// +public struct OrderStatItem: Decodable { + public let period: String + public let orders: Int + public let products: Int + public let coupons: Int + public let couponDiscount: Int + public let totalSales: Int + public let totalTax: Int + public let totalShipping: Int + public let totalShippingTax: Int + public let totalRefund: Int + public let totalTaxRefund: Int + public let totalShippingRefund: Int + public let totalShippingTaxRefund: Int + public let currency: String + public let grossSales: Int + public let netSales: Int + public let avgOrderValue: Int + public let avgProductsPerOrder: Int + + + /// OrderStatItem struct initializer. + /// + public init(period: String, orders: Int, products: Int, coupons: Int, couponDiscount: Int, totalSales: Int, totalTax: Int, totalShipping: Int, + totalShippingTax: Int, totalRefund: Int, totalTaxRefund: Int, totalShippingRefund: Int, totalShippingTaxRefund: Int, + currency: String, grossSales: Int, netSales: Int, avgOrderValue: Int, avgProductsPerOrder: Int) { + self.period = period + self.orders = orders + self.products = products + self.coupons = coupons + self.couponDiscount = couponDiscount + self.totalSales = totalSales + self.totalTax = totalTax + self.totalShipping = totalShipping + self.totalShippingTax = totalShippingTax + self.totalRefund = totalRefund + self.totalTaxRefund = totalTaxRefund + self.totalShippingRefund = totalShippingRefund + self.totalShippingTaxRefund = totalShippingTaxRefund + self.currency = currency + self.grossSales = grossSales + self.netSales = netSales + self.avgOrderValue = avgOrderValue + self.avgProductsPerOrder = avgProductsPerOrder + } +} + + +/// Defines all of the OrderStatItem CodingKeys. +/// +private extension OrderStatItem { + + enum CodingKeys: String, CodingKey { + case period = "period" + case orders = "orders" + case products = "products" + case coupons = "coupons" + case couponDiscount = "coupon_discount" + case totalSales = "total_sales" + case totalTax = "total_tax" + case totalShipping = "total_shipping" + case totalShippingTax = "total_shipping_tax" + case totalRefund = "total_refund" + case totalTaxRefund = "total_tax_refund" + case totalShippingRefund = "total_shipping_refund" + case totalShippingTaxRefund = "total_shipping_tax_refund" + case currency = "currency" + case grossSales = "gross_sales" + case netSales = "net_sales" + case avgOrderValue = "avg_order_value" + case avgProductsPerOrder = "avg_products_per_order" + } +} + + +// MARK: - Comparable Conformance +// +extension OrderStatItem: Comparable { + public static func == (lhs: OrderStatItem, rhs: OrderStatItem) -> Bool { + return lhs.period == rhs.period && + lhs.orders == rhs.orders && + lhs.products == rhs.products && + lhs.coupons == rhs.coupons && + lhs.couponDiscount == rhs.couponDiscount && + lhs.totalSales == rhs.totalSales && + lhs.totalTax == rhs.totalTax && + lhs.totalShipping == rhs.totalShipping && + lhs.totalShippingTax == rhs.totalShippingTax && + lhs.totalRefund == rhs.totalRefund && + lhs.totalTaxRefund == rhs.totalTaxRefund && + lhs.totalShippingRefund == rhs.totalShippingRefund && + lhs.totalShippingTaxRefund == rhs.totalShippingTaxRefund && + lhs.currency == rhs.currency && + lhs.grossSales == rhs.grossSales && + lhs.netSales == rhs.netSales && + lhs.avgOrderValue == rhs.avgOrderValue && + lhs.avgProductsPerOrder == rhs.avgProductsPerOrder + } + + public static func < (lhs: OrderStatItem, rhs: OrderStatItem) -> Bool { + return lhs.period < rhs.period || + (lhs.period == rhs.period && lhs.currency < rhs.currency) || + (lhs.period == rhs.period && lhs.currency == rhs.currency && lhs.totalSales < rhs.totalSales) + } +} diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift new file mode 100644 index 00000000000..96f442ad644 --- /dev/null +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -0,0 +1,45 @@ +import Foundation +import Alamofire + + +/// OrderStats: Remote Endpoints +/// +public class OrderStatsRemote: Remote { + + /// Fetch the order stats for a given site up to the current day, month, or year (depending on the given granularity of the `unit` parameter). + /// + /// - Parameters: + /// - siteID: The site ID + /// - unit: Defines the granularity of the stats we are fetching (one of 'day', 'week', 'month', or 'year') + /// - latestDateToInclude: The latest date to include in the results. This string should match the `granularity`, e.g.: 'day':'1955-11-05', 'week':'1955-W44', 'month':'1955-11', 'year':'1955' + /// - quantity: How many `unit`s to fetch + /// - completion: Closure to be executed upon completion. + /// + public func loadOrderStats(for siteID: Int, granularity: OrderStatGranularity, latestDateToInclude: String, quantity: String, completion: @escaping ([OrderStatItem]?, Error?) -> Void) { + let path = "\(Constants.sitesPath)/\(siteID)/\(Constants.orderStatsPath)/" + let parameters = [ParameterKeys.unit: granularity.rawValue, + ParameterKeys.date: latestDateToInclude, + ParameterKeys.quantity: quantity] + let request = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .get, path: path, parameters: parameters) + + // FIXME: Add OrderStatsMapper + //let mapper = OrderStatsMapper() + //enqueue(request, mapper: mapper, completion: completion) + } +} + + +// MARK: - Constants! +// +private extension OrderStatsRemote { + enum Constants { + static let sitesPath: String = "sites" + static let orderStatsPath: String = "stats/orders" + } + + enum ParameterKeys { + static let unit: String = "unit" + static let date: String = "date" + static let quantity: String = "page" + } +} diff --git a/Networking/Networking/Settings/WordPressAPIVersion.swift b/Networking/Networking/Settings/WordPressAPIVersion.swift index 62c9788689f..eca0704f1e1 100644 --- a/Networking/Networking/Settings/WordPressAPIVersion.swift +++ b/Networking/Networking/Settings/WordPressAPIVersion.swift @@ -9,6 +9,10 @@ enum WordPressAPIVersion: String { /// case mark1_1 = "rest/v1.1/" + /// WPcom REST API Endpoint Mark 2 + /// + case wpcomMark2 = "wpcom/v2/" + /// Returns the path for the current API Version /// var path: String { diff --git a/Yosemite/Yosemite/Model/Model.swift b/Yosemite/Yosemite/Model/Model.swift index 5891a949445..08e8a48c7f0 100644 --- a/Yosemite/Yosemite/Model/Model.swift +++ b/Yosemite/Yosemite/Model/Model.swift @@ -13,4 +13,6 @@ public typealias OrderItem = Networking.OrderItem public typealias OrderStatus = Networking.OrderStatus public typealias OrderCouponLine = Networking.OrderCouponLine public typealias OrderNote = Networking.OrderNote +public typealias OrderStatItem = Networking.OrderStatItem +public typealias OrderStatGranularity = Networking.OrderStatGranularity public typealias Site = Networking.Site From 5fa9cbfcbec1085ac4515982e7bcc93028d46c23 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 30 Jul 2018 14:54:07 -0500 Subject: [PATCH 006/112] Added OrderStatsMapper --- .../Networking.xcodeproj/project.pbxproj | 4 +++ .../Networking/Mapper/OrderStatsMapper.swift | 29 +++++++++++++++++++ .../Networking/Remote/OrderStatsRemote.swift | 6 ++-- 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 Networking/Networking/Mapper/OrderStatsMapper.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index d504aa69604..67cf10aba8e 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */; }; 748D4248210F89ED00CF7D1B /* OrderStatsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */; }; 748D424A210F92EA00CF7D1B /* OrderStatItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */; }; + 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */; }; 74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */; }; 74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06520EEB76400B6EDC9 /* order-notes.json */; }; 74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */; }; @@ -82,6 +83,7 @@ 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponLine.swift; sourceTree = ""; }; 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsRemote.swift; sourceTree = ""; }; 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatItem.swift; sourceTree = ""; }; + 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsMapper.swift; sourceTree = ""; }; 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNote.swift; sourceTree = ""; }; 74C8F06520EEB76400B6EDC9 /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = ""; }; 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapper.swift; sourceTree = ""; }; @@ -347,6 +349,7 @@ B56C1EB520EA757B00D749F9 /* SiteListMapper.swift */, 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */, CE583A0D2109154500D73C1C /* OrderNoteMapper.swift */, + 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */, ); path = Mapper; sourceTree = ""; @@ -585,6 +588,7 @@ B557DA0D20975DB1005962F4 /* WordPressAPIVersion.swift in Sources */, B557DA1D20979E7D005962F4 /* Order.swift in Sources */, B557DA0320975500005962F4 /* Remote.swift in Sources */, + 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */, B567AF2920A0FA1E00AB6C62 /* Mapper.swift in Sources */, B518662220A097C200037A38 /* Network.swift in Sources */, B518662420A099BF00037A38 /* AlamofireNetwork.swift in Sources */, diff --git a/Networking/Networking/Mapper/OrderStatsMapper.swift b/Networking/Networking/Mapper/OrderStatsMapper.swift new file mode 100644 index 00000000000..bceaec019bf --- /dev/null +++ b/Networking/Networking/Mapper/OrderStatsMapper.swift @@ -0,0 +1,29 @@ +import Foundation + + +/// Mapper: OrderStats +/// +class OrderStatsMapper: Mapper { + + /// (Attempts) to convert a dictionary into [OrderStatItem]. + /// + func map(response: Data) throws -> [OrderStatItem] { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter) + + return try decoder.decode(OrderStatsEnvelope.self, from: response).orderStats + } +} + + +/// OrderStats Disposable Entity: +/// `Load Order Stats` endpoint returns all of its individual stat items within the `data` key. This entity +/// allows us to do parse all the things with JSONDecoder. +/// +private struct OrderStatsEnvelope: Decodable { + let orderStats: [OrderStatItem] + + private enum CodingKeys: String, CodingKey { + case orderStats = "data" + } +} diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift index 96f442ad644..94ff476a5c1 100644 --- a/Networking/Networking/Remote/OrderStatsRemote.swift +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -21,10 +21,8 @@ public class OrderStatsRemote: Remote { ParameterKeys.date: latestDateToInclude, ParameterKeys.quantity: quantity] let request = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .get, path: path, parameters: parameters) - - // FIXME: Add OrderStatsMapper - //let mapper = OrderStatsMapper() - //enqueue(request, mapper: mapper, completion: completion) + let mapper = OrderStatsMapper() + enqueue(request, mapper: mapper, completion: completion) } } From 16c7c80484cd2e1cdd9e23e63de8d9861029e3b8 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 30 Jul 2018 15:39:01 -0500 Subject: [PATCH 007/112] Param name fix --- Networking/Networking/Remote/OrderStatsRemote.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift index 94ff476a5c1..d061503fe9e 100644 --- a/Networking/Networking/Remote/OrderStatsRemote.swift +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -11,13 +11,13 @@ public class OrderStatsRemote: Remote { /// - Parameters: /// - siteID: The site ID /// - unit: Defines the granularity of the stats we are fetching (one of 'day', 'week', 'month', or 'year') - /// - latestDateToInclude: The latest date to include in the results. This string should match the `granularity`, e.g.: 'day':'1955-11-05', 'week':'1955-W44', 'month':'1955-11', 'year':'1955' + /// - latestDateToInclude: The latest date to include in the results. This string should match the `unit`, e.g.: 'day':'1955-11-05', 'week':'1955-W44', 'month':'1955-11', 'year':'1955' /// - quantity: How many `unit`s to fetch /// - completion: Closure to be executed upon completion. /// - public func loadOrderStats(for siteID: Int, granularity: OrderStatGranularity, latestDateToInclude: String, quantity: String, completion: @escaping ([OrderStatItem]?, Error?) -> Void) { + public func loadOrderStats(for siteID: Int, unit: OrderStatGranularity, latestDateToInclude: String, quantity: String, completion: @escaping ([OrderStatItem]?, Error?) -> Void) { let path = "\(Constants.sitesPath)/\(siteID)/\(Constants.orderStatsPath)/" - let parameters = [ParameterKeys.unit: granularity.rawValue, + let parameters = [ParameterKeys.unit: unit.rawValue, ParameterKeys.date: latestDateToInclude, ParameterKeys.quantity: quantity] let request = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .get, path: path, parameters: parameters) From e3768c0f9c21947d4d4a3b8c94fc9925dd2c0f3c Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 30 Jul 2018 16:14:02 -0500 Subject: [PATCH 008/112] WIP: OrderStatsMapper tests --- .../Networking.xcodeproj/project.pbxproj | 20 + .../Mapper/OrderStatsMapperTests.swift | 57 + .../Responses/order-stats-day.json | 5223 +++++++++++++++++ .../Responses/order-stats-month.json | 2050 +++++++ .../Responses/order-stats-week.json | 5223 +++++++++++++++++ .../Responses/order-stats-year.json | 714 +++ 6 files changed, 13287 insertions(+) create mode 100644 Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift create mode 100644 Networking/NetworkingTests/Responses/order-stats-day.json create mode 100644 Networking/NetworkingTests/Responses/order-stats-month.json create mode 100644 Networking/NetworkingTests/Responses/order-stats-week.json create mode 100644 Networking/NetworkingTests/Responses/order-stats-year.json diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 67cf10aba8e..b66f22a0c47 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -10,9 +10,14 @@ 21DB5B99C4107CF69C0A57EC /* Pods_NetworkingTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */; }; 6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; }; 741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */; }; + 743FDB9C210FB36900AC737F /* order-stats-month.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB99210FB36900AC737F /* order-stats-month.json */; }; + 743FDB9D210FB36900AC737F /* order-stats-year.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9A210FB36900AC737F /* order-stats-year.json */; }; + 743FDB9E210FB36900AC737F /* order-stats-week.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9B210FB36900AC737F /* order-stats-week.json */; }; + 743FDBA0210FB3E500AC737F /* OrderStatsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743FDB9F210FB3E500AC737F /* OrderStatsMapperTests.swift */; }; 748D4248210F89ED00CF7D1B /* OrderStatsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */; }; 748D424A210F92EA00CF7D1B /* OrderStatItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */; }; 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */; }; + 748D424E210FB1F500CF7D1B /* order-stats-day.json in Resources */ = {isa = PBXBuildFile; fileRef = 748D424D210FB1F500CF7D1B /* order-stats-day.json */; }; 74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */; }; 74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06520EEB76400B6EDC9 /* order-notes.json */; }; 74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */; }; @@ -81,9 +86,14 @@ /* Begin PBXFileReference section */ 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NetworkingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponLine.swift; sourceTree = ""; }; + 743FDB99210FB36900AC737F /* order-stats-month.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-month.json"; sourceTree = ""; }; + 743FDB9A210FB36900AC737F /* order-stats-year.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-year.json"; sourceTree = ""; }; + 743FDB9B210FB36900AC737F /* order-stats-week.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-week.json"; sourceTree = ""; }; + 743FDB9F210FB3E500AC737F /* OrderStatsMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatsMapperTests.swift; sourceTree = ""; }; 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsRemote.swift; sourceTree = ""; }; 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatItem.swift; sourceTree = ""; }; 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsMapper.swift; sourceTree = ""; }; + 748D424D210FB1F500CF7D1B /* order-stats-day.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-day.json"; sourceTree = ""; }; 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNote.swift; sourceTree = ""; }; 74C8F06520EEB76400B6EDC9 /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = ""; }; 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapper.swift; sourceTree = ""; }; @@ -326,6 +336,10 @@ B559EBA820A0B5B100836CD4 /* Responses */ = { isa = PBXGroup; children = ( + 748D424D210FB1F500CF7D1B /* order-stats-day.json */, + 743FDB99210FB36900AC737F /* order-stats-month.json */, + 743FDB9B210FB36900AC737F /* order-stats-week.json */, + 743FDB9A210FB36900AC737F /* order-stats-year.json */, CE21B3E62106811000A259D5 /* new-order-note.json */, B505F6D420BEE4E600BB1B69 /* me.json */, B559EBA920A0B5CD00836CD4 /* orders-load-all.json */, @@ -378,6 +392,7 @@ B5C6FCCC20A34B8300A4F8E4 /* OrderListMapperTests.swift */, 74C8F06920EEBC8C00B6EDC9 /* OrderMapperTests.swift */, 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */, + 743FDB9F210FB3E500AC737F /* OrderStatsMapperTests.swift */, ); path = Mapper; sourceTree = ""; @@ -500,6 +515,10 @@ CE21B3E72106811000A259D5 /* new-order-note.json in Resources */, B505F6D520BEE4E700BB1B69 /* me.json in Resources */, B5C6FCD620A3768900A4F8E4 /* order.json in Resources */, + 743FDB9C210FB36900AC737F /* order-stats-month.json in Resources */, + 748D424E210FB1F500CF7D1B /* order-stats-day.json in Resources */, + 743FDB9E210FB36900AC737F /* order-stats-week.json in Resources */, + 743FDB9D210FB36900AC737F /* order-stats-year.json in Resources */, B559EBAA20A0B5CD00836CD4 /* orders-load-all.json in Resources */, B56C1EBA20EA7D2C00D749F9 /* sites.json in Resources */, CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */, @@ -626,6 +645,7 @@ B5C6FCCD20A34B8300A4F8E4 /* OrderListMapperTests.swift in Sources */, B518663520A0A2E800037A38 /* Constants.swift in Sources */, B567AF3020A0FB8F00AB6C62 /* DotcomRequestTests.swift in Sources */, + 743FDBA0210FB3E500AC737F /* OrderStatsMapperTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift new file mode 100644 index 00000000000..c4b17853e9f --- /dev/null +++ b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift @@ -0,0 +1,57 @@ +import XCTest +@testable import Networking + + +/// OrderStatsMapper Unit Tests +/// +class OrderStatsMapperTests: XCTestCase { + + /// Verifies that all of the OrderStatsItem Fields are parsed correctly. + /// + func testStatFieldsAreProperlyParsed() { +// let dayStatItems = mapOrderStatsWithDayUnitResponse() +// XCTAssertEqual(dayStatItems.count, 31) +// let testStat = dayStatItems[26] +// XCTAssertEqual(testStat.period, "2018-06-01") + } +} + + +/// Private Methods. +/// +private extension OrderStatsMapperTests { + + /// Returns the OrderNotesMapper output upon receiving `filename` (Data Encoded) + /// + func mapStatItems(from filename: String) -> [OrderStatItem] { + guard let response = Loader.contentsOf(filename) else { + return [] + } + + return try! OrderStatsMapper().map(response: response) + } + + /// Returns the OrderStatsMapper output upon receiving `order-stats-day` + /// + func mapOrderStatsWithDayUnitResponse() -> [OrderStatItem] { + return mapStatItems(from: "order-stats-day") + } + + /// Returns the OrderStatsMapper output upon receiving `order-stats-week` + /// + func mapOrderStatsWithWeekUnitResponse() -> [OrderStatItem] { + return mapStatItems(from: "order-stats-week") + } + + /// Returns the OrderStatsMapper output upon receiving `order-stats-month` + /// + func mapOrderStatsWithMonthUnitResponse() -> [OrderStatItem] { + return mapStatItems(from: "order-stats-month") + } + + /// Returns the OrderStatsMapper output upon receiving `order-stats-year` + /// + func mapOrderStatsWithYearUnitResponse() -> [OrderStatItem] { + return mapStatItems(from: "order-stats-year") + } +} diff --git a/Networking/NetworkingTests/Responses/order-stats-day.json b/Networking/NetworkingTests/Responses/order-stats-day.json new file mode 100644 index 00000000000..7000a5cbc5b --- /dev/null +++ b/Networking/NetworkingTests/Responses/order-stats-day.json @@ -0,0 +1,5223 @@ +{ + "date": "2018-06-08", + "unit": "day", + "quantity": "31", + "fields": [ + "period", + "orders", + "products", + "coupons", + "coupon_discount", + "total_sales", + "total_tax", + "total_shipping", + "total_shipping_tax", + "total_refund", + "total_tax_refund", + "total_shipping_refund", + "total_shipping_tax_refund", + "currency", + "gross_sales", + "net_sales", + "avg_order_value", + "avg_products_per_order" + ], + "data": [ + [ + "2018-05-09", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-10", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-11", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-12", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-13", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-14", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-15", + 1, + 1, + 0, + 0, + 20, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 20, + 20, + 20, + 1 + ], + [ + "2018-05-16", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-17", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-18", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-19", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-20", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-21", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-22", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-23", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-24", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-25", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-26", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-27", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-28", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-29", + 2, + 2, + 0, + 0, + 80, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 80, + 80, + 40, + 1 + ], + [ + "2018-05-30", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-05-31", + 1, + 5, + 0, + 0, + 150, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 150, + 150, + 150, + 5 + ], + [ + "2018-06-01", + 2, + 2, + 0, + 0, + 14.24, + 0.12, + 9.98, + 0.28, + 0, + 0, + 0, + 0, + "USD", + 14.24, + 14.120000000000001, + 7.12, + 1 + ], + [ + "2018-06-02", + 1, + 1, + 0, + 0, + 30.87, + 0.87, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 30.87, + 30, + 30.87, + 1 + ], + [ + "2018-06-03", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-06-04", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-06-05", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2.06, + 0.06, + 0, + 0, + "USD", + -2.06, + -2.06, + 0, + 0 + ], + [ + "2018-06-06", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-06-07", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-06-08", + 2, + 2, + 0, + 0, + 146.18, + 0, + 86.18, + 0, + 0, + 0, + 0, + 0, + "USD", + 146.18, + 146.18, + 73.09, + 1 + ] + ], + "delta_fields": [ + "period", + "delta", + "percentage_change", + "reference_period", + "favorable", + "direction", + "currency" + ], + "deltas": [ + { + "period": "2018-05-09", + "orders": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-09", + 0, + 0, + "2018-05-08", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-10", + "orders": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-10", + 0, + 0, + "2018-05-09", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-11", + "orders": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-11", + 0, + 0, + "2018-05-10", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-12", + "orders": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-12", + 0, + 0, + "2018-05-11", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-13", + "orders": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-13", + 0, + 0, + "2018-05-12", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-14", + "orders": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-14", + 0, + 0, + "2018-05-13", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-15", + "orders": [ + "2018-05-15", + 1, + 0, + "2018-05-14", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-05-15", + 1, + 0, + "2018-05-14", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-15", + 20, + 0, + "2018-05-14", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-15", + 0, + 0, + "2018-05-14", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-15", + 20, + 0, + "2018-05-14", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-05-15", + 20, + 0, + "2018-05-14", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-05-15", + 20, + 0, + "2018-05-14", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-05-15", + 1, + 0, + "2018-05-14", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-05-16", + "orders": [ + "2018-05-16", + -1, + -1, + "2018-05-15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-05-16", + -1, + -1, + "2018-05-15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-16", + -20, + -1, + "2018-05-15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-16", + 0, + 0, + "2018-05-15", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-16", + -20, + -1, + "2018-05-15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-05-16", + -20, + -1, + "2018-05-15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-05-16", + -20, + -1, + "2018-05-15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-05-16", + -1, + -1, + "2018-05-15", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-05-17", + "orders": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-17", + 0, + 0, + "2018-05-16", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-18", + "orders": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-18", + 0, + 0, + "2018-05-17", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-19", + "orders": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-19", + 0, + 0, + "2018-05-18", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-20", + "orders": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-20", + 0, + 0, + "2018-05-19", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-21", + "orders": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-21", + 0, + 0, + "2018-05-20", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-22", + "orders": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-22", + 0, + 0, + "2018-05-21", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-23", + "orders": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-23", + 0, + 0, + "2018-05-22", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-24", + "orders": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-24", + 0, + 0, + "2018-05-23", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-25", + "orders": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-25", + 0, + 0, + "2018-05-24", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-26", + "orders": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-26", + 0, + 0, + "2018-05-25", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-27", + "orders": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-27", + 0, + 0, + "2018-05-26", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-28", + "orders": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-05-28", + 0, + 0, + "2018-05-27", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-05-29", + "orders": [ + "2018-05-29", + 2, + 0, + "2018-05-28", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-05-29", + 2, + 0, + "2018-05-28", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-29", + 80, + 0, + "2018-05-28", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-29", + 0, + 0, + "2018-05-28", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-29", + 80, + 0, + "2018-05-28", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-05-29", + 80, + 0, + "2018-05-28", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-05-29", + 40, + 0, + "2018-05-28", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-05-29", + 1, + 0, + "2018-05-28", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-05-30", + "orders": [ + "2018-05-30", + -2, + -1, + "2018-05-29", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-05-30", + -2, + -1, + "2018-05-29", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-30", + -80, + -1, + "2018-05-29", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-30", + 0, + 0, + "2018-05-29", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-30", + -80, + -1, + "2018-05-29", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-05-30", + -80, + -1, + "2018-05-29", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-05-30", + -40, + -1, + "2018-05-29", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-05-30", + -1, + -1, + "2018-05-29", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-05-31", + "orders": [ + "2018-05-31", + 1, + 0, + "2018-05-30", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-05-31", + 5, + 0, + "2018-05-30", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-05-31", + 150, + 0, + "2018-05-30", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05-31", + 0, + 0, + "2018-05-30", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05-31", + 150, + 0, + "2018-05-30", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-05-31", + 150, + 0, + "2018-05-30", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-05-31", + 150, + 0, + "2018-05-30", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-05-31", + 5, + 0, + "2018-05-30", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-06-01", + "orders": [ + "2018-06-01", + 1, + 1, + "2018-05-31", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-06-01", + -3, + -0.6, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-01", + -135.76, + -0.9051, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-06-01", + 0.12, + 0, + "2018-05-31", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-06-01", + 9.98, + 0, + "2018-05-31", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-06-01", + 0.28, + 0, + "2018-05-31", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_refund": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-01", + -135.76, + -0.9051, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-06-01", + -135.88, + -0.9059, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-06-01", + -142.88, + -0.9525, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-06-01", + -4, + -0.8, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-06-02", + "orders": [ + "2018-06-02", + -1, + -0.5, + "2018-06-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-06-02", + -1, + -0.5, + "2018-06-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-02", + 16.630000000000003, + 1.1678, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-06-02", + 0.75, + 6.25, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping": [ + "2018-06-02", + -9.98, + -1, + "2018-06-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-06-02", + -0.28, + -1, + "2018-06-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-02", + 16.630000000000003, + 1.1678, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-06-02", + 15.879999999999999, + 1.1246, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-06-02", + 23.75, + 3.3357, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-06-03", + "orders": [ + "2018-06-03", + -1, + -1, + "2018-06-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-06-03", + -1, + -1, + "2018-06-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-06-03", + 0, + 0, + "2018-06-02", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-03", + 0, + 0, + "2018-06-02", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-03", + -30.87, + -1, + "2018-06-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-06-03", + -0.87, + -1, + "2018-06-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2018-06-03", + 0, + 0, + "2018-06-02", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-06-03", + 0, + 0, + "2018-06-02", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-06-03", + 0, + 0, + "2018-06-02", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-06-03", + 0, + 0, + "2018-06-02", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-06-03", + 0, + 0, + "2018-06-02", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-03", + 0, + 0, + "2018-06-02", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-03", + -30.87, + -1, + "2018-06-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-06-03", + -30, + -1, + "2018-06-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-06-03", + -30.87, + -1, + "2018-06-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-06-03", + -1, + -1, + "2018-06-02", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-06-04", + "orders": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-06-04", + 0, + 0, + "2018-06-03", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-06-05", + "orders": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-06-05", + 2.06, + 0, + "2018-06-04", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_tax_refund": [ + "2018-06-05", + 0.06, + 0, + "2018-06-04", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_refund": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-05", + -2.06, + 0, + "2018-06-04", + "is-unfavorable", + "is-undefined-decrease", + "USD" + ], + "net_sales": [ + "2018-06-05", + -2.06, + 0, + "2018-06-04", + "is-unfavorable", + "is-undefined-decrease", + "USD" + ], + "avg_order_value": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-06-05", + 0, + 0, + "2018-06-04", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-06-06", + "orders": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-06-06", + -2.06, + -1, + "2018-06-05", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2018-06-06", + -0.06, + -1, + "2018-06-05", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_refund": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-06", + 2.06, + -1, + "2018-06-05", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-06-06", + 2.06, + -1, + "2018-06-05", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-06-06", + 0, + 0, + "2018-06-05", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-06-07", + "orders": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-06-07", + 0, + 0, + "2018-06-06", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-06-08", + "orders": [ + "2018-06-08", + 2, + 0, + "2018-06-07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-06-08", + 2, + 0, + "2018-06-07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-06-08", + 0, + 0, + "2018-06-07", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-08", + 0, + 0, + "2018-06-07", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-08", + 146.18, + 0, + "2018-06-07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-06-08", + 0, + 0, + "2018-06-07", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-06-08", + 86.18, + 0, + "2018-06-07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-06-08", + 0, + 0, + "2018-06-07", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-06-08", + 0, + 0, + "2018-06-07", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-06-08", + 0, + 0, + "2018-06-07", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-06-08", + 0, + 0, + "2018-06-07", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-08", + 0, + 0, + "2018-06-07", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-08", + 146.18, + 0, + "2018-06-07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-06-08", + 146.18, + 0, + "2018-06-07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-06-08", + 73.09, + 0, + "2018-06-07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-06-08", + 1, + 0, + "2018-06-07", + "is-favorable", + "is-undefined-increase", + "USD" + ] + } + ], + "total_gross_sales": 439.23, + "total_net_sales": 438.24, + "total_orders": 9, + "total_products": 13, + "avg_gross_sales": 14.1687, + "avg_net_sales": 14.1368, + "avg_orders": 0.2903, + "avg_products": 0.4194 +} \ No newline at end of file diff --git a/Networking/NetworkingTests/Responses/order-stats-month.json b/Networking/NetworkingTests/Responses/order-stats-month.json new file mode 100644 index 00000000000..5713f14a6b1 --- /dev/null +++ b/Networking/NetworkingTests/Responses/order-stats-month.json @@ -0,0 +1,2050 @@ +{ + "date": "2018-06", + "unit": "month", + "quantity": "12", + "fields": [ + "period", + "orders", + "products", + "coupons", + "coupon_discount", + "total_sales", + "total_tax", + "total_shipping", + "total_shipping_tax", + "total_refund", + "total_tax_refund", + "total_shipping_refund", + "total_shipping_tax_refund", + "currency", + "gross_sales", + "net_sales", + "avg_order_value", + "avg_products_per_order" + ], + "data": [ + [ + "2017-07", + 49, + 74, + 1, + 7.5, + 1602.2500000000014, + 50.78999999999998, + 117.31, + 4.200800000000001, + 0, + 0, + 0, + 0, + "USD", + 1602.2500000000014, + 1551.4600000000014, + 32.699, + 1.5102 + ], + [ + "2017-08", + 19, + 43, + 2, + 54, + 3355.8500000000004, + 4.92, + 658.3399999999999, + 3.5876, + 183.77, + 6.99, + 61.7, + 3.9859999999999998, + "USD", + 3172.0800000000004, + 3167.1600000000003, + 166.9516, + 2.2632 + ], + [ + "2017-09", + 6, + 11, + 1, + 7.4, + 336.94, + 16.928, + 100.38, + 8.0304, + 223.71, + 0, + 43.71, + 0, + "USD", + 113.22999999999999, + 96.30199999999999, + 18.8717, + 1.8333 + ], + [ + "2017-10", + 9, + 9, + 0, + 0, + 393.64, + 20, + 68.18, + 5.4548, + 10.8, + 0, + 0, + 0, + "USD", + 382.84, + 362.84, + 42.5378, + 1 + ], + [ + "2017-11", + 6, + 9, + 2, + 60, + 268.24, + 7.24, + 0, + 0, + 1250.14, + 4.72, + 87.42, + 0, + "USD", + -981.9000000000001, + -989.1400000000001, + -163.65, + 1.5 + ], + [ + "2017-12", + 9, + 15, + 0, + 0, + 181.20000000000002, + 5.8, + 10, + 0.4, + 44.72, + 0, + 0, + 0, + "USD", + 136.48000000000002, + 130.68, + 15.1644, + 1.6667 + ], + [ + "2018-01", + 5, + 9, + 0, + 0, + 310.83000000000004, + 0, + 40.83, + 0, + 160, + 0, + 0, + 0, + "USD", + 150.83000000000004, + 150.83000000000004, + 30.166, + 1.8 + ], + [ + "2018-02", + 6, + 9, + 2, + 60, + 238.88, + 0, + 23.88, + 0, + 0, + 0, + 0, + 0, + "USD", + 238.88, + 238.88, + 39.8133, + 1.5 + ], + [ + "2018-03", + 13, + 16, + 1, + 50, + 737.6800000000001, + 3.2, + 149.48000000000002, + 0, + 92, + 0, + 7, + 0, + "USD", + 645.6800000000001, + 642.48, + 49.6677, + 1.2308 + ], + [ + "2018-04", + 23, + 28, + 1, + 30, + 674.7, + 3.2, + 4, + 0, + 64, + 0, + 4, + 0, + "USD", + 610.7, + 607.5, + 26.5522, + 1.2174 + ], + [ + "2018-05", + 4, + 8, + 0, + 0, + 250, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 250, + 250, + 62.5, + 2 + ], + [ + "2018-06", + 10, + 12, + 0, + 0, + 511.58000000000004, + 1.28, + 96.16000000000001, + 0.28, + 2.06, + 0.06, + 0, + 0, + "USD", + 509.52000000000004, + 508.24000000000007, + 50.952, + 1.2 + ] + ], + "delta_fields": [ + "period", + "delta", + "percentage_change", + "reference_period", + "favorable", + "direction", + "currency" + ], + "deltas": [ + { + "period": "2017-07", + "orders": [ + "2017-07", + 45, + 11.25, + "2017-06", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2017-07", + 70, + 17.5, + "2017-06", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2017-07", + 1, + 0, + "2017-06", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupon_discount": [ + "2017-07", + 7.5, + 0, + "2017-06", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_sales": [ + "2017-07", + 1416.5300000000013, + 7.6272, + "2017-06", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2017-07", + 50.78999999999998, + 0, + "2017-06", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2017-07", + -64.41, + -0.3544, + "2017-06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2017-07", + 4.200800000000001, + 0, + "2017-06", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_refund": [ + "2017-07", + 0, + 0, + "2017-06", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2017-07", + 0, + 0, + "2017-06", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2017-07", + 0, + 0, + "2017-06", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2017-07", + 0, + 0, + "2017-06", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2017-07", + 1416.5300000000013, + 7.6272, + "2017-06", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2017-07", + 1365.7400000000014, + 7.3538, + "2017-06", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2017-07", + -13.731000000000002, + -0.2957, + "2017-06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2017-07", + 0.5102, + 0.5102, + "2017-06", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2017-08", + "orders": [ + "2017-08", + -30, + -0.6122, + "2017-07", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2017-08", + -31, + -0.4189, + "2017-07", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2017-08", + 1, + 1, + "2017-07", + "is-favorable", + "is-increase", + "USD" + ], + "coupon_discount": [ + "2017-08", + 46.5, + 6.2, + "2017-07", + "is-favorable", + "is-increase", + "USD" + ], + "total_sales": [ + "2017-08", + 1753.599999999999, + 1.0945, + "2017-07", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2017-08", + -45.869999999999976, + -0.9031, + "2017-07", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2017-08", + 541.03, + 4.612, + "2017-07", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping_tax": [ + "2017-08", + -0.6132000000000009, + -0.146, + "2017-07", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + "2017-08", + 183.77, + 0, + "2017-07", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_tax_refund": [ + "2017-08", + 6.99, + 0, + "2017-07", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_refund": [ + "2017-08", + 61.7, + 0, + "2017-07", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax_refund": [ + "2017-08", + 3.9859999999999998, + 0, + "2017-07", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "gross_sales": [ + "2017-08", + 1569.829999999999, + 0.9798, + "2017-07", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2017-08", + 1615.699999999999, + 1.0414, + "2017-07", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2017-08", + 134.25260000000003, + 4.1057, + "2017-07", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2017-08", + 0.7529999999999999, + 0.4986, + "2017-07", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2017-09", + "orders": [ + "2017-09", + -13, + -0.6842, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2017-09", + -32, + -0.7442, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2017-09", + -1, + -0.5, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + "2017-09", + -46.6, + -0.863, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2017-09", + -3018.9100000000003, + -0.8996, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2017-09", + 12.008000000000001, + 2.4407, + "2017-08", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping": [ + "2017-09", + -557.9599999999999, + -0.8475, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2017-09", + 4.4428, + 1.2384, + "2017-08", + "is-favorable", + "is-increase", + "USD" + ], + "total_refund": [ + "2017-09", + 39.94, + 0.2173, + "2017-08", + "is-unfavorable", + "is-increase", + "USD" + ], + "total_tax_refund": [ + "2017-09", + -6.99, + -1, + "2017-08", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_refund": [ + "2017-09", + -17.990000000000002, + -0.2916, + "2017-08", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_tax_refund": [ + "2017-09", + -3.9859999999999998, + -1, + "2017-08", + "is-favorable", + "is-decrease", + "USD" + ], + "gross_sales": [ + "2017-09", + -3058.8500000000004, + -0.9643, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2017-09", + -3070.858, + -0.9696, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2017-09", + -148.0799, + -0.887, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2017-09", + -0.42989999999999995, + -0.19, + "2017-08", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2017-10", + "orders": [ + "2017-10", + 3, + 0.5, + "2017-09", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2017-10", + -2, + -0.1818, + "2017-09", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2017-10", + -1, + -1, + "2017-09", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + "2017-10", + -7.4, + -1, + "2017-09", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2017-10", + 56.69999999999999, + 0.1683, + "2017-09", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2017-10", + 3.071999999999999, + 0.1815, + "2017-09", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping": [ + "2017-10", + -32.19999999999999, + -0.3208, + "2017-09", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2017-10", + -2.5756000000000006, + -0.3207, + "2017-09", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + "2017-10", + -212.91, + -0.9517, + "2017-09", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2017-10", + 0, + 0, + "2017-09", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2017-10", + -43.71, + -1, + "2017-09", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_tax_refund": [ + "2017-10", + 0, + 0, + "2017-09", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2017-10", + 269.61, + 2.3811, + "2017-09", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2017-10", + 266.538, + 2.7677, + "2017-09", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2017-10", + 23.666099999999997, + 1.2541, + "2017-09", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2017-10", + -0.8332999999999999, + -0.4545, + "2017-09", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2017-11", + "orders": [ + "2017-11", + -3, + -0.3333, + "2017-10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2017-11", + 0, + 0, + "2017-10", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2017-11", + 2, + 0, + "2017-10", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupon_discount": [ + "2017-11", + 60, + 0, + "2017-10", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_sales": [ + "2017-11", + -125.39999999999998, + -0.3186, + "2017-10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2017-11", + -12.76, + -0.638, + "2017-10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2017-11", + -68.18, + -1, + "2017-10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2017-11", + -5.4548, + -1, + "2017-10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + "2017-11", + 1239.3400000000001, + 114.7537, + "2017-10", + "is-unfavorable", + "is-increase", + "USD" + ], + "total_tax_refund": [ + "2017-11", + 4.72, + 0, + "2017-10", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_refund": [ + "2017-11", + 87.42, + 0, + "2017-10", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax_refund": [ + "2017-11", + 0, + 0, + "2017-10", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2017-11", + -1364.74, + -3.5648, + "2017-10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2017-11", + -1351.98, + -3.7261, + "2017-10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2017-11", + -206.1878, + -4.8472, + "2017-10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2017-11", + 0.5, + 0.5, + "2017-10", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2017-12", + "orders": [ + "2017-12", + 3, + 0.5, + "2017-11", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2017-12", + 6, + 0.6667, + "2017-11", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2017-12", + -2, + -1, + "2017-11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + "2017-12", + -60, + -1, + "2017-11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2017-12", + -87.03999999999999, + -0.3245, + "2017-11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2017-12", + -1.4400000000000004, + -0.1989, + "2017-11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2017-12", + 10, + 0, + "2017-11", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2017-12", + 0.4, + 0, + "2017-11", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_refund": [ + "2017-12", + -1205.42, + -0.9642, + "2017-11", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2017-12", + -4.72, + -1, + "2017-11", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_refund": [ + "2017-12", + -87.42, + -1, + "2017-11", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_tax_refund": [ + "2017-12", + 0, + 0, + "2017-11", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2017-12", + 1118.38, + -1.139, + "2017-11", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2017-12", + 1119.8200000000002, + -1.1321, + "2017-11", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2017-12", + 178.8144, + -1.0927, + "2017-11", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2017-12", + 0.16670000000000007, + 0.1111, + "2017-11", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2018-01", + "orders": [ + "2018-01", + -4, + -0.4444, + "2017-12", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-01", + -6, + -0.4, + "2017-12", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-01", + 0, + 0, + "2017-12", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-01", + 0, + 0, + "2017-12", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-01", + 129.63000000000002, + 0.7154, + "2017-12", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-01", + -5.8, + -1, + "2017-12", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2018-01", + 30.83, + 3.083, + "2017-12", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-01", + -0.4, + -1, + "2017-12", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + "2018-01", + 115.28, + 2.5778, + "2017-12", + "is-unfavorable", + "is-increase", + "USD" + ], + "total_tax_refund": [ + "2018-01", + 0, + 0, + "2017-12", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-01", + 0, + 0, + "2017-12", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-01", + 0, + 0, + "2017-12", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-01", + 14.350000000000023, + 0.1051, + "2017-12", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-01", + 20.150000000000034, + 0.1542, + "2017-12", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-01", + 15.0016, + 0.9893, + "2017-12", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-01", + 0.13329999999999997, + 0.08, + "2017-12", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2018-02", + "orders": [ + "2018-02", + 1, + 0.2, + "2018-01", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-02", + 0, + 0, + "2018-01", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-02", + 2, + 0, + "2018-01", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupon_discount": [ + "2018-02", + 60, + 0, + "2018-01", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_sales": [ + "2018-02", + -71.95000000000005, + -0.2315, + "2018-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-02", + 0, + 0, + "2018-01", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-02", + -16.95, + -0.4151, + "2018-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-02", + 0, + 0, + "2018-01", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-02", + -160, + -1, + "2018-01", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2018-02", + 0, + 0, + "2018-01", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-02", + 0, + 0, + "2018-01", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-02", + 0, + 0, + "2018-01", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-02", + 88.04999999999995, + 0.5838, + "2018-01", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-02", + 88.04999999999995, + 0.5838, + "2018-01", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-02", + 9.647299999999998, + 0.3198, + "2018-01", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-02", + -0.30000000000000004, + -0.1667, + "2018-01", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-03", + "orders": [ + "2018-03", + 7, + 1.1667, + "2018-02", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-03", + 7, + 0.7778, + "2018-02", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-03", + -1, + -0.5, + "2018-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + "2018-03", + -10, + -0.1667, + "2018-02", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2018-03", + 498.80000000000007, + 2.0881, + "2018-02", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-03", + 3.2, + 0, + "2018-02", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-03", + 125.60000000000002, + 5.2596, + "2018-02", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-03", + 0, + 0, + "2018-02", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-03", + 92, + 0, + "2018-02", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_tax_refund": [ + "2018-03", + 0, + 0, + "2018-02", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-03", + 7, + 0, + "2018-02", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-03", + 0, + 0, + "2018-02", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-03", + 406.80000000000007, + 1.7029, + "2018-02", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-03", + 403.6, + 1.6896, + "2018-02", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-03", + 9.854400000000005, + 0.2475, + "2018-02", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-03", + -0.2692000000000001, + -0.1795, + "2018-02", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-04", + "orders": [ + "2018-04", + 10, + 0.7692, + "2018-03", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-04", + 12, + 0.75, + "2018-03", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-04", + 0, + 0, + "2018-03", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-04", + -20, + -0.4, + "2018-03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2018-04", + -62.98000000000002, + -0.0854, + "2018-03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-04", + 0, + 0, + "2018-03", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-04", + -145.48000000000002, + -0.9732, + "2018-03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-04", + 0, + 0, + "2018-03", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-04", + -28, + -0.3043, + "2018-03", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2018-04", + 0, + 0, + "2018-03", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-04", + -3, + -0.4286, + "2018-03", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-04", + 0, + 0, + "2018-03", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-04", + -34.98000000000002, + -0.0542, + "2018-03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-04", + -34.98000000000002, + -0.0544, + "2018-03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-04", + -23.115500000000004, + -0.4654, + "2018-03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-04", + -0.013399999999999856, + -0.0109, + "2018-03", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-05", + "orders": [ + "2018-05", + -19, + -0.8261, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-05", + -20, + -0.7143, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-05", + -1, + -1, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + "2018-05", + -30, + -1, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2018-05", + -424.70000000000005, + -0.6295, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-05", + -3.2, + -1, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2018-05", + -4, + -1, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-05", + 0, + 0, + "2018-04", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-05", + -64, + -1, + "2018-04", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2018-05", + 0, + 0, + "2018-04", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-05", + -4, + -1, + "2018-04", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-05", + 0, + 0, + "2018-04", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-05", + -360.70000000000005, + -0.5906, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-05", + -357.5, + -0.5885, + "2018-04", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-05", + 35.9478, + 1.3539, + "2018-04", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-05", + 0.7826, + 0.6428, + "2018-04", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2018-06", + "orders": [ + "2018-06", + 6, + 1.5, + "2018-05", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-06", + 4, + 0.5, + "2018-05", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-06", + 0, + 0, + "2018-05", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06", + 0, + 0, + "2018-05", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06", + 261.58000000000004, + 1.0463, + "2018-05", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-06", + 1.28, + 0, + "2018-05", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-06", + 96.16000000000001, + 0, + "2018-05", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-06", + 0.28, + 0, + "2018-05", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_refund": [ + "2018-06", + 2.06, + 0, + "2018-05", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_tax_refund": [ + "2018-06", + 0.06, + 0, + "2018-05", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_refund": [ + "2018-06", + 0, + 0, + "2018-05", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06", + 0, + 0, + "2018-05", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06", + 259.52000000000004, + 1.0381, + "2018-05", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-06", + 258.24000000000007, + 1.033, + "2018-05", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-06", + -11.548000000000002, + -0.1848, + "2018-05", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-06", + -0.8, + -0.4, + "2018-05", + "is-unfavorable", + "is-decrease", + "USD" + ] + } + ], + "total_gross_sales": 6830.590000000002, + "total_net_sales": 6717.232000000002, + "total_orders": 159, + "total_products": 243, + "avg_gross_sales": 569.2158, + "avg_net_sales": 559.7693, + "avg_orders": 13.25, + "avg_products": 20.25 +} \ No newline at end of file diff --git a/Networking/NetworkingTests/Responses/order-stats-week.json b/Networking/NetworkingTests/Responses/order-stats-week.json new file mode 100644 index 00000000000..d15db1844a1 --- /dev/null +++ b/Networking/NetworkingTests/Responses/order-stats-week.json @@ -0,0 +1,5223 @@ +{ + "date": "2018-W30", + "unit": "week", + "quantity": "31", + "fields": [ + "period", + "orders", + "products", + "coupons", + "coupon_discount", + "total_sales", + "total_tax", + "total_shipping", + "total_shipping_tax", + "total_refund", + "total_tax_refund", + "total_shipping_refund", + "total_shipping_tax_refund", + "currency", + "gross_sales", + "net_sales", + "avg_order_value", + "avg_products_per_order" + ], + "data": [ + [ + "2017-W52", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-W01", + 2, + 4, + 0, + 0, + 160, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 160, + 160, + 80, + 2 + ], + [ + "2018-W02", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 160, + 0, + 0, + 0, + "USD", + -160, + -160, + 0, + 0 + ], + [ + "2018-W03", + 2, + 4, + 0, + 0, + 133.23, + 0, + 33.23, + 0, + 0, + 0, + 0, + 0, + "USD", + 133.23, + 133.23, + 66.615, + 2 + ], + [ + "2018-W04", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-W05", + 2, + 2, + 0, + 0, + 67.6, + 0, + 7.6, + 0, + 0, + 0, + 0, + 0, + "USD", + 67.6, + 67.6, + 33.8, + 1 + ], + [ + "2018-W06", + 1, + 2, + 2, + 60, + 7.59, + 0, + 7.59, + 0, + 0, + 0, + 0, + 0, + "USD", + 7.59, + 7.59, + 7.59, + 2 + ], + [ + "2018-W07", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-W08", + 3, + 5, + 0, + 0, + 161.29000000000002, + 0, + 16.29, + 0, + 0, + 0, + 0, + 0, + "USD", + 161.29000000000002, + 161.29000000000002, + 53.7633, + 1.6667 + ], + [ + "2018-W09", + 2, + 3, + 0, + 0, + 80, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 80, + 80, + 40, + 1.5 + ], + [ + "2018-W10", + 2, + 2, + 0, + 0, + 195.95, + 0, + 75.95, + 0, + 0, + 0, + 0, + 0, + "USD", + 195.95, + 195.95, + 97.975, + 1 + ], + [ + "2018-W11", + 5, + 5, + 0, + 0, + 258.2, + 3.2, + 45, + 0, + 0, + 0, + 0, + 0, + "USD", + 258.2, + 255, + 51.64, + 1 + ], + [ + "2018-W12", + 1, + 1, + 1, + 50, + 7.2, + 0, + 7.2, + 0, + 0, + 0, + 0, + 0, + "USD", + 7.2, + 7.2, + 7.2, + 1 + ], + [ + "2018-W13", + 4, + 6, + 0, + 0, + 216.32999999999998, + 0, + 21.33, + 0, + 92, + 0, + 7, + 0, + "USD", + 124.32999999999998, + 124.32999999999998, + 31.0825, + 1.5 + ], + [ + "2018-W14", + 6, + 8, + 1, + 30, + 155.2, + 1.2, + 4, + 0, + 20, + 0, + 0, + 0, + "USD", + 135.2, + 134, + 22.5333, + 1.3333 + ], + [ + "2018-W15", + 9, + 11, + 0, + 0, + 225, + 0, + 0, + 0, + 44, + 0, + 4, + 0, + "USD", + 181, + 181, + 20.1111, + 1.2222 + ], + [ + "2018-W16", + 4, + 5, + 0, + 0, + 182.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 182.5, + 182.5, + 45.625, + 1.25 + ], + [ + "2018-W17", + 3, + 3, + 0, + 0, + 60, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 60, + 60, + 20, + 1 + ], + [ + "2018-W18", + 1, + 1, + 0, + 0, + 52, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 52, + 50, + 52, + 1 + ], + [ + "2018-W19", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-W20", + 1, + 1, + 0, + 0, + 20, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 20, + 20, + 20, + 1 + ], + [ + "2018-W21", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-W22", + 6, + 10, + 0, + 0, + 275.11, + 0.99, + 9.98, + 0.28, + 0, + 0, + 0, + 0, + "USD", + 275.11, + 274.12, + 45.8517, + 1.6667 + ], + [ + "2018-W23", + 2, + 2, + 0, + 0, + 146.18, + 0, + 86.18, + 0, + 2.06, + 0.06, + 0, + 0, + "USD", + 144.12, + 144.12, + 72.06, + 1 + ], + [ + "2018-W24", + 2, + 3, + 0, + 0, + 70, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 70, + 70, + 35, + 1.5 + ], + [ + "2018-W25", + 2, + 3, + 0, + 0, + 240, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 240, + 240, + 120, + 1.5 + ], + [ + "2018-W26", + 1, + 1, + 0, + 0, + 10.29, + 0.29, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 10.29, + 10, + 10.29, + 1 + ], + [ + "2018-W27", + 3, + 3, + 0, + 0, + 134.66, + 7.08, + 26.3, + 1.28, + 0, + 0, + 0, + 0, + "USD", + 134.66, + 127.58, + 44.8867, + 1 + ], + [ + "2018-W28", + 1, + 2, + 0, + 0, + 318.25, + 10.21, + 177.3, + 15.74, + 0, + 0, + 0, + 0, + "USD", + 318.25, + 308.04, + 318.25, + 2 + ], + [ + "2018-W29", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + "2018-W30", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ] + ], + "delta_fields": [ + "period", + "delta", + "percentage_change", + "reference_period", + "favorable", + "direction", + "currency" + ], + "deltas": [ + { + "period": "2017-W52", + "orders": [ + "2017-W52", + -3, + -1, + "2017-W51", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2017-W52", + -3, + -1, + "2017-W51", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2017-W52", + 0, + 0, + "2017-W51", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2017-W52", + 0, + 0, + "2017-W51", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2017-W52", + -72, + -1, + "2017-W51", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2017-W52", + -2, + -1, + "2017-W51", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2017-W52", + 0, + 0, + "2017-W51", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2017-W52", + 0, + 0, + "2017-W51", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2017-W52", + 0, + 0, + "2017-W51", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2017-W52", + 0, + 0, + "2017-W51", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2017-W52", + 0, + 0, + "2017-W51", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2017-W52", + 0, + 0, + "2017-W51", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2017-W52", + -72, + -1, + "2017-W51", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2017-W52", + -70, + -1, + "2017-W51", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2017-W52", + -24, + -1, + "2017-W51", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2017-W52", + -1, + -1, + "2017-W51", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W01", + "orders": [ + "2018-W01", + 2, + 0, + "2017-W52", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-W01", + 4, + 0, + "2017-W52", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W01", + 160, + 0, + "2017-W52", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W01", + 0, + 0, + "2017-W52", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W01", + 160, + 0, + "2017-W52", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-W01", + 160, + 0, + "2017-W52", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-W01", + 80, + 0, + "2017-W52", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W01", + 2, + 0, + "2017-W52", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-W02", + "orders": [ + "2018-W02", + -2, + -1, + "2018-W01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W02", + -4, + -1, + "2018-W01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W02", + 0, + 0, + "2018-W01", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W02", + 0, + 0, + "2018-W01", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W02", + -160, + -1, + "2018-W01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W02", + 0, + 0, + "2018-W01", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W02", + 0, + 0, + "2018-W01", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W02", + 0, + 0, + "2018-W01", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W02", + 160, + 0, + "2018-W01", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_tax_refund": [ + "2018-W02", + 0, + 0, + "2018-W01", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W02", + 0, + 0, + "2018-W01", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W02", + 0, + 0, + "2018-W01", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W02", + -320, + -2, + "2018-W01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W02", + -320, + -2, + "2018-W01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W02", + -80, + -1, + "2018-W01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W02", + -2, + -1, + "2018-W01", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W03", + "orders": [ + "2018-W03", + 2, + 0, + "2018-W02", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-W03", + 4, + 0, + "2018-W02", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-W03", + 0, + 0, + "2018-W02", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W03", + 0, + 0, + "2018-W02", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W03", + 133.23, + 0, + "2018-W02", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-W03", + 0, + 0, + "2018-W02", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W03", + 33.23, + 0, + "2018-W02", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W03", + 0, + 0, + "2018-W02", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W03", + -160, + -1, + "2018-W02", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2018-W03", + 0, + 0, + "2018-W02", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W03", + 0, + 0, + "2018-W02", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W03", + 0, + 0, + "2018-W02", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W03", + 293.23, + -1.8327, + "2018-W02", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W03", + 293.23, + -1.8327, + "2018-W02", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W03", + 66.615, + 0, + "2018-W02", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W03", + 2, + 0, + "2018-W02", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-W04", + "orders": [ + "2018-W04", + -2, + -1, + "2018-W03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W04", + -4, + -1, + "2018-W03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W04", + 0, + 0, + "2018-W03", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W04", + 0, + 0, + "2018-W03", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W04", + -133.23, + -1, + "2018-W03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W04", + 0, + 0, + "2018-W03", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W04", + -33.23, + -1, + "2018-W03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W04", + 0, + 0, + "2018-W03", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W04", + 0, + 0, + "2018-W03", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W04", + 0, + 0, + "2018-W03", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W04", + 0, + 0, + "2018-W03", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W04", + 0, + 0, + "2018-W03", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W04", + -133.23, + -1, + "2018-W03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W04", + -133.23, + -1, + "2018-W03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W04", + -66.615, + -1, + "2018-W03", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W04", + -2, + -1, + "2018-W03", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W05", + "orders": [ + "2018-W05", + 2, + 0, + "2018-W04", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-W05", + 2, + 0, + "2018-W04", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-W05", + 0, + 0, + "2018-W04", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W05", + 0, + 0, + "2018-W04", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W05", + 67.6, + 0, + "2018-W04", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-W05", + 0, + 0, + "2018-W04", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W05", + 7.6, + 0, + "2018-W04", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W05", + 0, + 0, + "2018-W04", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W05", + 0, + 0, + "2018-W04", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W05", + 0, + 0, + "2018-W04", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W05", + 0, + 0, + "2018-W04", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W05", + 0, + 0, + "2018-W04", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W05", + 67.6, + 0, + "2018-W04", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-W05", + 67.6, + 0, + "2018-W04", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-W05", + 33.8, + 0, + "2018-W04", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W05", + 1, + 0, + "2018-W04", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-W06", + "orders": [ + "2018-W06", + -1, + -0.5, + "2018-W05", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W06", + 0, + 0, + "2018-W05", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-W06", + 2, + 0, + "2018-W05", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupon_discount": [ + "2018-W06", + 60, + 0, + "2018-W05", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_sales": [ + "2018-W06", + -60.00999999999999, + -0.8877, + "2018-W05", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W06", + 0, + 0, + "2018-W05", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W06", + -0.009999999999999787, + -0.0013, + "2018-W05", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W06", + 0, + 0, + "2018-W05", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W06", + 0, + 0, + "2018-W05", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W06", + 0, + 0, + "2018-W05", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W06", + 0, + 0, + "2018-W05", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W06", + 0, + 0, + "2018-W05", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W06", + -60.00999999999999, + -0.8877, + "2018-W05", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W06", + -60.00999999999999, + -0.8877, + "2018-W05", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W06", + -26.209999999999997, + -0.7754, + "2018-W05", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W06", + 1, + 1, + "2018-W05", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2018-W07", + "orders": [ + "2018-W07", + -1, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W07", + -2, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W07", + -2, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + "2018-W07", + -60, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2018-W07", + -7.59, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W07", + 0, + 0, + "2018-W06", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W07", + -7.59, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W07", + 0, + 0, + "2018-W06", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W07", + 0, + 0, + "2018-W06", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W07", + 0, + 0, + "2018-W06", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W07", + 0, + 0, + "2018-W06", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W07", + 0, + 0, + "2018-W06", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W07", + -7.59, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W07", + -7.59, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W07", + -7.59, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W07", + -2, + -1, + "2018-W06", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W08", + "orders": [ + "2018-W08", + 3, + 0, + "2018-W07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-W08", + 5, + 0, + "2018-W07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-W08", + 0, + 0, + "2018-W07", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W08", + 0, + 0, + "2018-W07", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W08", + 161.29000000000002, + 0, + "2018-W07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-W08", + 0, + 0, + "2018-W07", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W08", + 16.29, + 0, + "2018-W07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W08", + 0, + 0, + "2018-W07", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W08", + 0, + 0, + "2018-W07", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W08", + 0, + 0, + "2018-W07", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W08", + 0, + 0, + "2018-W07", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W08", + 0, + 0, + "2018-W07", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W08", + 161.29000000000002, + 0, + "2018-W07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-W08", + 161.29000000000002, + 0, + "2018-W07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-W08", + 53.7633, + 0, + "2018-W07", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W08", + 1.6667, + 0, + "2018-W07", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-W09", + "orders": [ + "2018-W09", + -1, + -0.3333, + "2018-W08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W09", + -2, + -0.4, + "2018-W08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W09", + 0, + 0, + "2018-W08", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W09", + 0, + 0, + "2018-W08", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W09", + -81.29000000000002, + -0.504, + "2018-W08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W09", + 0, + 0, + "2018-W08", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W09", + -16.29, + -1, + "2018-W08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W09", + 0, + 0, + "2018-W08", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W09", + 0, + 0, + "2018-W08", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W09", + 0, + 0, + "2018-W08", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W09", + 0, + 0, + "2018-W08", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W09", + 0, + 0, + "2018-W08", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W09", + -81.29000000000002, + -0.504, + "2018-W08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W09", + -81.29000000000002, + -0.504, + "2018-W08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W09", + -13.763300000000001, + -0.256, + "2018-W08", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W09", + -0.16670000000000007, + -0.1, + "2018-W08", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W10", + "orders": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-W10", + -1, + -0.3333, + "2018-W09", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W10", + 115.94999999999999, + 1.4494, + "2018-W09", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W10", + 75.95, + 0, + "2018-W09", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W10", + 0, + 0, + "2018-W09", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W10", + 115.94999999999999, + 1.4494, + "2018-W09", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W10", + 115.94999999999999, + 1.4494, + "2018-W09", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W10", + 57.974999999999994, + 1.4494, + "2018-W09", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W10", + -0.5, + -0.3333, + "2018-W09", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W11", + "orders": [ + "2018-W11", + 3, + 1.5, + "2018-W10", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-W11", + 3, + 1.5, + "2018-W10", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-W11", + 0, + 0, + "2018-W10", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W11", + 0, + 0, + "2018-W10", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W11", + 62.25, + 0.3177, + "2018-W10", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-W11", + 3.2, + 0, + "2018-W10", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-W11", + -30.950000000000003, + -0.4075, + "2018-W10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W11", + 0, + 0, + "2018-W10", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W11", + 0, + 0, + "2018-W10", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W11", + 0, + 0, + "2018-W10", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W11", + 0, + 0, + "2018-W10", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W11", + 0, + 0, + "2018-W10", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W11", + 62.25, + 0.3177, + "2018-W10", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W11", + 59.05000000000001, + 0.3014, + "2018-W10", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W11", + -46.334999999999994, + -0.4729, + "2018-W10", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W11", + 0, + 0, + "2018-W10", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-W12", + "orders": [ + "2018-W12", + -4, + -0.8, + "2018-W11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W12", + -4, + -0.8, + "2018-W11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W12", + 1, + 0, + "2018-W11", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupon_discount": [ + "2018-W12", + 50, + 0, + "2018-W11", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_sales": [ + "2018-W12", + -251, + -0.9721, + "2018-W11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W12", + -3.2, + -1, + "2018-W11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2018-W12", + -37.8, + -0.84, + "2018-W11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W12", + 0, + 0, + "2018-W11", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W12", + 0, + 0, + "2018-W11", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W12", + 0, + 0, + "2018-W11", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W12", + 0, + 0, + "2018-W11", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W12", + 0, + 0, + "2018-W11", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W12", + -251, + -0.9721, + "2018-W11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W12", + -247.8, + -0.9718, + "2018-W11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W12", + -44.44, + -0.8606, + "2018-W11", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W12", + 0, + 0, + "2018-W11", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-W13", + "orders": [ + "2018-W13", + 3, + 3, + "2018-W12", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-W13", + 5, + 5, + "2018-W12", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-W13", + -1, + -1, + "2018-W12", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + "2018-W13", + -50, + -1, + "2018-W12", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2018-W13", + 209.13, + 29.0458, + "2018-W12", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-W13", + 0, + 0, + "2018-W12", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W13", + 14.129999999999999, + 1.9625, + "2018-W12", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W13", + 0, + 0, + "2018-W12", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W13", + 92, + 0, + "2018-W12", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_tax_refund": [ + "2018-W13", + 0, + 0, + "2018-W12", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W13", + 7, + 0, + "2018-W12", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W13", + 0, + 0, + "2018-W12", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W13", + 117.12999999999998, + 16.2681, + "2018-W12", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W13", + 117.12999999999998, + 16.2681, + "2018-W12", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W13", + 23.8825, + 3.317, + "2018-W12", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W13", + 0.5, + 0.5, + "2018-W12", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2018-W14", + "orders": [ + "2018-W14", + 2, + 0.5, + "2018-W13", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-W14", + 2, + 0.3333, + "2018-W13", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-W14", + 1, + 0, + "2018-W13", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupon_discount": [ + "2018-W14", + 30, + 0, + "2018-W13", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_sales": [ + "2018-W14", + -61.129999999999995, + -0.2826, + "2018-W13", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W14", + 1.2, + 0, + "2018-W13", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-W14", + -17.33, + -0.8125, + "2018-W13", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W14", + 0, + 0, + "2018-W13", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W14", + -72, + -0.7826, + "2018-W13", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2018-W14", + 0, + 0, + "2018-W13", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W14", + -7, + -1, + "2018-W13", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W14", + 0, + 0, + "2018-W13", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W14", + 10.870000000000005, + 0.0874, + "2018-W13", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W14", + 9.670000000000016, + 0.0778, + "2018-W13", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W14", + -8.549199999999999, + -0.275, + "2018-W13", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W14", + -0.16670000000000007, + -0.1111, + "2018-W13", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W15", + "orders": [ + "2018-W15", + 3, + 0.5, + "2018-W14", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-W15", + 3, + 0.375, + "2018-W14", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-W15", + -1, + -1, + "2018-W14", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + "2018-W15", + -30, + -1, + "2018-W14", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + "2018-W15", + 69.80000000000001, + 0.4497, + "2018-W14", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-W15", + -1.2, + -1, + "2018-W14", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2018-W15", + -4, + -1, + "2018-W14", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W15", + 0, + 0, + "2018-W14", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W15", + 24, + 1.2, + "2018-W14", + "is-unfavorable", + "is-increase", + "USD" + ], + "total_tax_refund": [ + "2018-W15", + 0, + 0, + "2018-W14", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W15", + 4, + 0, + "2018-W14", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W15", + 0, + 0, + "2018-W14", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W15", + 45.80000000000001, + 0.3388, + "2018-W14", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W15", + 47, + 0.3507, + "2018-W14", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W15", + -2.4222, + -0.1075, + "2018-W14", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W15", + -0.11109999999999998, + -0.0833, + "2018-W14", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W16", + "orders": [ + "2018-W16", + -5, + -0.5556, + "2018-W15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W16", + -6, + -0.5455, + "2018-W15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W16", + 0, + 0, + "2018-W15", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W16", + 0, + 0, + "2018-W15", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W16", + -42.5, + -0.1889, + "2018-W15", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W16", + 0, + 0, + "2018-W15", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W16", + 0, + 0, + "2018-W15", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W16", + 0, + 0, + "2018-W15", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W16", + -44, + -1, + "2018-W15", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2018-W16", + 0, + 0, + "2018-W15", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W16", + -4, + -1, + "2018-W15", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W16", + 0, + 0, + "2018-W15", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W16", + 1.5, + 0.0083, + "2018-W15", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W16", + 1.5, + 0.0083, + "2018-W15", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W16", + 25.5139, + 1.2686, + "2018-W15", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W16", + 0.027800000000000047, + 0.0227, + "2018-W15", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2018-W17", + "orders": [ + "2018-W17", + -1, + -0.25, + "2018-W16", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W17", + -2, + -0.4, + "2018-W16", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W17", + -122.5, + -0.6712, + "2018-W16", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W17", + 0, + 0, + "2018-W16", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W17", + -122.5, + -0.6712, + "2018-W16", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W17", + -122.5, + -0.6712, + "2018-W16", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W17", + -25.625, + -0.5616, + "2018-W16", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W17", + -0.25, + -0.2, + "2018-W16", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W18", + "orders": [ + "2018-W18", + -2, + -0.6667, + "2018-W17", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W18", + -2, + -0.6667, + "2018-W17", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W18", + -8, + -0.1333, + "2018-W17", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W18", + 2, + 0, + "2018-W17", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W18", + -8, + -0.1333, + "2018-W17", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W18", + -10, + -0.1667, + "2018-W17", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W18", + 32, + 1.6, + "2018-W17", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W18", + 0, + 0, + "2018-W17", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-W19", + "orders": [ + "2018-W19", + -1, + -1, + "2018-W18", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W19", + -1, + -1, + "2018-W18", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W19", + 0, + 0, + "2018-W18", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W19", + 0, + 0, + "2018-W18", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W19", + -52, + -1, + "2018-W18", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W19", + -2, + -1, + "2018-W18", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2018-W19", + 0, + 0, + "2018-W18", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W19", + 0, + 0, + "2018-W18", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W19", + 0, + 0, + "2018-W18", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W19", + 0, + 0, + "2018-W18", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W19", + 0, + 0, + "2018-W18", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W19", + 0, + 0, + "2018-W18", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W19", + -52, + -1, + "2018-W18", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W19", + -50, + -1, + "2018-W18", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W19", + -52, + -1, + "2018-W18", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W19", + -1, + -1, + "2018-W18", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W20", + "orders": [ + "2018-W20", + 1, + 0, + "2018-W19", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-W20", + 1, + 0, + "2018-W19", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W20", + 20, + 0, + "2018-W19", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W20", + 0, + 0, + "2018-W19", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W20", + 20, + 0, + "2018-W19", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-W20", + 20, + 0, + "2018-W19", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-W20", + 20, + 0, + "2018-W19", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W20", + 1, + 0, + "2018-W19", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-W21", + "orders": [ + "2018-W21", + -1, + -1, + "2018-W20", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W21", + -1, + -1, + "2018-W20", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W21", + -20, + -1, + "2018-W20", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W21", + 0, + 0, + "2018-W20", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W21", + -20, + -1, + "2018-W20", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W21", + -20, + -1, + "2018-W20", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W21", + -20, + -1, + "2018-W20", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W21", + -1, + -1, + "2018-W20", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W22", + "orders": [ + "2018-W22", + 6, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + "2018-W22", + 10, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + "2018-W22", + 0, + 0, + "2018-W21", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W22", + 0, + 0, + "2018-W21", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W22", + 275.11, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + "2018-W22", + 0.99, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-W22", + 9.98, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W22", + 0.28, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_refund": [ + "2018-W22", + 0, + 0, + "2018-W21", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W22", + 0, + 0, + "2018-W21", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W22", + 0, + 0, + "2018-W21", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W22", + 0, + 0, + "2018-W21", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W22", + 275.11, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + "2018-W22", + 274.12, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + "2018-W22", + 45.8517, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W22", + 1.6667, + 0, + "2018-W21", + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": "2018-W23", + "orders": [ + "2018-W23", + -4, + -0.6667, + "2018-W22", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W23", + -8, + -0.8, + "2018-W22", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W23", + 0, + 0, + "2018-W22", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W23", + 0, + 0, + "2018-W22", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W23", + -128.93, + -0.4686, + "2018-W22", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W23", + -0.99, + -1, + "2018-W22", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2018-W23", + 76.2, + 7.6353, + "2018-W22", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W23", + -0.28, + -1, + "2018-W22", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + "2018-W23", + 2.06, + 0, + "2018-W22", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_tax_refund": [ + "2018-W23", + 0.06, + 0, + "2018-W22", + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_refund": [ + "2018-W23", + 0, + 0, + "2018-W22", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W23", + 0, + 0, + "2018-W22", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W23", + -130.99, + -0.4761, + "2018-W22", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W23", + -130, + -0.4742, + "2018-W22", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W23", + 26.2083, + 0.5716, + "2018-W22", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W23", + -0.6667000000000001, + -0.4, + "2018-W22", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W24", + "orders": [ + "2018-W24", + 0, + 0, + "2018-W23", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-W24", + 1, + 0.5, + "2018-W23", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-W24", + 0, + 0, + "2018-W23", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W24", + 0, + 0, + "2018-W23", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W24", + -76.18, + -0.5211, + "2018-W23", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W24", + 0, + 0, + "2018-W23", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W24", + -86.18, + -1, + "2018-W23", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W24", + 0, + 0, + "2018-W23", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W24", + -2.06, + -1, + "2018-W23", + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + "2018-W24", + -0.06, + -1, + "2018-W23", + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_refund": [ + "2018-W24", + 0, + 0, + "2018-W23", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W24", + 0, + 0, + "2018-W23", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W24", + -74.12, + -0.5143, + "2018-W23", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W24", + -74.12, + -0.5143, + "2018-W23", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W24", + -37.06, + -0.5143, + "2018-W23", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W24", + 0.5, + 0.5, + "2018-W23", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2018-W25", + "orders": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W25", + 170, + 2.4286, + "2018-W24", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W25", + 170, + 2.4286, + "2018-W24", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W25", + 170, + 2.4286, + "2018-W24", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W25", + 85, + 2.4286, + "2018-W24", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W25", + 0, + 0, + "2018-W24", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-W26", + "orders": [ + "2018-W26", + -1, + -0.5, + "2018-W25", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W26", + -2, + -0.6667, + "2018-W25", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W26", + 0, + 0, + "2018-W25", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W26", + 0, + 0, + "2018-W25", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W26", + -229.71, + -0.9571, + "2018-W25", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W26", + 0.29, + 0, + "2018-W25", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-W26", + 0, + 0, + "2018-W25", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W26", + 0, + 0, + "2018-W25", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W26", + 0, + 0, + "2018-W25", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W26", + 0, + 0, + "2018-W25", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W26", + 0, + 0, + "2018-W25", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W26", + 0, + 0, + "2018-W25", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W26", + -229.71, + -0.9571, + "2018-W25", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W26", + -230, + -0.9583, + "2018-W25", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W26", + -109.71000000000001, + -0.9143, + "2018-W25", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W26", + -0.5, + -0.3333, + "2018-W25", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W27", + "orders": [ + "2018-W27", + 2, + 2, + "2018-W26", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-W27", + 2, + 2, + "2018-W26", + "is-favorable", + "is-increase", + "USD" + ], + "coupons": [ + "2018-W27", + 0, + 0, + "2018-W26", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W27", + 0, + 0, + "2018-W26", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W27", + 124.37, + 12.0865, + "2018-W26", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-W27", + 6.79, + 23.4138, + "2018-W26", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping": [ + "2018-W27", + 26.3, + 0, + "2018-W26", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W27", + 1.28, + 0, + "2018-W26", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_refund": [ + "2018-W27", + 0, + 0, + "2018-W26", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W27", + 0, + 0, + "2018-W26", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W27", + 0, + 0, + "2018-W26", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W27", + 0, + 0, + "2018-W26", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W27", + 124.37, + 12.0865, + "2018-W26", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W27", + 117.58, + 11.758, + "2018-W26", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W27", + 34.5967, + 3.3622, + "2018-W26", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W27", + 0, + 0, + "2018-W26", + "", + "is-neutral", + "USD" + ] + }, + { + "period": "2018-W28", + "orders": [ + "2018-W28", + -2, + -0.6667, + "2018-W27", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W28", + -1, + -0.3333, + "2018-W27", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W28", + 0, + 0, + "2018-W27", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W28", + 0, + 0, + "2018-W27", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W28", + 183.59, + 1.3634, + "2018-W27", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-W28", + 3.130000000000001, + 0.4421, + "2018-W27", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping": [ + "2018-W28", + 151, + 5.7414, + "2018-W27", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-W28", + 14.46, + 11.2969, + "2018-W27", + "is-favorable", + "is-increase", + "USD" + ], + "total_refund": [ + "2018-W28", + 0, + 0, + "2018-W27", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W28", + 0, + 0, + "2018-W27", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W28", + 0, + 0, + "2018-W27", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W28", + 0, + 0, + "2018-W27", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W28", + 183.59, + 1.3634, + "2018-W27", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-W28", + 180.46000000000004, + 1.4145, + "2018-W27", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-W28", + 273.3633, + 6.0901, + "2018-W27", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-W28", + 1, + 1, + "2018-W27", + "is-favorable", + "is-increase", + "USD" + ] + }, + { + "period": "2018-W29", + "orders": [ + "2018-W29", + -1, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-W29", + -2, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-W29", + 0, + 0, + "2018-W28", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W29", + 0, + 0, + "2018-W28", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W29", + -318.25, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-W29", + -10.21, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + "2018-W29", + -177.3, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-W29", + -15.74, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + "2018-W29", + 0, + 0, + "2018-W28", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W29", + 0, + 0, + "2018-W28", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W29", + 0, + 0, + "2018-W28", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W29", + 0, + 0, + "2018-W28", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W29", + -318.25, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-W29", + -308.04, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-W29", + -318.25, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-W29", + -2, + -1, + "2018-W28", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-W30", + "orders": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "products": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "coupons": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "total_tax": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "total_refund": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "net_sales": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + "2018-W30", + 0, + 0, + "2018-W29", + "", + "is-neutral", + "USD" + ] + } + ], + "total_gross_sales": 2858.52, + "total_net_sales": 2833.5499999999997, + "total_orders": 65, + "total_products": 87, + "avg_gross_sales": 92.2103, + "avg_net_sales": 91.4048, + "avg_orders": 2.0968, + "avg_products": 2.8065 +} \ No newline at end of file diff --git a/Networking/NetworkingTests/Responses/order-stats-year.json b/Networking/NetworkingTests/Responses/order-stats-year.json new file mode 100644 index 00000000000..243eef0d85f --- /dev/null +++ b/Networking/NetworkingTests/Responses/order-stats-year.json @@ -0,0 +1,714 @@ +{ + "date": "2018", + "unit": "year", + "quantity": "4", + "fields": [ + "period", + "orders", + "products", + "coupons", + "coupon_discount", + "total_sales", + "total_tax", + "total_shipping", + "total_shipping_tax", + "total_refund", + "total_tax_refund", + "total_shipping_refund", + "total_shipping_tax_refund", + "currency", + "gross_sales", + "net_sales", + "avg_order_value", + "avg_products_per_order" + ], + "data": [ + [ + 2015, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + 2016, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 0, + 0, + 0, + 0 + ], + [ + 2017, + 228, + 539, + 32, + 237.14000000000007, + 9813.699999999988, + 219.6780000000001, + 1466.0300000000007, + 27.6736, + 1743.3, + 12.47, + 202.83, + 4.386, + "USD", + 8070.399999999988, + 7850.721999999988, + 35.3965, + 2.364 + ], + [ + 2018, + 65, + 87, + 4, + 140, + 3176.580000000001, + 24.97, + 517.95, + 17.3, + 318.06, + 0.06, + 11, + 0, + "USD", + 2858.520000000001, + 2833.550000000001, + 43.9772, + 1.3385 + ] + ], + "delta_fields": [ + "period", + "delta", + "percentage_change", + "reference_period", + "favorable", + "direction", + "currency" + ], + "deltas": [ + { + "period": 2015, + "orders": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "products": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "coupons": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "total_sales": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "total_tax": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "total_refund": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "net_sales": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + 2015, + 0, + 0, + 2014, + "", + "is-neutral", + "USD" + ] + }, + { + "period": 2016, + "orders": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "products": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "coupons": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "total_sales": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "total_tax": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "total_shipping": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "total_shipping_tax": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "total_refund": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "net_sales": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "avg_order_value": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ], + "avg_products_per_order": [ + 2016, + 0, + 0, + 2015, + "", + "is-neutral", + "USD" + ] + }, + { + "period": 2017, + "orders": [ + 2017, + 228, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "products": [ + 2017, + 539, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupons": [ + 2017, + 32, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "coupon_discount": [ + 2017, + 237.14000000000007, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_sales": [ + 2017, + 9813.699999999988, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_tax": [ + 2017, + 219.6780000000001, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + 2017, + 1466.0300000000007, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + 2017, + 27.6736, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_refund": [ + 2017, + 1743.3, + 0, + 2016, + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_tax_refund": [ + 2017, + 12.47, + 0, + 2016, + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_refund": [ + 2017, + 202.83, + 0, + 2016, + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax_refund": [ + 2017, + 4.386, + 0, + 2016, + "is-unfavorable", + "is-undefined-increase", + "USD" + ], + "gross_sales": [ + 2017, + 8070.399999999988, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "net_sales": [ + 2017, + 7850.721999999988, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_order_value": [ + 2017, + 35.3965, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ], + "avg_products_per_order": [ + 2017, + 2.364, + 0, + 2016, + "is-favorable", + "is-undefined-increase", + "USD" + ] + }, + { + "period": 2018, + "orders": [ + 2018, + -163, + -0.7149, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + 2018, + -452, + -0.8386, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + 2018, + -28, + -0.875, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupon_discount": [ + 2018, + -97.14000000000007, + -0.4096, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_sales": [ + 2018, + -6637.119999999987, + -0.6763, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + 2018, + -194.7080000000001, + -0.8863, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping": [ + 2018, + -948.0800000000006, + -0.6467, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + 2018, + -10.3736, + -0.3749, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + 2018, + -1425.24, + -0.8176, + 2017, + "is-favorable", + "is-decrease", + "USD" + ], + "total_tax_refund": [ + 2018, + -12.41, + -0.9952, + 2017, + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_refund": [ + 2018, + -191.83, + -0.9458, + 2017, + "is-favorable", + "is-decrease", + "USD" + ], + "total_shipping_tax_refund": [ + 2018, + -4.386, + -1, + 2017, + "is-favorable", + "is-decrease", + "USD" + ], + "gross_sales": [ + 2018, + -5211.8799999999865, + -0.6458, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + 2018, + -5017.171999999987, + -0.6391, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + 2018, + 8.5807, + 0.2424, + 2017, + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + 2018, + -1.0254999999999999, + -0.4338, + 2017, + "is-unfavorable", + "is-decrease", + "USD" + ] + } + ], + "total_gross_sales": 10928.91999999999, + "total_net_sales": 10684.27199999999, + "total_orders": 293, + "total_products": 626, + "avg_gross_sales": 2732.23, + "avg_net_sales": 2671.068, + "avg_orders": 73.25, + "avg_products": 156.5 +} \ No newline at end of file From bfd6d1cd7602b05b501c7b49225137a22cab12bc Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 14:31:47 -0300 Subject: [PATCH 009/112] Increasing version to Mark 0.4.3 --- WooCommerce/Resources/Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Resources/Info.plist b/WooCommerce/Resources/Info.plist index 45b1fc9a771..dd08d4544d1 100644 --- a/WooCommerce/Resources/Info.plist +++ b/WooCommerce/Resources/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.4.2 + 0.4.3 CFBundleURLTypes @@ -43,7 +43,7 @@ CFBundleVersion - 20180730 + 20180731 Fabric APIKey From 7c4ec47994b9970eea0edf9cddd6ff1b2a40c131 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 17:53:38 -0300 Subject: [PATCH 010/112] Nukes CoreDataManager+Woo --- .../Yosemite/CoreDataManager+Woo.swift | 22 ------------------- .../WooCommerce.xcodeproj/project.pbxproj | 4 ---- 2 files changed, 26 deletions(-) delete mode 100644 WooCommerce/Classes/Yosemite/CoreDataManager+Woo.swift diff --git a/WooCommerce/Classes/Yosemite/CoreDataManager+Woo.swift b/WooCommerce/Classes/Yosemite/CoreDataManager+Woo.swift deleted file mode 100644 index 722652e33d3..00000000000 --- a/WooCommerce/Classes/Yosemite/CoreDataManager+Woo.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation -import Storage - - - -/// CoreDataManager WooCommerce Extensions -/// -extension CoreDataManager { - - /// Returns the default CoreDataManager Instance. - /// - private(set) public static var global: CoreDataManager = { - return CoreDataManager(name: Settings.databaseName) - }() - - - /// Stack Settings - /// - private struct Settings { - static let databaseName = "WooCommerce" - } -} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 75613bce456..bbfff93f86b 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -34,7 +34,6 @@ B50911322049E27A007D25DC /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509112F2049E27A007D25DC /* SettingsViewController.swift */; }; B53B898920D450AF00EDB467 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B898820D450AF00EDB467 /* SessionManagerTests.swift */; }; B53B898D20D462A000EDB467 /* StoresManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B898C20D462A000EDB467 /* StoresManager.swift */; }; - B54175F220D4C15D0083BB8C /* CoreDataManager+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54175F120D4C15D0083BB8C /* CoreDataManager+Woo.swift */; }; B557652B20F681E800185843 /* StoreTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B557652A20F681E800185843 /* StoreTableViewCell.swift */; }; B557652D20F6827900185843 /* StoreTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B557652C20F6827900185843 /* StoreTableViewCell.xib */; }; B557DA1520979904005962F4 /* CustomerNoteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B557DA1320979904005962F4 /* CustomerNoteTableViewCell.swift */; }; @@ -197,7 +196,6 @@ B509112F2049E27A007D25DC /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; B53B898820D450AF00EDB467 /* SessionManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManagerTests.swift; sourceTree = ""; }; B53B898C20D462A000EDB467 /* StoresManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoresManager.swift; sourceTree = ""; }; - B54175F120D4C15D0083BB8C /* CoreDataManager+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreDataManager+Woo.swift"; sourceTree = ""; }; B557652A20F681E800185843 /* StoreTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreTableViewCell.swift; sourceTree = ""; }; B557652C20F6827900185843 /* StoreTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StoreTableViewCell.xib; sourceTree = ""; }; B557DA1320979904005962F4 /* CustomerNoteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomerNoteTableViewCell.swift; sourceTree = ""; }; @@ -387,7 +385,6 @@ B53B898C20D462A000EDB467 /* StoresManager.swift */, B5DBF3CA20E149CC00B53AED /* AuthenticatedState.swift */, B5DBF3C420E148E000B53AED /* DeauthenticatedState.swift */, - B54175F120D4C15D0083BB8C /* CoreDataManager+Woo.swift */, ); path = Yosemite; sourceTree = ""; @@ -1021,7 +1018,6 @@ B557652B20F681E800185843 /* StoreTableViewCell.swift in Sources */, B57C743D20F5493300EEFC87 /* AccountHeaderView.swift in Sources */, B50911312049E27A007D25DC /* OrdersViewController.swift in Sources */, - B54175F220D4C15D0083BB8C /* CoreDataManager+Woo.swift in Sources */, B5A8F8AD20B88D9900D211DE /* LoginPrologueViewController.swift in Sources */, B5D1AFC620BC7B7300DB0E8C /* StorePickerViewController.swift in Sources */, B5A8F8A920B84D3F00D211DE /* ApiCredentials.swift in Sources */, From 43af64c22f6fe5d93ab0e8a993bf7e2a5c1f737e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 17:54:00 -0300 Subject: [PATCH 011/112] AppDelegate: StorageManager property --- WooCommerce/Classes/AppDelegate.swift | 6 ++++++ WooCommerce/Classes/System/WooConstants.swift | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/WooCommerce/Classes/AppDelegate.swift b/WooCommerce/Classes/AppDelegate.swift index 5f41c1f18c3..7d8dc93c2af 100644 --- a/WooCommerce/Classes/AppDelegate.swift +++ b/WooCommerce/Classes/AppDelegate.swift @@ -1,5 +1,6 @@ import UIKit import CoreData +import Storage import CocoaLumberjack import Crashlytics @@ -33,6 +34,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { /// let noticePresenter = NoticePresenter() + /// CoreData Stack + /// + let storageManager = CoreDataManager(name: WooConstants.databaseStackName) + + // MARK: - AppDelegate Methods diff --git a/WooCommerce/Classes/System/WooConstants.swift b/WooCommerce/Classes/System/WooConstants.swift index 40cfa771886..e5473cbcea4 100644 --- a/WooCommerce/Classes/System/WooConstants.swift +++ b/WooCommerce/Classes/System/WooConstants.swift @@ -5,6 +5,10 @@ import Foundation /// enum WooConstants { + /// CoreData Stack Name + /// + static let databaseStackName = "WooCommerce" + /// Keychain Access's Service Name /// static let keychainServiceName = "com.automattic.woocommerce" From e5c26888900c3176e1ab2402ef900e3feab0c14e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 18:27:29 -0300 Subject: [PATCH 012/112] CoreDataManager: Nuking viewContext property --- Storage/Storage/CoreData/CoreDataManager.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Storage/Storage/CoreData/CoreDataManager.swift b/Storage/Storage/CoreData/CoreDataManager.swift index a70b9110992..d832b1c37de 100644 --- a/Storage/Storage/CoreData/CoreDataManager.swift +++ b/Storage/Storage/CoreData/CoreDataManager.swift @@ -26,12 +26,6 @@ public class CoreDataManager: StorageManagerType { /// Returns the Storage associated with the View Thread. /// public var viewStorage: StorageType { - return viewContext - } - - /// Returns the NSManagedObjectContext associated with the Main Thread. Convenience helper!! - /// - public var viewContext: NSManagedObjectContext { return persistentContainer.viewContext } From 72df0524c1591b877ff2897648cf241cd1087c89 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 18:27:43 -0300 Subject: [PATCH 013/112] ResultsController: New convenience initializer --- Yosemite/Yosemite/Tools/ResultsController.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Yosemite/Yosemite/Tools/ResultsController.swift b/Yosemite/Yosemite/Tools/ResultsController.swift index ce375cfec92..e82c916e785 100644 --- a/Yosemite/Yosemite/Tools/ResultsController.swift +++ b/Yosemite/Yosemite/Tools/ResultsController.swift @@ -80,6 +80,19 @@ public class ResultsController { setupEventsForwarding() } + /// Convenience Initializer. + /// + public convenience init(storageManager: CoreDataManager, + sectionNameKeyPath: String? = nil, + matching predicate: NSPredicate? = nil, + sortedBy descriptors: [NSSortDescriptor]) { + + self.init(viewContext: storageManager.persistentContainer.viewContext, + sectionNameKeyPath: sectionNameKeyPath, + matching: predicate, + sortedBy: descriptors) + } + /// Executes the fetch request on the store to get objects. /// From 62d65934e2dfcccb49fca664b9eded494cb12d8b Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 18:28:03 -0300 Subject: [PATCH 014/112] StorePickerViewController: Nuking Storage Import --- .../Epilogue/StorePickerViewController.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/WooCommerce/Classes/Authentication/Epilogue/StorePickerViewController.swift b/WooCommerce/Classes/Authentication/Epilogue/StorePickerViewController.swift index 61abe17effd..b474a9b14f9 100644 --- a/WooCommerce/Classes/Authentication/Epilogue/StorePickerViewController.swift +++ b/WooCommerce/Classes/Authentication/Epilogue/StorePickerViewController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import WordPressAuthenticator import WordPressUI -import Storage import Yosemite @@ -27,12 +26,12 @@ class StorePickerViewController: UIViewController { /// ResultsController: Loads Sites from the Storage Layer. /// - private let resultsController: ResultsController = { - let viewContext = CoreDataManager.global.viewContext + private let resultsController: ResultsController = { + let storageManager = AppDelegate.shared.storageManager let predicate = NSPredicate(format: "isWooCommerceActive == YES") let descriptor = NSSortDescriptor(key: "name", ascending: true) - return ResultsController(viewContext: viewContext, matching: predicate, sortedBy: [descriptor]) + return ResultsController(storageManager: storageManager, matching: predicate, sortedBy: [descriptor]) }() /// White-Background View, to be placed surrounding the bottom area. From ea66e1a6c4f90e8093e618d730446a0c29cc5fe0 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 18:28:10 -0300 Subject: [PATCH 015/112] OrderDetailsViewController: Nuking Storage Import --- .../Orders/OrderDetails/OrderDetailsViewController.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/OrderDetailsViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/OrderDetailsViewController.swift index 57e6a5b85ca..92d632b1e4e 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/OrderDetailsViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/OrderDetailsViewController.swift @@ -3,7 +3,6 @@ import Gridicons import Contacts import MessageUI import Yosemite -import Storage import CocoaLumberjack @@ -41,12 +40,12 @@ class OrderDetailsViewController: UIViewController { /// TODO: Replace with `ResultController` (OR) `ObjectController` ASAP /// - private lazy var resultsController: ResultsController = { - let viewContext = CoreDataManager.global.viewContext + private lazy var resultsController: ResultsController = { + let storageManager = AppDelegate.shared.storageManager let predicate = NSPredicate(format: "orderID = %ld", self.viewModel.order.orderID) let descriptor = NSSortDescriptor(key: "orderID", ascending: true) - return ResultsController(viewContext: viewContext, matching: predicate, sortedBy: [descriptor]) + return ResultsController(storageManager: storageManager, matching: predicate, sortedBy: [descriptor]) }() From acf4bc1f630215241f98310e952e2785a990d205 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 18:28:22 -0300 Subject: [PATCH 016/112] AuthenticatedState: OrderDetailsViewController --- WooCommerce/Classes/Yosemite/AuthenticatedState.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/Classes/Yosemite/AuthenticatedState.swift b/WooCommerce/Classes/Yosemite/AuthenticatedState.swift index 01b2c4427d9..cddfaf80b70 100644 --- a/WooCommerce/Classes/Yosemite/AuthenticatedState.swift +++ b/WooCommerce/Classes/Yosemite/AuthenticatedState.swift @@ -1,6 +1,5 @@ import Foundation import Yosemite -import Storage import Networking @@ -21,7 +20,7 @@ class AuthenticatedState: StoresManagerState { /// Designated Initializer /// init(credentials: Credentials) { - let storageManager = CoreDataManager.global + let storageManager = AppDelegate.shared.storageManager let network = AlamofireNetwork(credentials: credentials) services = [ From 4c14406572198065e0a12b96c3f65ce294020898 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 31 Jul 2018 18:29:04 -0300 Subject: [PATCH 017/112] Model: Exporting Storage Model(s) --- Yosemite/Yosemite/Model/Model.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Yosemite/Yosemite/Model/Model.swift b/Yosemite/Yosemite/Model/Model.swift index 5891a949445..fcc5982c6e7 100644 --- a/Yosemite/Yosemite/Model/Model.swift +++ b/Yosemite/Yosemite/Model/Model.swift @@ -1,9 +1,10 @@ import Foundation import Networking +import Storage -// MARK: - Exported Symbols +// MARK: - Exported ReadOnly Symbols public typealias Account = Networking.Account public typealias Address = Networking.Address @@ -14,3 +15,9 @@ public typealias OrderStatus = Networking.OrderStatus public typealias OrderCouponLine = Networking.OrderCouponLine public typealias OrderNote = Networking.OrderNote public typealias Site = Networking.Site + + +// MARK: - Exported Storage Symbols + +public typealias StorageSite = Storage.Site +public typealias StorageOrder = Storage.Order From cca4c5960239ab79ad061e55dbb0aa8785d1c83b Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Tue, 31 Jul 2018 16:42:14 -0500 Subject: [PATCH 018/112] Updates order stats models, decoder, and tests --- .../Networking.xcodeproj/project.pbxproj | 12 +- .../Networking/Mapper/OrderStatsMapper.swift | 21 +--- Networking/Networking/Model/OrderStats.swift | 116 ++++++++++++++++++ ...derStatItem.swift => OrderStatsItem.swift} | 14 +-- .../Networking/Remote/OrderStatsRemote.swift | 2 +- .../Mapper/OrderStatsMapperTests.swift | 103 ++++++++++++++-- Yosemite/Yosemite/Model/Model.swift | 3 +- 7 files changed, 228 insertions(+), 43 deletions(-) create mode 100644 Networking/Networking/Model/OrderStats.swift rename Networking/Networking/Model/{OrderStatItem.swift => OrderStatsItem.swift} (91%) diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 212c7a3bd9d..7a53dead527 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -15,9 +15,10 @@ 743FDB9E210FB36900AC737F /* order-stats-week.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9B210FB36900AC737F /* order-stats-week.json */; }; 743FDBA0210FB3E500AC737F /* OrderStatsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743FDB9F210FB3E500AC737F /* OrderStatsMapperTests.swift */; }; 748D4248210F89ED00CF7D1B /* OrderStatsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */; }; - 748D424A210F92EA00CF7D1B /* OrderStatItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */; }; + 748D424A210F92EA00CF7D1B /* OrderStatsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */; }; 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */; }; 748D424E210FB1F500CF7D1B /* order-stats-day.json in Resources */ = {isa = PBXBuildFile; fileRef = 748D424D210FB1F500CF7D1B /* order-stats-day.json */; }; + 74A1196C2110F4BB00E1E5F0 /* OrderStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */; }; 74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */; }; 74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06520EEB76400B6EDC9 /* order-notes.json */; }; 74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */; }; @@ -91,9 +92,10 @@ 743FDB9B210FB36900AC737F /* order-stats-week.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-week.json"; sourceTree = ""; }; 743FDB9F210FB3E500AC737F /* OrderStatsMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatsMapperTests.swift; sourceTree = ""; }; 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsRemote.swift; sourceTree = ""; }; - 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatItem.swift; sourceTree = ""; }; + 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsItem.swift; sourceTree = ""; }; 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsMapper.swift; sourceTree = ""; }; 748D424D210FB1F500CF7D1B /* order-stats-day.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-day.json"; sourceTree = ""; }; + 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStats.swift; sourceTree = ""; }; 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNote.swift; sourceTree = ""; }; 74C8F06520EEB76400B6EDC9 /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = ""; }; 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapper.swift; sourceTree = ""; }; @@ -327,7 +329,8 @@ B5C6FCCE20A3592900A4F8E4 /* OrderItem.swift */, 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */, B5BB1D1120A255EC00112D92 /* OrderStatus.swift */, - 748D4249210F92EA00CF7D1B /* OrderStatItem.swift */, + 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */, + 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */, B56C1EB720EA76F500D749F9 /* Site.swift */, ); path = Model; @@ -594,6 +597,7 @@ files = ( B56C1EB620EA757B00D749F9 /* SiteListMapper.swift in Sources */, B557DA1A20979D66005962F4 /* Settings.swift in Sources */, + 74A1196C2110F4BB00E1E5F0 /* OrderStats.swift in Sources */, B58E5BEA20FFB3D0003C986E /* CodingUserInfoKey+Woo.swift in Sources */, 741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */, 74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */, @@ -614,7 +618,7 @@ B557DA1820979D51005962F4 /* Credentials.swift in Sources */, CE583A0E2109154500D73C1C /* OrderNoteMapper.swift in Sources */, B5C6FCCF20A3592900A4F8E4 /* OrderItem.swift in Sources */, - 748D424A210F92EA00CF7D1B /* OrderStatItem.swift in Sources */, + 748D424A210F92EA00CF7D1B /* OrderStatsItem.swift in Sources */, B505F6EC20BEFDC200BB1B69 /* Loader.swift in Sources */, B5BB1D1220A255EC00112D92 /* OrderStatus.swift in Sources */, 74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */, diff --git a/Networking/Networking/Mapper/OrderStatsMapper.swift b/Networking/Networking/Mapper/OrderStatsMapper.swift index bceaec019bf..5468bce1806 100644 --- a/Networking/Networking/Mapper/OrderStatsMapper.swift +++ b/Networking/Networking/Mapper/OrderStatsMapper.swift @@ -5,25 +5,10 @@ import Foundation /// class OrderStatsMapper: Mapper { - /// (Attempts) to convert a dictionary into [OrderStatItem]. + /// (Attempts) to convert a dictionary into an OrderStats entity. /// - func map(response: Data) throws -> [OrderStatItem] { + func map(response: Data) throws -> OrderStats { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter) - - return try decoder.decode(OrderStatsEnvelope.self, from: response).orderStats - } -} - - -/// OrderStats Disposable Entity: -/// `Load Order Stats` endpoint returns all of its individual stat items within the `data` key. This entity -/// allows us to do parse all the things with JSONDecoder. -/// -private struct OrderStatsEnvelope: Decodable { - let orderStats: [OrderStatItem] - - private enum CodingKeys: String, CodingKey { - case orderStats = "data" + return try decoder.decode(OrderStats.self, from: response) } } diff --git a/Networking/Networking/Model/OrderStats.swift b/Networking/Networking/Model/OrderStats.swift new file mode 100644 index 00000000000..6ca6916f56f --- /dev/null +++ b/Networking/Networking/Model/OrderStats.swift @@ -0,0 +1,116 @@ +import Foundation + + +/// Represents order stats over a specific period. +/// +public struct OrderStats: Decodable { + public let date: String + public let unit: String + public let quantity: String + public let fields: [String] + public let data: [OrderStatsItem]? + public let totalGrossSales: Float + public let totalNetSales: Float + public let totalOrders: Int + public let totalProducts: Int + public let averageGrossSales: Float + public let averageNetSales: Float + public let averageOrders: Float + public let averageProducts: Float + + + /// The public initializer for order stats. + /// + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let date = try container.decode(String.self, forKey: .date) + let unit = try container.decode(String.self, forKey: .unit) + let quantity = try container.decode(String.self, forKey: .quantity) + + // FIXME: Decode data into [OrderStatItem] + let fields = try container.decode([String].self, forKey: .fields) + let data: [OrderStatsItem]? = nil + //// + + let totalGrossSales = try container.decode(Float.self, forKey: .totalGrossSales) + let totalNetSales = try container.decode(Float.self, forKey: .totalNetSales) + let totalOrders = try container.decode(Int.self, forKey: .totalOrders) + let totalProducts = try container.decode(Int.self, forKey: .totalProducts) + + let averageGrossSales = try container.decode(Float.self, forKey: .averageGrossSales) + let averageNetSales = try container.decode(Float.self, forKey: .averageNetSales) + let averageOrders = try container.decode(Float.self, forKey: .averageOrders) + let averageProducts = try container.decode(Float.self, forKey: .averageProducts) + + self.init(date: date, unit: unit, quantity: quantity, fields: fields, data: data, totalGrossSales: totalGrossSales, totalNetSales: totalNetSales, totalOrders: totalOrders, totalProducts: totalProducts, averageGrossSales: averageGrossSales, averageNetSales: averageNetSales, averageOrders: averageOrders, averageProducts: averageProducts) + } + + + /// OrderStats struct initializer. + /// + public init(date: String, unit: String, quantity: String, fields: [String], data: [OrderStatsItem]?, totalGrossSales: Float, totalNetSales: Float, + totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { + self.date = date + self.unit = unit + self.quantity = quantity + self.fields = fields + self.data = data + self.totalGrossSales = totalGrossSales + self.totalNetSales = totalNetSales + self.totalOrders = totalOrders + self.totalProducts = totalProducts + self.averageGrossSales = averageGrossSales + self.averageNetSales = averageNetSales + self.averageOrders = averageOrders + self.averageProducts = averageProducts + } +} + + +/// Defines all of the OrderStats CodingKeys. +/// +private extension OrderStats { + + enum CodingKeys: String, CodingKey { + case date = "date" + case unit = "unit" + case quantity = "quantity" + case fields = "fields" + case data = "data" + case totalGrossSales = "total_gross_sales" + case totalNetSales = "total_net_sales" + case totalOrders = "total_orders" + case totalProducts = "total_products" + case averageGrossSales = "avg_gross_sales" + case averageNetSales = "avg_net_sales" + case averageOrders = "avg_orders" + case averageProducts = "avg_products" + } +} + + +// MARK: - Comparable Conformance +// +extension OrderStats: Comparable { + public static func == (lhs: OrderStats, rhs: OrderStats) -> Bool { + return lhs.date == rhs.date && + lhs.unit == rhs.unit && + lhs.quantity == rhs.quantity && + lhs.fields == rhs.fields && + lhs.totalGrossSales == rhs.totalGrossSales && + lhs.totalNetSales == rhs.totalNetSales && + lhs.totalOrders == rhs.totalOrders && + lhs.totalProducts == rhs.totalProducts && + lhs.averageGrossSales == rhs.averageGrossSales && + lhs.averageNetSales == rhs.averageNetSales && + lhs.averageOrders == rhs.averageOrders && + lhs.averageProducts == rhs.averageProducts + } + + public static func < (lhs: OrderStats, rhs: OrderStats) -> Bool { + return lhs.date < rhs.date || + (lhs.date == rhs.date && lhs.unit < rhs.unit) || + (lhs.date == rhs.date && lhs.unit == rhs.unit && lhs.quantity < rhs.quantity) + } +} diff --git a/Networking/Networking/Model/OrderStatItem.swift b/Networking/Networking/Model/OrderStatsItem.swift similarity index 91% rename from Networking/Networking/Model/OrderStatItem.swift rename to Networking/Networking/Model/OrderStatsItem.swift index 6ea8c945d11..d8ab395103d 100644 --- a/Networking/Networking/Model/OrderStatItem.swift +++ b/Networking/Networking/Model/OrderStatsItem.swift @@ -11,7 +11,7 @@ public enum OrderStatGranularity: String { /// Represents an single order stat during a specific period. /// -public struct OrderStatItem: Decodable { +public struct OrderStatsItem: Decodable { public let period: String public let orders: Int public let products: Int @@ -32,7 +32,7 @@ public struct OrderStatItem: Decodable { public let avgProductsPerOrder: Int - /// OrderStatItem struct initializer. + /// OrderStatsItem struct initializer. /// public init(period: String, orders: Int, products: Int, coupons: Int, couponDiscount: Int, totalSales: Int, totalTax: Int, totalShipping: Int, totalShippingTax: Int, totalRefund: Int, totalTaxRefund: Int, totalShippingRefund: Int, totalShippingTaxRefund: Int, @@ -59,9 +59,9 @@ public struct OrderStatItem: Decodable { } -/// Defines all of the OrderStatItem CodingKeys. +/// Defines all of the OrderStatsItem CodingKeys. /// -private extension OrderStatItem { +private extension OrderStatsItem { enum CodingKeys: String, CodingKey { case period = "period" @@ -88,8 +88,8 @@ private extension OrderStatItem { // MARK: - Comparable Conformance // -extension OrderStatItem: Comparable { - public static func == (lhs: OrderStatItem, rhs: OrderStatItem) -> Bool { +extension OrderStatsItem: Comparable { + public static func == (lhs: OrderStatsItem, rhs: OrderStatsItem) -> Bool { return lhs.period == rhs.period && lhs.orders == rhs.orders && lhs.products == rhs.products && @@ -110,7 +110,7 @@ extension OrderStatItem: Comparable { lhs.avgProductsPerOrder == rhs.avgProductsPerOrder } - public static func < (lhs: OrderStatItem, rhs: OrderStatItem) -> Bool { + public static func < (lhs: OrderStatsItem, rhs: OrderStatsItem) -> Bool { return lhs.period < rhs.period || (lhs.period == rhs.period && lhs.currency < rhs.currency) || (lhs.period == rhs.period && lhs.currency == rhs.currency && lhs.totalSales < rhs.totalSales) diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift index d061503fe9e..96205394fd5 100644 --- a/Networking/Networking/Remote/OrderStatsRemote.swift +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -15,7 +15,7 @@ public class OrderStatsRemote: Remote { /// - quantity: How many `unit`s to fetch /// - completion: Closure to be executed upon completion. /// - public func loadOrderStats(for siteID: Int, unit: OrderStatGranularity, latestDateToInclude: String, quantity: String, completion: @escaping ([OrderStatItem]?, Error?) -> Void) { + public func loadOrderStats(for siteID: Int, unit: OrderStatGranularity, latestDateToInclude: String, quantity: String, completion: @escaping (OrderStats?, Error?) -> Void) { let path = "\(Constants.sitesPath)/\(siteID)/\(Constants.orderStatsPath)/" let parameters = [ParameterKeys.unit: unit.rawValue, ParameterKeys.date: latestDateToInclude, diff --git a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift index c4b17853e9f..d93a734e2f3 100644 --- a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift @@ -6,13 +6,92 @@ import XCTest /// class OrderStatsMapperTests: XCTestCase { - /// Verifies that all of the OrderStatsItem Fields are parsed correctly. + /// Verifies that all of the day unit OrderStats fields are parsed correctly. /// - func testStatFieldsAreProperlyParsed() { -// let dayStatItems = mapOrderStatsWithDayUnitResponse() -// XCTAssertEqual(dayStatItems.count, 31) -// let testStat = dayStatItems[26] -// XCTAssertEqual(testStat.period, "2018-06-01") + func testDayUnitStatFieldsAreProperlyParsed() { + guard let dayStats = mapOrderStatsWithDayUnitResponse() else { + XCTFail() + return + } + + XCTAssertEqual(dayStats.unit, "day") + XCTAssertEqual(dayStats.date, "2018-06-08") + XCTAssertEqual(dayStats.quantity, "31") + XCTAssertEqual(dayStats.fields.count, 18) + XCTAssertEqual(dayStats.totalOrders, 9) + XCTAssertEqual(dayStats.totalProducts, 13) + XCTAssertEqual(dayStats.totalGrossSales, 439.23) + XCTAssertEqual(dayStats.totalNetSales, 438.24) + XCTAssertEqual(dayStats.averageGrossSales, 14.1687) + XCTAssertEqual(dayStats.averageNetSales, 14.1368) + XCTAssertEqual(dayStats.averageOrders, 0.2903) + XCTAssertEqual(dayStats.averageProducts, 0.4194) + } + + /// Verifies that all of the week unit OrderStats fields are parsed correctly. + /// + func testWeekUnitStatFieldsAreProperlyParsed() { + guard let weekStats = mapOrderStatsWithWeekUnitResponse() else { + XCTFail() + return + } + + XCTAssertEqual(weekStats.unit, "week") + XCTAssertEqual(weekStats.date, "2018-W30") + XCTAssertEqual(weekStats.quantity, "31") + XCTAssertEqual(weekStats.fields.count, 18) + XCTAssertEqual(weekStats.totalOrders, 65) + XCTAssertEqual(weekStats.totalProducts, 87) + XCTAssertEqual(weekStats.totalGrossSales, 2858.52) + XCTAssertEqual(weekStats.totalNetSales, 2833.55) + XCTAssertEqual(weekStats.averageGrossSales, 92.2103) + XCTAssertEqual(weekStats.averageNetSales, 91.4048) + XCTAssertEqual(weekStats.averageOrders, 2.0968) + XCTAssertEqual(weekStats.averageProducts, 2.8065) + } + + /// Verifies that all of the month unit OrderStats fields are parsed correctly. + /// + func testMonthUnitStatFieldsAreProperlyParsed() { + guard let monthStats = mapOrderStatsWithMonthUnitResponse() else { + XCTFail() + return + } + + XCTAssertEqual(monthStats.unit, "month") + XCTAssertEqual(monthStats.date, "2018-06") + XCTAssertEqual(monthStats.quantity, "12") + XCTAssertEqual(monthStats.fields.count, 18) + XCTAssertEqual(monthStats.totalOrders, 159) + XCTAssertEqual(monthStats.totalProducts, 243) + XCTAssertEqual(monthStats.totalGrossSales, 6830.590000000002) + XCTAssertEqual(monthStats.totalNetSales, 6717.232000000002) + XCTAssertEqual(monthStats.averageGrossSales, 569.2158) + XCTAssertEqual(monthStats.averageNetSales, 559.7693) + XCTAssertEqual(monthStats.averageOrders, 13.25) + XCTAssertEqual(monthStats.averageProducts, 20.25) + } + + /// Verifies that all of the year unit OrderStats fields are parsed correctly. + /// + func testYearUnitStatFieldsAreProperlyParsed() { + guard let yearStats = mapOrderStatsWithYearUnitResponse() else { + XCTFail() + return + } + + XCTAssertEqual(yearStats.unit, "year") + XCTAssertEqual(yearStats.date, "2018") + XCTAssertEqual(yearStats.quantity, "4") + XCTAssertEqual(yearStats.fields.count, 18) + XCTAssertEqual(yearStats.totalOrders, 293) + XCTAssertEqual(yearStats.totalProducts, 626) + XCTAssertEqual(yearStats.totalGrossSales, 10928.91999999999) + XCTAssertEqual(yearStats.totalNetSales, 10684.27199999999) + XCTAssertEqual(yearStats.averageGrossSales, 2732.23) + XCTAssertEqual(yearStats.averageNetSales, 2671.068) + XCTAssertEqual(yearStats.averageOrders, 73.25) + XCTAssertEqual(yearStats.averageProducts, 156.5) } } @@ -23,9 +102,9 @@ private extension OrderStatsMapperTests { /// Returns the OrderNotesMapper output upon receiving `filename` (Data Encoded) /// - func mapStatItems(from filename: String) -> [OrderStatItem] { + func mapStatItems(from filename: String) -> OrderStats? { guard let response = Loader.contentsOf(filename) else { - return [] + return nil } return try! OrderStatsMapper().map(response: response) @@ -33,25 +112,25 @@ private extension OrderStatsMapperTests { /// Returns the OrderStatsMapper output upon receiving `order-stats-day` /// - func mapOrderStatsWithDayUnitResponse() -> [OrderStatItem] { + func mapOrderStatsWithDayUnitResponse() -> OrderStats? { return mapStatItems(from: "order-stats-day") } /// Returns the OrderStatsMapper output upon receiving `order-stats-week` /// - func mapOrderStatsWithWeekUnitResponse() -> [OrderStatItem] { + func mapOrderStatsWithWeekUnitResponse() -> OrderStats? { return mapStatItems(from: "order-stats-week") } /// Returns the OrderStatsMapper output upon receiving `order-stats-month` /// - func mapOrderStatsWithMonthUnitResponse() -> [OrderStatItem] { + func mapOrderStatsWithMonthUnitResponse() -> OrderStats? { return mapStatItems(from: "order-stats-month") } /// Returns the OrderStatsMapper output upon receiving `order-stats-year` /// - func mapOrderStatsWithYearUnitResponse() -> [OrderStatItem] { + func mapOrderStatsWithYearUnitResponse() -> OrderStats? { return mapStatItems(from: "order-stats-year") } } diff --git a/Yosemite/Yosemite/Model/Model.swift b/Yosemite/Yosemite/Model/Model.swift index 08e8a48c7f0..c8f8d2fb29e 100644 --- a/Yosemite/Yosemite/Model/Model.swift +++ b/Yosemite/Yosemite/Model/Model.swift @@ -13,6 +13,7 @@ public typealias OrderItem = Networking.OrderItem public typealias OrderStatus = Networking.OrderStatus public typealias OrderCouponLine = Networking.OrderCouponLine public typealias OrderNote = Networking.OrderNote -public typealias OrderStatItem = Networking.OrderStatItem +public typealias OrderStats = Networking.OrderStats +public typealias OrderStatsItem = Networking.OrderStatsItem public typealias OrderStatGranularity = Networking.OrderStatGranularity public typealias Site = Networking.Site From faea35a8cf56e3a5c44f0f360df3adf66e1ef81d Mon Sep 17 00:00:00 2001 From: Aaron Douglas Date: Wed, 1 Aug 2018 07:42:52 -0500 Subject: [PATCH 019/112] Eliminate the .debug app ID and enable push notifications --- .../Resources/WooCommerce.debug.entitlements | 10 ---------- ...ease.entitlements => WooCommerce.entitlements} | 2 ++ WooCommerce/WooCommerce.xcodeproj/project.pbxproj | 15 ++++++++------- 3 files changed, 10 insertions(+), 17 deletions(-) delete mode 100644 WooCommerce/Resources/WooCommerce.debug.entitlements rename WooCommerce/Resources/{WooCommerce.release.entitlements => WooCommerce.entitlements} (84%) diff --git a/WooCommerce/Resources/WooCommerce.debug.entitlements b/WooCommerce/Resources/WooCommerce.debug.entitlements deleted file mode 100644 index 494ba6c8c77..00000000000 --- a/WooCommerce/Resources/WooCommerce.debug.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - keychain-access-groups - - $(AppIdentifierPrefix)com.automattic.woocommerce.debug - - - diff --git a/WooCommerce/Resources/WooCommerce.release.entitlements b/WooCommerce/Resources/WooCommerce.entitlements similarity index 84% rename from WooCommerce/Resources/WooCommerce.release.entitlements rename to WooCommerce/Resources/WooCommerce.entitlements index d3794fb0618..74205aedbb9 100644 --- a/WooCommerce/Resources/WooCommerce.release.entitlements +++ b/WooCommerce/Resources/WooCommerce.entitlements @@ -2,6 +2,8 @@ + aps-environment + production keychain-access-groups $(AppIdentifierPrefix)com.automattic.woocommerce diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 75613bce456..a0399a9584e 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -234,8 +234,7 @@ B58B4AB52108F11C00076FDD /* Notice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notice.swift; sourceTree = ""; }; B58B4AB72108F14700076FDD /* NoticeNotificationInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeNotificationInfo.swift; sourceTree = ""; }; B58B4ABF2108FF6100076FDD /* Array+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Helpers.swift"; sourceTree = ""; }; - B59F38DF20D40A24008C1829 /* WooCommerce.debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WooCommerce.debug.entitlements; sourceTree = ""; }; - B59F38E020D40A24008C1829 /* WooCommerce.release.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WooCommerce.release.entitlements; sourceTree = ""; }; + B59F38E020D40A24008C1829 /* WooCommerce.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WooCommerce.entitlements; sourceTree = ""; }; B5A82EE121025C450053ADC8 /* FulfillViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FulfillViewController.swift; sourceTree = ""; }; B5A82EE421025E550053ADC8 /* FulfillViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FulfillViewController.xib; sourceTree = ""; }; B5A82EE6210263460053ADC8 /* UIViewController+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Helpers.swift"; sourceTree = ""; }; @@ -499,8 +498,7 @@ CE1CCB4620570A6B000EE3AC /* en.lproj */, B56DB3D82049BFAA00D4AA8E /* Info.plist */, B56DB3D52049BFAA00D4AA8E /* LaunchScreen.storyboard */, - B59F38DF20D40A24008C1829 /* WooCommerce.debug.entitlements */, - B59F38E020D40A24008C1829 /* WooCommerce.release.entitlements */, + B59F38E020D40A24008C1829 /* WooCommerce.entitlements */, ); path = Resources; sourceTree = ""; @@ -796,6 +794,9 @@ com.apple.Keychain = { enabled = 1; }; + com.apple.Push = { + enabled = 1; + }; }; }; B56DB3DC2049BFAA00D4AA8E = { @@ -1280,7 +1281,7 @@ baseConfigurationReference = 90AC1C0B391E04A837BDC64E /* Pods-WooCommerce.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = Resources/WooCommerce.debug.entitlements; + CODE_SIGN_ENTITLEMENTS = Resources/WooCommerce.entitlements; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = PZYM8XX95Q; ENABLE_BITCODE = NO; @@ -1289,7 +1290,7 @@ INFOPLIST_PREPROCESS = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.automattic.woocommerce.debug; + PRODUCT_BUNDLE_IDENTIFIER = com.automattic.woocommerce; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = "WooCommerce Development"; @@ -1306,7 +1307,7 @@ baseConfigurationReference = 33035144757869DE5E4DC88A /* Pods-WooCommerce.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = Resources/WooCommerce.release.entitlements; + CODE_SIGN_ENTITLEMENTS = Resources/WooCommerce.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = PZYM8XX95Q; From d2d1b81dff654163913ec709666c8c4d826dc734 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 12:18:33 -0300 Subject: [PATCH 020/112] ResultsController: MutableType Alias --- Yosemite/Yosemite/Tools/ResultsController.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Yosemite/Yosemite/Tools/ResultsController.swift b/Yosemite/Yosemite/Tools/ResultsController.swift index e82c916e785..ad6b860edf8 100644 --- a/Yosemite/Yosemite/Tools/ResultsController.swift +++ b/Yosemite/Yosemite/Tools/ResultsController.swift @@ -4,9 +4,14 @@ import Storage +// MARK: - MutableType: Storage.framework Type that will be retrieved (and converted into ReadOnly) +// +public typealias ResultsControllerMutableType = NSManagedObject & ReadOnlyConvertible + + // MARK: - ResultsController // -public class ResultsController { +public class ResultsController { /// Managed Object Context used to fetch objects. /// From e1ec41193d58ae62d1a17111268a171a2e8ab2a8 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 12:19:02 -0300 Subject: [PATCH 021/112] Storage: Removing xcdatamodeld from build phase --- Storage/Storage.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/Storage/Storage.xcodeproj/project.pbxproj b/Storage/Storage.xcodeproj/project.pbxproj index 5a586eee76c..179de3b5924 100644 --- a/Storage/Storage.xcodeproj/project.pbxproj +++ b/Storage/Storage.xcodeproj/project.pbxproj @@ -33,7 +33,6 @@ B54CA5C320A4BF6900F38CD1 /* NSManagedObjectContextStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54CA5C120A4BF6900F38CD1 /* NSManagedObjectContextStorageTests.swift */; }; B54CA5C720A4BFDC00F38CD1 /* DummyStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54CA5C620A4BFDC00F38CD1 /* DummyStack.swift */; }; B54CA5C920A4C17800F38CD1 /* NSObject+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54CA5C820A4C17800F38CD1 /* NSObject+Storage.swift */; }; - B59E11DA20A9D00C004121A4 /* WooCommerce.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B59E11D820A9D00C004121A4 /* WooCommerce.xcdatamodeld */; }; B59E11DE20A9F1FB004121A4 /* CoreDataManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59E11DD20A9F1FB004121A4 /* CoreDataManagerTests.swift */; }; B59E11E020A9F5E6004121A4 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59E11DF20A9F5E6004121A4 /* Constants.swift */; }; B5B914C520EFF03500F2F832 /* Site+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B914C320EFF03500F2F832 /* Site+CoreDataClass.swift */; }; @@ -442,7 +441,6 @@ 7426A05520F69DA4002A4E07 /* OrderItem+CoreDataProperties.swift in Sources */, 7426A04720F68F27002A4E07 /* Order+CoreDataClass.swift in Sources */, B54CA5BD20A4BD3B00F38CD1 /* NSManagedObjectContext+Storage.swift in Sources */, - B59E11DA20A9D00C004121A4 /* WooCommerce.xcdatamodeld in Sources */, 74B7D6AD20F90CBB002667AC /* OrderNote+CoreDataClass.swift in Sources */, B52B0F7920AA287C00477698 /* StorageManagerType.swift in Sources */, 7426A05420F69DA4002A4E07 /* OrderItem+CoreDataClass.swift in Sources */, From ed098bfd9723d32e520bbc714971679085c18a31 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 15:59:01 -0300 Subject: [PATCH 022/112] Implements ResultsController+UIKit --- .../Tools/ResultsController+UIKit.swift | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 WooCommerce/Classes/Tools/ResultsController+UIKit.swift diff --git a/WooCommerce/Classes/Tools/ResultsController+UIKit.swift b/WooCommerce/Classes/Tools/ResultsController+UIKit.swift new file mode 100644 index 00000000000..0e9111b5454 --- /dev/null +++ b/WooCommerce/Classes/Tools/ResultsController+UIKit.swift @@ -0,0 +1,128 @@ +import Foundation +import Yosemite +import UIKit +import CocoaLumberjack + + + +// MARK: - ResultsTableAnimations: Defines the Animations to be applied during Table Update(s) +// +struct ResultsTableAnimations { + + /// TableViewRowAnimation to be applied during Delete OP's. + /// + let delete: UITableViewRowAnimation = .fade + + /// TableViewRowAnimation to be applied during Insert OP's. + /// + let insert: UITableViewRowAnimation = .fade + + /// TableViewRowAnimation to be applied during Move OP's. + /// + let move: UITableViewRowAnimation = .fade + + /// TableViewRowAnimation to be applied during Update OP's. + /// + let update: UITableViewRowAnimation = .fade + + /// Standard ResultsTableAnimations Settings + /// + static let standard = ResultsTableAnimations() +} + + +// MARK: - ResultsController >> UITableView Events Forwarder. +// +extension ResultsController { + + /// + /// + func startForwardingEvents(to tableView: UITableView, with animations: ResultsTableAnimations = .standard) { + startForwardingContentEvents(to: tableView, with: animations) + startForwardingObjectEvents(to: tableView, with: animations) + startForwardingSectionEvents(to: tableView, with: animations) + } +} + + +// MARK: - Private Event Forwarding Methods +// +private extension ResultsController { + + /// Sets up all of the Content Events from the inner FRC over to the specified TableView. + /// + func startForwardingContentEvents(to tableView: UITableView, with animations: ResultsTableAnimations) { + onWillChangeContent = { [weak tableView] in + tableView?.beginUpdates() + } + + onDidChangeContent = { [weak tableView] in + tableView?.endUpdates() + } + } + + /// Sets up all of the Object Events from the inner FRC over to the specified TableView. + /// + func startForwardingObjectEvents(to tableView: UITableView, with animations: ResultsTableAnimations) { + onDidChangeObject = { [weak tableView] (object, indexPath, type, newIndexPath) in + guard let `tableView` = tableView else { + return + } + + // Seriously, Apple? + // https://developer.apple.com/library/archive/releasenotes/iPhone/NSFetchedResultsChangeMoveReportedAsNSFetchedResultsChangeUpdate/index.html + // + let fixedType: ChangeType = { + guard type == .update && newIndexPath != nil && newIndexPath != indexPath else { + return type + } + + return .move + }() + + switch fixedType { + case .delete: + if let indexPath = indexPath { + tableView.deleteRows(at: [indexPath], with: animations.delete) + } + case .insert: + if let newIndexPath = newIndexPath { + tableView.insertRows(at: [newIndexPath], with: animations.insert) + } + case .move: + if let oldIndexPath = indexPath { + tableView.deleteRows(at: [oldIndexPath], with: animations.move) + } + + if let newIndexPath = newIndexPath { + tableView.insertRows(at: [newIndexPath], with: animations.move) + } + case .update: + if let indexPath = indexPath { + tableView.reloadRows(at: [indexPath], with: animations.update) + } + } + } + } + + /// Sets up all of the Section Events from the inner FRC over to the specified TableView. + /// + func startForwardingSectionEvents(to tableView: UITableView, with animations: ResultsTableAnimations) { + onDidChangeSection = { [weak tableView] (sectionInfo, sectionIndex, type) in + guard let `tableView` = tableView else { + return + } + + let sectionIndexSet = IndexSet(integer: sectionIndex) + + switch type { + case .delete: + tableView.deleteSections(sectionIndexSet, with: animations.delete) + case .insert: + tableView.insertSections(sectionIndexSet, with: animations.insert) + default: + DDLogError("## ResultsController: Unsupported Section Event: \(type)") + } + } + } +} From 6f3af9d8a319f1826c6dc832283e96d275108c93 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 15:59:15 -0300 Subject: [PATCH 023/112] Updates Project --- WooCommerce/WooCommerce.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index bbfff93f86b..fc595a3ac27 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ B50911322049E27A007D25DC /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509112F2049E27A007D25DC /* SettingsViewController.swift */; }; B53B898920D450AF00EDB467 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B898820D450AF00EDB467 /* SessionManagerTests.swift */; }; B53B898D20D462A000EDB467 /* StoresManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B898C20D462A000EDB467 /* StoresManager.swift */; }; + B54FBE552111F70700390F57 /* ResultsController+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54FBE542111F70700390F57 /* ResultsController+UIKit.swift */; }; B557652B20F681E800185843 /* StoreTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B557652A20F681E800185843 /* StoreTableViewCell.swift */; }; B557652D20F6827900185843 /* StoreTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B557652C20F6827900185843 /* StoreTableViewCell.xib */; }; B557DA1520979904005962F4 /* CustomerNoteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B557DA1320979904005962F4 /* CustomerNoteTableViewCell.swift */; }; @@ -196,6 +197,7 @@ B509112F2049E27A007D25DC /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; B53B898820D450AF00EDB467 /* SessionManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManagerTests.swift; sourceTree = ""; }; B53B898C20D462A000EDB467 /* StoresManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoresManager.swift; sourceTree = ""; }; + B54FBE542111F70700390F57 /* ResultsController+UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ResultsController+UIKit.swift"; sourceTree = ""; }; B557652A20F681E800185843 /* StoreTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreTableViewCell.swift; sourceTree = ""; }; B557652C20F6827900185843 /* StoreTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StoreTableViewCell.xib; sourceTree = ""; }; B557DA1320979904005962F4 /* CustomerNoteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomerNoteTableViewCell.swift; sourceTree = ""; }; @@ -416,6 +418,7 @@ B58B4ABC2108F7F800076FDD /* Notices */, B55D4C2620B717C000D7A50F /* UserAgent.swift */, B5BE368D20BED80B00BE0A8C /* SafariViewController.swift */, + B54FBE542111F70700390F57 /* ResultsController+UIKit.swift */, ); path = Tools; sourceTree = ""; @@ -1055,6 +1058,7 @@ CE855366209BA6A700938BDC /* CustomerInfoTableViewCell.swift in Sources */, B5A8532220BDBFAF00FAAB4D /* CircularImageView.swift in Sources */, CE1F51252064179A00C6C810 /* UILabel+Helpers.swift in Sources */, + B54FBE552111F70700390F57 /* ResultsController+UIKit.swift in Sources */, B50911302049E27A007D25DC /* DashboardViewController.swift in Sources */, B5D1AFB820BC510200DB0E8C /* UIImage+Woo.swift in Sources */, CE1EC8E920B8A3F5009762BF /* OrderNoteViewModel.swift in Sources */, From 22a0349ca80dd2aab46268444b378490332c6dcc Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 16:53:35 -0300 Subject: [PATCH 024/112] MockupStorage: Nukes viewContext --- Yosemite/YosemiteTests/Mockups/MockupStorage.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Yosemite/YosemiteTests/Mockups/MockupStorage.swift b/Yosemite/YosemiteTests/Mockups/MockupStorage.swift index fe4b356e9b5..a9689284338 100644 --- a/Yosemite/YosemiteTests/Mockups/MockupStorage.swift +++ b/Yosemite/YosemiteTests/Mockups/MockupStorage.swift @@ -17,12 +17,6 @@ public class MockupStorageManager: StorageManagerType { return persistentContainer.viewContext } - /// Returns the NSManagedObjectContext associated with the Main Thread. Convenience helper!! - /// - public var viewContext: NSManagedObjectContext { - return persistentContainer.viewContext - } - /// Persistent Container: Holds the full CoreData Stack /// public lazy var persistentContainer: NSPersistentContainer = { From 54a4b932437768e56136fcb00eaa295cf2d131c0 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 16:53:57 -0300 Subject: [PATCH 025/112] Implements MockupStorageManager:Sample --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 4 ++++ .../Mockups/MockupStorage+Sample.swift | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 Yosemite/YosemiteTests/Mockups/MockupStorage+Sample.swift diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 80617bbea07..975d620e12a 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 74B7D6B020F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B7D6AF20F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift */; }; 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */; }; B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */; }; + B53A56A0211245E0000776C9 /* MockupStorage+Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */; }; B53D89E520E6C22B00F90866 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53D89E420E6C22B00F90866 /* Model.swift */; }; B546CCF22093636A007CDA5F /* Yosemite.h in Headers */ = {isa = PBXBuildFile; fileRef = B546CCF12093636A007CDA5F /* Yosemite.h */; settings = {ATTRIBUTES = (Public, ); }; }; B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56C1EBD20EABD2B00D749F9 /* ResultsController.swift */; }; @@ -82,6 +83,7 @@ 991BBCE6E4A92F0A028885D8 /* Pods_YosemiteTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_YosemiteTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ABE0D84C5393D9EE2CCF2B7E /* Pods-YosemiteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YosemiteTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-YosemiteTests/Pods-YosemiteTests.release.xcconfig"; sourceTree = ""; }; B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Site+ReadOnlyConvertible.swift"; sourceTree = ""; }; + B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockupStorage+Sample.swift"; sourceTree = ""; }; B53D89E420E6C22B00F90866 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Model.swift; path = Yosemite/Model/Model.swift; sourceTree = SOURCE_ROOT; }; B546CCF12093636A007CDA5F /* Yosemite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Yosemite.h; sourceTree = ""; }; B56C1EBD20EABD2B00D749F9 /* ResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsController.swift; sourceTree = ""; }; @@ -296,6 +298,7 @@ B5C9DE202087FF20006B910A /* MockupAcount.swift */, B5C9DE212087FF20006B910A /* MockupSite.swift */, B5A01CA020D19C4700E3207E /* MockupStorage.swift */, + B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */, ); path = Mockups; sourceTree = ""; @@ -534,6 +537,7 @@ 74A7688E20D45ED400F9D437 /* OrderStoreTests.swift in Sources */, B5C9DE272087FF20006B910A /* MockupAcount.swift in Sources */, B5C9DE222087FF20006B910A /* DispatcherTests.swift in Sources */, + B53A56A0211245E0000776C9 /* MockupStorage+Sample.swift in Sources */, B5A01CA120D19C4700E3207E /* MockupStorage.swift in Sources */, B5C9DE242087FF20006B910A /* StoreTests.swift in Sources */, B5C9DE252087FF20006B910A /* MockupProcessor.swift in Sources */, diff --git a/Yosemite/YosemiteTests/Mockups/MockupStorage+Sample.swift b/Yosemite/YosemiteTests/Mockups/MockupStorage+Sample.swift new file mode 100644 index 00000000000..7735257521e --- /dev/null +++ b/Yosemite/YosemiteTests/Mockups/MockupStorage+Sample.swift @@ -0,0 +1,22 @@ +import Foundation +import Yosemite + + +// MARK: - MockupStorage Sample Entity Insertion Methods +// +extension MockupStorageManager { + + /// Inserts a new (Sample) account into the specified context. + /// + @discardableResult + func insertSampleAccount() -> StorageAccount { + let newAccount = viewStorage.insertNewObject(ofType: StorageAccount.self) + newAccount.userID = Int64(arc4random()) + newAccount.displayName = "Yosemite" + newAccount.email = "yosemite@yosemite" + newAccount.gravatarUrl = "https://something" + newAccount.username = "yosemite" + + return newAccount + } +} From df1bfb90bb5afe110bf2b22f7b265686b7d9986e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 16:54:09 -0300 Subject: [PATCH 026/112] Yosemite: Exporting Storage.Account --- Yosemite/Yosemite/Model/Model.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Yosemite/Yosemite/Model/Model.swift b/Yosemite/Yosemite/Model/Model.swift index fcc5982c6e7..7006506187f 100644 --- a/Yosemite/Yosemite/Model/Model.swift +++ b/Yosemite/Yosemite/Model/Model.swift @@ -19,5 +19,6 @@ public typealias Site = Networking.Site // MARK: - Exported Storage Symbols +public typealias StorageAccount = Storage.Account public typealias StorageSite = Storage.Site public typealias StorageOrder = Storage.Order From 17d93fc85a35aff435d2dccf054d3d002aed151c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 16:54:31 -0300 Subject: [PATCH 027/112] Yosemite: Simplifying ResultsController Tests --- .../Tools/ResultsControllerTests.swift | 42 +++++-------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift b/Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift index c948e8d40fe..9237c7bcafd 100644 --- a/Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift +++ b/Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift @@ -16,7 +16,7 @@ class ResultsControllerTests: XCTestCase { /// Returns the NSMOC associated to the Main Thread /// private var viewContext: NSManagedObjectContext { - return storage.viewContext + return storage.persistentContainer.viewContext } /// Returns a sample NSSortDescriptor @@ -49,7 +49,7 @@ class ResultsControllerTests: XCTestCase { /// Verifies that ResultsController does pick up pre-existant entities, right after performFetch runs. /// func testResultsControllerPicksUpEntitiesAvailablePriorToInstantiation() { - insertSampleAccount(into: viewContext) + storage.insertSampleAccount() viewContext.saveIfNeeded() let resultsController = ResultsController(viewContext: viewContext, sortedBy: [sampleSortDescriptor]) @@ -66,7 +66,7 @@ class ResultsControllerTests: XCTestCase { let resultsController = ResultsController(viewContext: viewContext, sortedBy: [sampleSortDescriptor]) try? resultsController.performFetch() - insertSampleAccount(into: viewContext) + storage.insertSampleAccount() viewContext.saveIfNeeded() XCTAssertEqual(resultsController.sections.count, 1) @@ -83,7 +83,7 @@ class ResultsControllerTests: XCTestCase { let numberOfAccounts = 100 for _ in 0 ..< numberOfAccounts { - insertSampleAccount(into: viewContext) + storage.insertSampleAccount() } viewContext.saveIfNeeded() @@ -103,7 +103,7 @@ class ResultsControllerTests: XCTestCase { let resultsController = ResultsController(viewContext: viewContext, sectionNameKeyPath: sectionNameKeyPath, sortedBy: [sampleSortDescriptor]) try? resultsController.performFetch() - let mutableAccount = insertSampleAccount(into: viewContext) + let mutableAccount = storage.insertSampleAccount() viewContext.saveIfNeeded() let indexPath = IndexPath(row: 0, section: 0) @@ -131,7 +131,7 @@ class ResultsControllerTests: XCTestCase { didChangeObjectWasCalled = true } - insertSampleAccount(into: viewContext) + storage.insertSampleAccount() viewContext.saveIfNeeded() waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) @@ -154,7 +154,7 @@ class ResultsControllerTests: XCTestCase { expectation.fulfill() } - insertSampleAccount(into: viewContext) + storage.insertSampleAccount() viewContext.saveIfNeeded() waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) @@ -176,7 +176,7 @@ class ResultsControllerTests: XCTestCase { expectation.fulfill() } - insertSampleAccount(into: viewContext) + storage.insertSampleAccount() viewContext.saveIfNeeded() waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) @@ -196,7 +196,7 @@ class ResultsControllerTests: XCTestCase { expectation.fulfill() } - insertSampleAccount(into: viewContext) + storage.insertSampleAccount() viewContext.saveIfNeeded() waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) @@ -210,8 +210,8 @@ class ResultsControllerTests: XCTestCase { let resultsController = ResultsController(viewContext: viewContext, sortedBy: [sortDescriptor]) try? resultsController.performFetch() - let first = insertSampleAccount(into: viewContext).toReadOnly() - let second = insertSampleAccount(into: viewContext).toReadOnly() + let first = storage.insertSampleAccount().toReadOnly() + let second = storage.insertSampleAccount().toReadOnly() let expected = [first.userID: first, second.userID: second] viewContext.saveIfNeeded() @@ -221,23 +221,3 @@ class ResultsControllerTests: XCTestCase { } } } - - -// MARK: - Private Helpers -// -private extension ResultsControllerTests { - - /// Inserts a new (Sample) account into the specified context. - /// - @discardableResult - func insertSampleAccount(into context: NSManagedObjectContext) -> Storage.Account { - let newAccount = context.insertNewObject(ofType: Storage.Account.self) - newAccount.userID = Int64(arc4random()) - newAccount.displayName = "Yosemite" - newAccount.email = "yosemite@yosemite" - newAccount.gravatarUrl = "https://something" - newAccount.username = "yosemite" - - return newAccount - } -} From 0eab0052d0c8e42db8be4d16d2bb4e197ebbc0aa Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 17:13:50 -0300 Subject: [PATCH 028/112] Implements MockupTableView --- .../Mockups/MockupTableView.swift | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 WooCommerce/WooCommerceTests/Mockups/MockupTableView.swift diff --git a/WooCommerce/WooCommerceTests/Mockups/MockupTableView.swift b/WooCommerce/WooCommerceTests/Mockups/MockupTableView.swift new file mode 100644 index 00000000000..ad161965a89 --- /dev/null +++ b/WooCommerce/WooCommerceTests/Mockups/MockupTableView.swift @@ -0,0 +1,68 @@ +import Foundation +import UIKit + + +// MARK: - UITableView Mockup +// +class MockupTableView: UITableView { + + /// Closure to be executed whenever `beginUpdates` is called. + /// + var onBeginUpdates: (() -> Void)? + + /// Closure to be executed whenever `endUpdates` is called. + /// + var onEndUpdates: (() -> Void)? + + /// Closure to be executed whenever `insertRows` is called. + /// + var onInsertedRows: (([IndexPath]) -> Void)? + + /// Closure to be executed whenever `deleteRows` is called. + /// + var onDeletedRows: (([IndexPath]) -> Void)? + + /// Closure to be executed whenever `reloadRows` is called. + /// + var onReloadRows: (([IndexPath]) -> Void)? + + /// Closure to be executed whenever `deleteSections` is called. + /// + var onDeleteSections: ((IndexSet) -> Void)? + + /// Closure to be executed whenever `insertSections` is called. + /// + var onInsertSections: ((IndexSet) -> Void)? + + + + // MARK: - Overridden Methods + + override func beginUpdates() { + onBeginUpdates?() + } + + override func endUpdates() { + onEndUpdates?() + } + + override func insertRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation) { + onInsertedRows?(indexPaths) + } + + override func deleteRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation) { + onDeletedRows?(indexPaths) + } + + override func reloadRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation) { + onReloadRows?(indexPaths) + } + + override func deleteSections(_ sections: IndexSet, with animation: UITableViewRowAnimation) { + onDeleteSections?(sections) + } + + override func insertSections(_ sections: IndexSet, with animation: UITableViewRowAnimation) { + onInsertSections?(sections) + } +} From e695ab115cbb244b2211a2b743e1507480f34d6a Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 17:24:10 -0300 Subject: [PATCH 029/112] MockupTableView: Fixing Typo --- .../WooCommerceTests/Mockups/MockupTableView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/WooCommerceTests/Mockups/MockupTableView.swift b/WooCommerce/WooCommerceTests/Mockups/MockupTableView.swift index ad161965a89..3cc3126389a 100644 --- a/WooCommerce/WooCommerceTests/Mockups/MockupTableView.swift +++ b/WooCommerce/WooCommerceTests/Mockups/MockupTableView.swift @@ -28,11 +28,11 @@ class MockupTableView: UITableView { /// Closure to be executed whenever `deleteSections` is called. /// - var onDeleteSections: ((IndexSet) -> Void)? + var onDeletedSections: ((IndexSet) -> Void)? /// Closure to be executed whenever `insertSections` is called. /// - var onInsertSections: ((IndexSet) -> Void)? + var onInsertedSections: ((IndexSet) -> Void)? @@ -59,10 +59,10 @@ class MockupTableView: UITableView { } override func deleteSections(_ sections: IndexSet, with animation: UITableViewRowAnimation) { - onDeleteSections?(sections) + onDeletedSections?(sections) } override func insertSections(_ sections: IndexSet, with animation: UITableViewRowAnimation) { - onInsertSections?(sections) + onInsertedSections?(sections) } } From a36a1cc52a5a7162cfaa345a3c427d0a65f0d508 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 17:24:23 -0300 Subject: [PATCH 030/112] Implements ResultsControllerUIKitTests --- .../Tools/ResultsControllerUIKitTests.swift | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 WooCommerce/WooCommerceTests/Tools/ResultsControllerUIKitTests.swift diff --git a/WooCommerce/WooCommerceTests/Tools/ResultsControllerUIKitTests.swift b/WooCommerce/WooCommerceTests/Tools/ResultsControllerUIKitTests.swift new file mode 100644 index 00000000000..83fd02e780d --- /dev/null +++ b/WooCommerce/WooCommerceTests/Tools/ResultsControllerUIKitTests.swift @@ -0,0 +1,183 @@ +import XCTest +import Yosemite +@testable import WooCommerce + + +/// StoresManager Unit Tests +/// +class ResultsControllerUIKitTests: XCTestCase { + + /// Mockup StorageManager + /// + private var storageManager: MockupStorageManager! + + /// Mockup TableView + /// + private var tableView: MockupTableView! + + /// Sample ResultsController + /// + private var resultsController: ResultsController! + + + // MARK: - Overridden Methods + + override func setUp() { + storageManager = MockupStorageManager() + tableView = MockupTableView() + + resultsController = { + let viewContext = storageManager.persistentContainer.viewContext + let sectionNameKeyPath = "username" + let descriptor = NSSortDescriptor(keyPath: \StorageAccount.userID, ascending: false) + + return ResultsController(viewContext: viewContext, sectionNameKeyPath: sectionNameKeyPath, sortedBy: [descriptor]) + }() + + resultsController.startForwardingEvents(to: tableView) + try? resultsController.performFetch() + } + + + /// Verifies that `beginUpdates` + `endUpdates` are called in sequence. + /// + func testBeginAndEndUpdatesAreProperlyExecutedBeforeAndAfterPerformingUpdates() { + let expectation = self.expectation(description: "BeginUpdates Goes First") + expectation.expectedFulfillmentCount = 2 + expectation.assertForOverFulfill = true + + var beginUpdatesWasExecuted = false + + tableView.onBeginUpdates = { + beginUpdatesWasExecuted = true + expectation.fulfill() + } + + tableView.onEndUpdates = { + XCTAssertTrue(beginUpdatesWasExecuted) + expectation.fulfill() + } + + storageManager.insertSampleAccount() + waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) + } + + /// Verifies that inserted entities result in `tableView.insertRows` + /// + func testAddingAnEntityResultsInNewRows() { + let expectation = self.expectation(description: "Entity Insertion triggers Row Insertion") + + tableView.onInsertedRows = { rows in + XCTAssertEqual(rows.count, 1) + expectation.fulfill() + } + + tableView.onReloadRows = { _ in + XCTFail() + } + + tableView.onDeletedRows = { _ in + XCTFail() + } + + storageManager.insertSampleAccount() + waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) + } + + + /// Verifies that deleted entities result in `tableView.deleteRows`. + /// + func testDeletingAnEntityResultsInDeletedRows() { + let expectation = self.expectation(description: "Entity Deletion triggers Row Removal") + + let account = storageManager.insertSampleAccount() + storageManager.viewStorage.saveIfNeeded() + + tableView.onDeletedRows = { rows in + XCTAssertEqual(rows.count, 1) + expectation.fulfill() + } + + tableView.onInsertedRows = { _ in + XCTFail() + } + + tableView.onReloadRows = { _ in + XCTFail() + } + + storageManager.viewStorage.deleteObject(account) + storageManager.viewStorage.saveIfNeeded() + waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) + } + + /// Verifies that updated entities result in `tableView.reloadRows`. + /// + func testUpdatedEntityResultsInReloadedRows() { + let expectation = self.expectation(description: "Entity Update triggers Row Reload") + + let account = storageManager.insertSampleAccount() + storageManager.viewStorage.saveIfNeeded() + + tableView.onDeletedRows = { _ in + XCTFail() + } + + tableView.onInsertedRows = { _ in + XCTFail() + } + + tableView.onReloadRows = { rows in + XCTAssertEqual(rows.count, 1) + expectation.fulfill() + } + + account.displayName = "Updated!" + storageManager.viewStorage.saveIfNeeded() + waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) + } + + /// Verifies that whenever entities are updated so that they match the "New Section Criteria", `tableView.insertSections` is + /// effectively called. + /// + func testInsertSectionsIsExecutedWheneverEntitiesMatchNewSectionsCriteria() { + let expectation = self.expectation(description: "SectionKeyPath Update Results in New Section") + + let first = storageManager.insertSampleAccount() + let _ = storageManager.insertSampleAccount() + storageManager.viewStorage.saveIfNeeded() + + tableView.onInsertedSections = { indexSet in + expectation.fulfill() + } + + tableView.onDeletedSections = { indexSet in + XCTFail() + } + + first.username = "Something Different Here!" + storageManager.viewStorage.saveIfNeeded() + waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) + } + + /// Verifies that deleting the last Entity gets mapped to `tableView.deleteSections`. + /// + func testDeletingLastEntityResultsInDeletedSection() { + let expectation = self.expectation(description: "Zero Entities results in Deleted Sections") + + let first = storageManager.insertSampleAccount() + storageManager.viewStorage.saveIfNeeded() + + tableView.onInsertedSections = { indexSet in + XCTFail() + } + + tableView.onDeletedSections = { indexSet in + expectation.fulfill() + } + + storageManager.viewStorage.deleteObject(first) + storageManager.viewStorage.saveIfNeeded() + waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) + } +} From 791b528778c13e960ea6a46d5049dfd4468c45ba Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 17:28:55 -0300 Subject: [PATCH 031/112] ResultsController+UIKit: Adds missing documentation --- WooCommerce/Classes/Tools/ResultsController+UIKit.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/Classes/Tools/ResultsController+UIKit.swift b/WooCommerce/Classes/Tools/ResultsController+UIKit.swift index 0e9111b5454..152b11e4c13 100644 --- a/WooCommerce/Classes/Tools/ResultsController+UIKit.swift +++ b/WooCommerce/Classes/Tools/ResultsController+UIKit.swift @@ -35,7 +35,8 @@ struct ResultsTableAnimations { // extension ResultsController { - /// + /// Forwards Events to the specified UITableView Instance. We'll take care of Inserting / Deleting / Moving and Reloading + /// Rows and Sections. /// func startForwardingEvents(to tableView: UITableView, with animations: ResultsTableAnimations = .standard) { startForwardingContentEvents(to: tableView, with: animations) From e1af80ecb63a326883c410b95ab70d3b42b3ec53 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 1 Aug 2018 17:30:51 -0300 Subject: [PATCH 032/112] Updates WooCommerce Project --- .../WooCommerce.xcodeproj/project.pbxproj | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index fc595a3ac27..d17e0246912 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -32,6 +32,11 @@ B50911302049E27A007D25DC /* DashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509112D2049E27A007D25DC /* DashboardViewController.swift */; }; B50911312049E27A007D25DC /* OrdersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509112E2049E27A007D25DC /* OrdersViewController.swift */; }; B50911322049E27A007D25DC /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509112F2049E27A007D25DC /* SettingsViewController.swift */; }; + B53A569721123D3B000776C9 /* ResultsControllerUIKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569621123D3B000776C9 /* ResultsControllerUIKitTests.swift */; }; + B53A569B21123E8E000776C9 /* MockupTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569A21123E8E000776C9 /* MockupTableView.swift */; }; + B53A569D21123EEB000776C9 /* MockupStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569C21123EEB000776C9 /* MockupStorage.swift */; }; + B53A56A22112470C000776C9 /* MockupStorage+Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A56A12112470C000776C9 /* MockupStorage+Sample.swift */; }; + B53A56A42112483E000776C9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A56A32112483D000776C9 /* Constants.swift */; }; B53B898920D450AF00EDB467 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B898820D450AF00EDB467 /* SessionManagerTests.swift */; }; B53B898D20D462A000EDB467 /* StoresManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B898C20D462A000EDB467 /* StoresManager.swift */; }; B54FBE552111F70700390F57 /* ResultsController+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54FBE542111F70700390F57 /* ResultsController+UIKit.swift */; }; @@ -195,6 +200,11 @@ B509112D2049E27A007D25DC /* DashboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashboardViewController.swift; sourceTree = ""; }; B509112E2049E27A007D25DC /* OrdersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrdersViewController.swift; sourceTree = ""; }; B509112F2049E27A007D25DC /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + B53A569621123D3B000776C9 /* ResultsControllerUIKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsControllerUIKitTests.swift; sourceTree = ""; }; + B53A569A21123E8E000776C9 /* MockupTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockupTableView.swift; sourceTree = ""; }; + B53A569C21123EEB000776C9 /* MockupStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MockupStorage.swift; path = ../../../Yosemite/YosemiteTests/Mockups/MockupStorage.swift; sourceTree = ""; }; + B53A56A12112470C000776C9 /* MockupStorage+Sample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "MockupStorage+Sample.swift"; path = "../../../Yosemite/YosemiteTests/Mockups/MockupStorage+Sample.swift"; sourceTree = ""; }; + B53A56A32112483D000776C9 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = ../../Yosemite/YosemiteTests/Settings/Constants.swift; sourceTree = ""; }; B53B898820D450AF00EDB467 /* SessionManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManagerTests.swift; sourceTree = ""; }; B53B898C20D462A000EDB467 /* StoresManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoresManager.swift; sourceTree = ""; }; B54FBE542111F70700390F57 /* ResultsController+UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ResultsController+UIKit.swift"; sourceTree = ""; }; @@ -346,6 +356,9 @@ isa = PBXGroup; children = ( 746791652108D87B007CF1DC /* MockupAnalyticsProvider.swift */, + B53A569C21123EEB000776C9 /* MockupStorage.swift */, + B53A56A12112470C000776C9 /* MockupStorage+Sample.swift */, + B53A569A21123E8E000776C9 /* MockupTableView.swift */, ); path = Mockups; sourceTree = ""; @@ -372,6 +385,14 @@ name = Frameworks; sourceTree = ""; }; + B53A569521123D27000776C9 /* Tools */ = { + isa = PBXGroup; + children = ( + B53A569621123D3B000776C9 /* ResultsControllerUIKitTests.swift */, + ); + path = Tools; + sourceTree = ""; + }; B53B898A20D4606400EDB467 /* System */ = { isa = PBXGroup; children = ( @@ -451,9 +472,11 @@ B56DB3E02049BFAA00D4AA8E /* WooCommerceTests */ = { isa = PBXGroup; children = ( + B53A56A32112483D000776C9 /* Constants.swift */, B57C744F20F56ED300EEFC87 /* Extensions */, B53B898A20D4606400EDB467 /* System */, 746791642108D853007CF1DC /* Mockups */, + B53A569521123D27000776C9 /* Tools */, B5DBF3C120E1482900B53AED /* Yosemite */, B56DB3E32049BFAA00D4AA8E /* Info.plist */, ); @@ -1099,9 +1122,14 @@ files = ( B53B898920D450AF00EDB467 /* SessionManagerTests.swift in Sources */, B57C745120F56EE900EEFC87 /* UITableViewCellHelpersTests.swift in Sources */, + B53A569D21123EEB000776C9 /* MockupStorage.swift in Sources */, + B53A569B21123E8E000776C9 /* MockupTableView.swift in Sources */, 746791632108D7C0007CF1DC /* WooAnalyticsTests.swift in Sources */, + B53A56A42112483E000776C9 /* Constants.swift in Sources */, + B53A56A22112470C000776C9 /* MockupStorage+Sample.swift in Sources */, 746791662108D87B007CF1DC /* MockupAnalyticsProvider.swift in Sources */, B5DBF3C320E1484400B53AED /* StoresManagerTests.swift in Sources */, + B53A569721123D3B000776C9 /* ResultsControllerUIKitTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From dab1e975c212e82686e1ab6a6f97467958c4901d Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Wed, 1 Aug 2018 15:59:49 -0500 Subject: [PATCH 033/112] WIP: Added AnyCodable --- .../Networking.xcodeproj/project.pbxproj | 12 ++ Networking/Networking/Model/OrderStats.swift | 26 ++- .../Networking/Model/OrderStatsItem.swift | 108 +++------- .../Networking/Network/Tools/AnyCodable.swift | 98 +++++++++ .../Network/Tools/AnyDecodable.swift | 142 ++++++++++++ .../Network/Tools/AnyEncodable.swift | 203 ++++++++++++++++++ .../Mapper/OrderStatsMapperTests.swift | 4 + 7 files changed, 501 insertions(+), 92 deletions(-) create mode 100755 Networking/Networking/Network/Tools/AnyCodable.swift create mode 100755 Networking/Networking/Network/Tools/AnyDecodable.swift create mode 100755 Networking/Networking/Network/Tools/AnyEncodable.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 7a53dead527..33b8d57a233 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -14,6 +14,9 @@ 743FDB9D210FB36900AC737F /* order-stats-year.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9A210FB36900AC737F /* order-stats-year.json */; }; 743FDB9E210FB36900AC737F /* order-stats-week.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9B210FB36900AC737F /* order-stats-week.json */; }; 743FDBA0210FB3E500AC737F /* OrderStatsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743FDB9F210FB3E500AC737F /* OrderStatsMapperTests.swift */; }; + 7452387221124B7700A973CD /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7452386F21124B7700A973CD /* AnyEncodable.swift */; }; + 7452387321124B7700A973CD /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7452387021124B7700A973CD /* AnyCodable.swift */; }; + 7452387421124B7700A973CD /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7452387121124B7700A973CD /* AnyDecodable.swift */; }; 748D4248210F89ED00CF7D1B /* OrderStatsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */; }; 748D424A210F92EA00CF7D1B /* OrderStatsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */; }; 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */; }; @@ -91,6 +94,9 @@ 743FDB9A210FB36900AC737F /* order-stats-year.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-year.json"; sourceTree = ""; }; 743FDB9B210FB36900AC737F /* order-stats-week.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-week.json"; sourceTree = ""; }; 743FDB9F210FB3E500AC737F /* OrderStatsMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatsMapperTests.swift; sourceTree = ""; }; + 7452386F21124B7700A973CD /* AnyEncodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = ""; }; + 7452387021124B7700A973CD /* AnyCodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCodable.swift; sourceTree = ""; }; + 7452387121124B7700A973CD /* AnyDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = ""; }; 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsRemote.swift; sourceTree = ""; }; 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsItem.swift; sourceTree = ""; }; 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsMapper.swift; sourceTree = ""; }; @@ -193,6 +199,9 @@ isa = PBXGroup; children = ( B518663420A0A2E800037A38 /* Loader.swift */, + 7452387021124B7700A973CD /* AnyCodable.swift */, + 7452387121124B7700A973CD /* AnyDecodable.swift */, + 7452386F21124B7700A973CD /* AnyEncodable.swift */, ); name = Tools; path = Network/Tools; @@ -595,6 +604,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7452387421124B7700A973CD /* AnyDecodable.swift in Sources */, B56C1EB620EA757B00D749F9 /* SiteListMapper.swift in Sources */, B557DA1A20979D66005962F4 /* Settings.swift in Sources */, 74A1196C2110F4BB00E1E5F0 /* OrderStats.swift in Sources */, @@ -605,6 +615,7 @@ B567AF2520A0CCA300AB6C62 /* AuthenticatedRequest.swift in Sources */, B505F6EA20BEFC3700BB1B69 /* MockupNetwork.swift in Sources */, 748D4248210F89ED00CF7D1B /* OrderStatsRemote.swift in Sources */, + 7452387221124B7700A973CD /* AnyEncodable.swift in Sources */, B557DA0220975500005962F4 /* JetpackRequest.swift in Sources */, B56C1EB820EA76F500D749F9 /* Site.swift in Sources */, B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */, @@ -612,6 +623,7 @@ B557DA1D20979E7D005962F4 /* Order.swift in Sources */, B557DA0320975500005962F4 /* Remote.swift in Sources */, 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */, + 7452387321124B7700A973CD /* AnyCodable.swift in Sources */, B567AF2920A0FA1E00AB6C62 /* Mapper.swift in Sources */, B518662220A097C200037A38 /* Network.swift in Sources */, B518662420A099BF00037A38 /* AlamofireNetwork.swift in Sources */, diff --git a/Networking/Networking/Model/OrderStats.swift b/Networking/Networking/Model/OrderStats.swift index 6ca6916f56f..53458cc707a 100644 --- a/Networking/Networking/Model/OrderStats.swift +++ b/Networking/Networking/Model/OrderStats.swift @@ -1,6 +1,16 @@ import Foundation +/// Represents the granularity of a specific `OrderStats` +/// +public enum OrderStatGranularity: String { + case day = "day" + case week = "week" + case month = "month" + case year = "year" +} + + /// Represents order stats over a specific period. /// public struct OrderStats: Decodable { @@ -8,7 +18,7 @@ public struct OrderStats: Decodable { public let unit: String public let quantity: String public let fields: [String] - public let data: [OrderStatsItem]? + public let orderStatsItems: [[AnyCodable]]? public let totalGrossSales: Float public let totalNetSales: Float public let totalOrders: Int @@ -28,10 +38,8 @@ public struct OrderStats: Decodable { let unit = try container.decode(String.self, forKey: .unit) let quantity = try container.decode(String.self, forKey: .quantity) - // FIXME: Decode data into [OrderStatItem] let fields = try container.decode([String].self, forKey: .fields) - let data: [OrderStatsItem]? = nil - //// + let orderStatsItems = try container.decode([[AnyCodable]].self, forKey: .orderStatsItems) let totalGrossSales = try container.decode(Float.self, forKey: .totalGrossSales) let totalNetSales = try container.decode(Float.self, forKey: .totalNetSales) @@ -43,19 +51,18 @@ public struct OrderStats: Decodable { let averageOrders = try container.decode(Float.self, forKey: .averageOrders) let averageProducts = try container.decode(Float.self, forKey: .averageProducts) - self.init(date: date, unit: unit, quantity: quantity, fields: fields, data: data, totalGrossSales: totalGrossSales, totalNetSales: totalNetSales, totalOrders: totalOrders, totalProducts: totalProducts, averageGrossSales: averageGrossSales, averageNetSales: averageNetSales, averageOrders: averageOrders, averageProducts: averageProducts) + self.init(date: date, unit: unit, quantity: quantity, fields: fields, orderStatsItems: orderStatsItems, totalGrossSales: totalGrossSales, totalNetSales: totalNetSales, totalOrders: totalOrders, totalProducts: totalProducts, averageGrossSales: averageGrossSales, averageNetSales: averageNetSales, averageOrders: averageOrders, averageProducts: averageProducts) } /// OrderStats struct initializer. /// - public init(date: String, unit: String, quantity: String, fields: [String], data: [OrderStatsItem]?, totalGrossSales: Float, totalNetSales: Float, - totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { + public init(date: String, unit: String, quantity: String, fields: [String], orderStatsItems: [[AnyCodable]]?, totalGrossSales: Float, totalNetSales: Float, totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { self.date = date self.unit = unit self.quantity = quantity self.fields = fields - self.data = data + self.orderStatsItems = orderStatsItems self.totalGrossSales = totalGrossSales self.totalNetSales = totalNetSales self.totalOrders = totalOrders @@ -77,7 +84,7 @@ private extension OrderStats { case unit = "unit" case quantity = "quantity" case fields = "fields" - case data = "data" + case orderStatsItems = "data" case totalGrossSales = "total_gross_sales" case totalNetSales = "total_net_sales" case totalOrders = "total_orders" @@ -98,6 +105,7 @@ extension OrderStats: Comparable { lhs.unit == rhs.unit && lhs.quantity == rhs.quantity && lhs.fields == rhs.fields && + lhs.orderStatsItems == rhs.orderStatsItems && lhs.totalGrossSales == rhs.totalGrossSales && lhs.totalNetSales == rhs.totalNetSales && lhs.totalOrders == rhs.totalOrders && diff --git a/Networking/Networking/Model/OrderStatsItem.swift b/Networking/Networking/Model/OrderStatsItem.swift index d8ab395103d..7736e627b21 100644 --- a/Networking/Networking/Model/OrderStatsItem.swift +++ b/Networking/Networking/Model/OrderStatsItem.swift @@ -1,69 +1,43 @@ import Foundation -/// Represents the granularity of a given stat -/// -public enum OrderStatGranularity: String { - case day = "day" - case week = "week" - case month = "month" - case year = "year" -} /// Represents an single order stat during a specific period. /// public struct OrderStatsItem: Decodable { - public let period: String - public let orders: Int - public let products: Int - public let coupons: Int - public let couponDiscount: Int - public let totalSales: Int - public let totalTax: Int - public let totalShipping: Int - public let totalShippingTax: Int - public let totalRefund: Int - public let totalTaxRefund: Int - public let totalShippingRefund: Int - public let totalShippingTaxRefund: Int - public let currency: String - public let grossSales: Int - public let netSales: Int - public let avgOrderValue: Int - public let avgProductsPerOrder: Int - + public let data: [AnyCodable] /// OrderStatsItem struct initializer. /// - public init(period: String, orders: Int, products: Int, coupons: Int, couponDiscount: Int, totalSales: Int, totalTax: Int, totalShipping: Int, - totalShippingTax: Int, totalRefund: Int, totalTaxRefund: Int, totalShippingRefund: Int, totalShippingTaxRefund: Int, - currency: String, grossSales: Int, netSales: Int, avgOrderValue: Int, avgProductsPerOrder: Int) { - self.period = period - self.orders = orders - self.products = products - self.coupons = coupons - self.couponDiscount = couponDiscount - self.totalSales = totalSales - self.totalTax = totalTax - self.totalShipping = totalShipping - self.totalShippingTax = totalShippingTax - self.totalRefund = totalRefund - self.totalTaxRefund = totalTaxRefund - self.totalShippingRefund = totalShippingRefund - self.totalShippingTaxRefund = totalShippingTaxRefund - self.currency = currency - self.grossSales = grossSales - self.netSales = netSales - self.avgOrderValue = avgOrderValue - self.avgProductsPerOrder = avgProductsPerOrder + public init(data: [AnyCodable]) { + self.data = data + } + + /// The public initializer for OrderStatsItem. + /// + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + let data = try container.decode([AnyCodable].self) + self.init(data: data) // initialize the struct } } -/// Defines all of the OrderStatsItem CodingKeys. -/// +// MARK: - Equatable Conformance +// +extension OrderStatsItem: Equatable { + public static func == (lhs: OrderStatsItem, rhs: OrderStatsItem) -> Bool { + return lhs.data == rhs.data + } +} + + +// MARK: - Constants! +// private extension OrderStatsItem { - enum CodingKeys: String, CodingKey { + /// Defines all of the possbile fields for an OrderStatsItem. + /// + enum Fields: String { case period = "period" case orders = "orders" case products = "products" @@ -84,35 +58,3 @@ private extension OrderStatsItem { case avgProductsPerOrder = "avg_products_per_order" } } - - -// MARK: - Comparable Conformance -// -extension OrderStatsItem: Comparable { - public static func == (lhs: OrderStatsItem, rhs: OrderStatsItem) -> Bool { - return lhs.period == rhs.period && - lhs.orders == rhs.orders && - lhs.products == rhs.products && - lhs.coupons == rhs.coupons && - lhs.couponDiscount == rhs.couponDiscount && - lhs.totalSales == rhs.totalSales && - lhs.totalTax == rhs.totalTax && - lhs.totalShipping == rhs.totalShipping && - lhs.totalShippingTax == rhs.totalShippingTax && - lhs.totalRefund == rhs.totalRefund && - lhs.totalTaxRefund == rhs.totalTaxRefund && - lhs.totalShippingRefund == rhs.totalShippingRefund && - lhs.totalShippingTaxRefund == rhs.totalShippingTaxRefund && - lhs.currency == rhs.currency && - lhs.grossSales == rhs.grossSales && - lhs.netSales == rhs.netSales && - lhs.avgOrderValue == rhs.avgOrderValue && - lhs.avgProductsPerOrder == rhs.avgProductsPerOrder - } - - public static func < (lhs: OrderStatsItem, rhs: OrderStatsItem) -> Bool { - return lhs.period < rhs.period || - (lhs.period == rhs.period && lhs.currency < rhs.currency) || - (lhs.period == rhs.period && lhs.currency == rhs.currency && lhs.totalSales < rhs.totalSales) - } -} diff --git a/Networking/Networking/Network/Tools/AnyCodable.swift b/Networking/Networking/Network/Tools/AnyCodable.swift new file mode 100755 index 00000000000..57f48aa3d9c --- /dev/null +++ b/Networking/Networking/Network/Tools/AnyCodable.swift @@ -0,0 +1,98 @@ +// Code orginially sourced from Mattt +// Date: August 1st, 2018 +// URL: https://github.com/Flight-School/AnyCodable +// License: MIT +// +import Foundation + +/** + A type-erased `Codable` value. + + The `AnyCodable` type forwards encoding and decoding responsibilities + to an underlying value, hiding its specific underlying type. + + You can encode or decode mixed-type values in dictionaries + and other collections that require `Encodable` or `Decodable` conformance + by declaring their contained type to be `AnyCodable`. + + - SeeAlso: `AnyEncodable` + - SeeAlso: `AnyDecodable` + */ +public struct AnyCodable: Codable { + public let value: Any + + public init(_ value: T?) { + self.value = value ?? () + } +} + +extension AnyCodable: _AnyEncodable, _AnyDecodable {} + +extension AnyCodable: Equatable { + public static func ==(lhs: AnyCodable, rhs: AnyCodable) -> Bool { + switch (lhs.value, rhs.value) { + case is (Void, Void): + return true + case let (lhs as Bool, rhs as Bool): + return lhs == rhs + case let (lhs as Int, rhs as Int): + return lhs == rhs + case let (lhs as Int8, rhs as Int8): + return lhs == rhs + case let (lhs as Int16, rhs as Int16): + return lhs == rhs + case let (lhs as Int32, rhs as Int32): + return lhs == rhs + case let (lhs as Int64, rhs as Int64): + return lhs == rhs + case let (lhs as UInt, rhs as UInt): + return lhs == rhs + case let (lhs as UInt8, rhs as UInt8): + return lhs == rhs + case let (lhs as UInt16, rhs as UInt16): + return lhs == rhs + case let (lhs as UInt32, rhs as UInt32): + return lhs == rhs + case let (lhs as UInt64, rhs as UInt64): + return lhs == rhs + case let (lhs as Float, rhs as Float): + return lhs == rhs + case let (lhs as Double, rhs as Double): + return lhs == rhs + case let (lhs as String, rhs as String): + return lhs == rhs + case (let lhs as [String: AnyCodable], let rhs as [String: AnyCodable]): + return lhs == rhs + case (let lhs as [AnyCodable], let rhs as [AnyCodable]): + return lhs == rhs + default: + return false + } + } +} + +extension AnyCodable: CustomStringConvertible { + public var description: String { + switch value { + case is Void: + return String(describing: nil as Any?) + case let value as CustomStringConvertible: + return value.description + default: + return String(describing: value) + } + } +} + +extension AnyCodable: CustomDebugStringConvertible { + public var debugDescription: String { + switch value { + case let value as CustomDebugStringConvertible: + return "AnyCodable(\(value.debugDescription))" + default: + return "AnyCodable(\(self.description))" + } + } +} + +extension AnyCodable: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {} diff --git a/Networking/Networking/Network/Tools/AnyDecodable.swift b/Networking/Networking/Network/Tools/AnyDecodable.swift new file mode 100755 index 00000000000..dec46e4d38d --- /dev/null +++ b/Networking/Networking/Network/Tools/AnyDecodable.swift @@ -0,0 +1,142 @@ +// Code orginially sourced from Mattt +// Date: August 1st, 2018 +// URL: https://github.com/Flight-School/AnyCodable +// License: MIT +// +import Foundation + +/** + A type-erased `Decodable` value. + + The `AnyDecodable` type forwards decoding responsibilities + to an underlying value, hiding its specific underlying type. + + You can decode mixed-type values in dictionaries + and other collections that require `Decodable` conformance + by declaring their contained type to be `AnyDecodable`: + + let json = """ + { + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": { + "a": "alpha", + "b": "bravo", + "c": "charlie" + } + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + let dictionary = try! decoder.decode([String: AnyCodable].self, from: json) + */ +public struct AnyDecodable: Decodable { + public let value: Any + + public init(_ value: T?) { + self.value = value ?? () + } +} + +protocol _AnyDecodable { + var value: Any { get } + init(_ value: T?) +} + +extension AnyDecodable: _AnyDecodable {} + +extension _AnyDecodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self.init(()) + } else if let bool = try? container.decode(Bool.self) { + self.init(bool) + } else if let int = try? container.decode(Int.self) { + self.init(int) + } else if let uint = try? container.decode(UInt.self) { + self.init(uint) + } else if let double = try? container.decode(Double.self) { + self.init(double) + } else if let string = try? container.decode(String.self) { + self.init(string) + } else if let array = try? container.decode([AnyCodable].self) { + self.init(array.map { $0.value }) + } else if let dictionary = try? container.decode([String: AnyCodable].self) { + self.init(dictionary.mapValues { $0.value }) + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyCodable value cannot be decoded") + } + } +} + +extension AnyDecodable: Equatable { + public static func ==(lhs: AnyDecodable, rhs: AnyDecodable) -> Bool { + switch (lhs.value, rhs.value) { + case is (Void, Void): + return true + case let (lhs as Bool, rhs as Bool): + return lhs == rhs + case let (lhs as Int, rhs as Int): + return lhs == rhs + case let (lhs as Int8, rhs as Int8): + return lhs == rhs + case let (lhs as Int16, rhs as Int16): + return lhs == rhs + case let (lhs as Int32, rhs as Int32): + return lhs == rhs + case let (lhs as Int64, rhs as Int64): + return lhs == rhs + case let (lhs as UInt, rhs as UInt): + return lhs == rhs + case let (lhs as UInt8, rhs as UInt8): + return lhs == rhs + case let (lhs as UInt16, rhs as UInt16): + return lhs == rhs + case let (lhs as UInt32, rhs as UInt32): + return lhs == rhs + case let (lhs as UInt64, rhs as UInt64): + return lhs == rhs + case let (lhs as Float, rhs as Float): + return lhs == rhs + case let (lhs as Double, rhs as Double): + return lhs == rhs + case let (lhs as String, rhs as String): + return lhs == rhs + case (let lhs as [String: AnyDecodable], let rhs as [String: AnyDecodable]): + return lhs == rhs + case (let lhs as [AnyDecodable], let rhs as [AnyDecodable]): + return lhs == rhs + default: + return false + } + } +} + +extension AnyDecodable: CustomStringConvertible { + public var description: String { + switch value { + case is Void: + return String(describing: nil as Any?) + case let value as CustomStringConvertible: + return value.description + default: + return String(describing: value) + } + } +} + +extension AnyDecodable: CustomDebugStringConvertible { + public var debugDescription: String { + switch value { + case let value as CustomDebugStringConvertible: + return "AnyDecodable(\(value.debugDescription))" + default: + return "AnyDecodable(\(self.description))" + } + } +} diff --git a/Networking/Networking/Network/Tools/AnyEncodable.swift b/Networking/Networking/Network/Tools/AnyEncodable.swift new file mode 100755 index 00000000000..de334419b69 --- /dev/null +++ b/Networking/Networking/Network/Tools/AnyEncodable.swift @@ -0,0 +1,203 @@ +// Code orginially sourced from Mattt +// Date: August 1st, 2018 +// URL: https://github.com/Flight-School/AnyCodable +// License: MIT +// +import Foundation + +/** + A type-erased `Encodable` value. + + The `AnyEncodable` type forwards encoding responsibilities + to an underlying value, hiding its specific underlying type. + + You can encode mixed-type values in dictionaries + and other collections that require `Encodable` conformance + by declaring their contained type to be `AnyEncodable`: + + let dictionary: [String: AnyEncodable] = [ + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": [ + "a": "alpha", + "b": "bravo", + "c": "charlie" + ] + ] + + let encoder = JSONEncoder() + let json = try! encoder.encode(dictionary) + */ +public struct AnyEncodable: Encodable { + public let value: Any + + public init(_ value: T?) { + self.value = value ?? () + } +} + +protocol _AnyEncodable { + var value: Any { get } + init(_ value: T?) +} + + +extension AnyEncodable: _AnyEncodable {} + +// MARK: - Encodable + +extension _AnyEncodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self.value { + case is Void: + try container.encodeNil() + case let bool as Bool: + try container.encode(bool) + case let int as Int: + try container.encode(int) + case let int8 as Int8: + try container.encode(int8) + case let int16 as Int16: + try container.encode(int16) + case let int32 as Int32: + try container.encode(int32) + case let int64 as Int64: + try container.encode(int64) + case let uint as UInt: + try container.encode(uint) + case let uint8 as UInt8: + try container.encode(uint8) + case let uint16 as UInt16: + try container.encode(uint16) + case let uint32 as UInt32: + try container.encode(uint32) + case let uint64 as UInt64: + try container.encode(uint64) + case let float as Float: + try container.encode(float) + case let double as Double: + try container.encode(double) + case let string as String: + try container.encode(string) + case let date as Date: + try container.encode(date) + case let url as URL: + try container.encode(url) + case let array as [Any?]: + try container.encode(array.map { AnyCodable($0) }) + case let dictionary as [String: Any?]: + try container.encode(dictionary.mapValues { AnyCodable($0) }) + default: + let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyCodable value cannot be encoded") + throw EncodingError.invalidValue(self.value, context) + } + } +} + +extension AnyEncodable: Equatable { + public static func ==(lhs: AnyEncodable, rhs: AnyEncodable) -> Bool { + switch (lhs.value, rhs.value) { + case is (Void, Void): + return true + case let (lhs as Bool, rhs as Bool): + return lhs == rhs + case let (lhs as Int, rhs as Int): + return lhs == rhs + case let (lhs as Int8, rhs as Int8): + return lhs == rhs + case let (lhs as Int16, rhs as Int16): + return lhs == rhs + case let (lhs as Int32, rhs as Int32): + return lhs == rhs + case let (lhs as Int64, rhs as Int64): + return lhs == rhs + case let (lhs as UInt, rhs as UInt): + return lhs == rhs + case let (lhs as UInt8, rhs as UInt8): + return lhs == rhs + case let (lhs as UInt16, rhs as UInt16): + return lhs == rhs + case let (lhs as UInt32, rhs as UInt32): + return lhs == rhs + case let (lhs as UInt64, rhs as UInt64): + return lhs == rhs + case let (lhs as Float, rhs as Float): + return lhs == rhs + case let (lhs as Double, rhs as Double): + return lhs == rhs + case let (lhs as String, rhs as String): + return lhs == rhs + case (let lhs as [String: AnyEncodable], let rhs as [String: AnyEncodable]): + return lhs == rhs + case (let lhs as [AnyEncodable], let rhs as [AnyEncodable]): + return lhs == rhs + default: + return false + } + } +} + +extension AnyEncodable: CustomStringConvertible { + public var description: String { + switch value { + case is Void: + return String(describing: nil as Any?) + case let value as CustomStringConvertible: + return value.description + default: + return String(describing: value) + } + } +} + +extension AnyEncodable: CustomDebugStringConvertible { + public var debugDescription: String { + switch value { + case let value as CustomDebugStringConvertible: + return "AnyEncodable(\(value.debugDescription))" + default: + return "AnyEncodable(\(self.description))" + } + } +} + +extension AnyEncodable: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {} + +extension _AnyEncodable { + public init(nilLiteral: ()) { + self.init(nil as Any?) + } + + public init(booleanLiteral value: Bool) { + self.init(value) + } + + public init(integerLiteral value: Int) { + self.init(value) + } + + public init(floatLiteral value: Double) { + self.init(value) + } + + public init(extendedGraphemeClusterLiteral value: String) { + self.init(value) + } + + public init(stringLiteral value: String) { + self.init(value) + } + + public init(arrayLiteral elements: Any...) { + self.init(elements) + } + + public init(dictionaryLiteral elements: (AnyHashable, Any)...) { + self.init(Dictionary(elements, uniquingKeysWith: { (first, _) in first })) + } +} diff --git a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift index d93a734e2f3..d93f82979ca 100644 --- a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift @@ -18,6 +18,7 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(dayStats.date, "2018-06-08") XCTAssertEqual(dayStats.quantity, "31") XCTAssertEqual(dayStats.fields.count, 18) + XCTAssertEqual(dayStats.orderStatsItems!.count, 31) XCTAssertEqual(dayStats.totalOrders, 9) XCTAssertEqual(dayStats.totalProducts, 13) XCTAssertEqual(dayStats.totalGrossSales, 439.23) @@ -40,6 +41,7 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(weekStats.date, "2018-W30") XCTAssertEqual(weekStats.quantity, "31") XCTAssertEqual(weekStats.fields.count, 18) + XCTAssertEqual(weekStats.orderStatsItems!.count, 31) XCTAssertEqual(weekStats.totalOrders, 65) XCTAssertEqual(weekStats.totalProducts, 87) XCTAssertEqual(weekStats.totalGrossSales, 2858.52) @@ -62,6 +64,7 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(monthStats.date, "2018-06") XCTAssertEqual(monthStats.quantity, "12") XCTAssertEqual(monthStats.fields.count, 18) + XCTAssertEqual(monthStats.orderStatsItems!.count, 12) XCTAssertEqual(monthStats.totalOrders, 159) XCTAssertEqual(monthStats.totalProducts, 243) XCTAssertEqual(monthStats.totalGrossSales, 6830.590000000002) @@ -84,6 +87,7 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(yearStats.date, "2018") XCTAssertEqual(yearStats.quantity, "4") XCTAssertEqual(yearStats.fields.count, 18) + XCTAssertEqual(yearStats.orderStatsItems!.count, 4) XCTAssertEqual(yearStats.totalOrders, 293) XCTAssertEqual(yearStats.totalProducts, 626) XCTAssertEqual(yearStats.totalGrossSales, 10928.91999999999) From 1aeb20af065de21c4d398719faabc5e316db16aa Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 2 Aug 2018 10:48:26 -0500 Subject: [PATCH 034/112] More tests and OrderStatsItem parsing --- Networking/Networking/Model/OrderStats.swift | 18 +- .../Networking/Model/OrderStatsItem.swift | 166 ++++++++++++++-- .../Mapper/OrderStatsMapperTests.swift | 188 +++++++++++++++++- .../Responses/order-stats-year.json | 2 +- 4 files changed, 346 insertions(+), 28 deletions(-) diff --git a/Networking/Networking/Model/OrderStats.swift b/Networking/Networking/Model/OrderStats.swift index 53458cc707a..032f8a56a84 100644 --- a/Networking/Networking/Model/OrderStats.swift +++ b/Networking/Networking/Model/OrderStats.swift @@ -18,7 +18,6 @@ public struct OrderStats: Decodable { public let unit: String public let quantity: String public let fields: [String] - public let orderStatsItems: [[AnyCodable]]? public let totalGrossSales: Float public let totalNetSales: Float public let totalOrders: Int @@ -27,6 +26,7 @@ public struct OrderStats: Decodable { public let averageNetSales: Float public let averageOrders: Float public let averageProducts: Float + public let items: [OrderStatsItem]? /// The public initializer for order stats. @@ -39,7 +39,7 @@ public struct OrderStats: Decodable { let quantity = try container.decode(String.self, forKey: .quantity) let fields = try container.decode([String].self, forKey: .fields) - let orderStatsItems = try container.decode([[AnyCodable]].self, forKey: .orderStatsItems) + let rawData: [[AnyCodable]] = try container.decode([[AnyCodable]].self, forKey: .data) let totalGrossSales = try container.decode(Float.self, forKey: .totalGrossSales) let totalNetSales = try container.decode(Float.self, forKey: .totalNetSales) @@ -51,18 +51,19 @@ public struct OrderStats: Decodable { let averageOrders = try container.decode(Float.self, forKey: .averageOrders) let averageProducts = try container.decode(Float.self, forKey: .averageProducts) - self.init(date: date, unit: unit, quantity: quantity, fields: fields, orderStatsItems: orderStatsItems, totalGrossSales: totalGrossSales, totalNetSales: totalNetSales, totalOrders: totalOrders, totalProducts: totalProducts, averageGrossSales: averageGrossSales, averageNetSales: averageNetSales, averageOrders: averageOrders, averageProducts: averageProducts) + let items = rawData.map({ OrderStatsItem(fieldNames: fields, rawData: $0) }) + + self.init(date: date, unit: unit, quantity: quantity, fields: fields, items: items, totalGrossSales: totalGrossSales, totalNetSales: totalNetSales, totalOrders: totalOrders, totalProducts: totalProducts, averageGrossSales: averageGrossSales, averageNetSales: averageNetSales, averageOrders: averageOrders, averageProducts: averageProducts) } /// OrderStats struct initializer. /// - public init(date: String, unit: String, quantity: String, fields: [String], orderStatsItems: [[AnyCodable]]?, totalGrossSales: Float, totalNetSales: Float, totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { + public init(date: String, unit: String, quantity: String, fields: [String], items: [OrderStatsItem]?, totalGrossSales: Float, totalNetSales: Float, totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { self.date = date self.unit = unit self.quantity = quantity self.fields = fields - self.orderStatsItems = orderStatsItems self.totalGrossSales = totalGrossSales self.totalNetSales = totalNetSales self.totalOrders = totalOrders @@ -71,6 +72,7 @@ public struct OrderStats: Decodable { self.averageNetSales = averageNetSales self.averageOrders = averageOrders self.averageProducts = averageProducts + self.items = items } } @@ -84,7 +86,7 @@ private extension OrderStats { case unit = "unit" case quantity = "quantity" case fields = "fields" - case orderStatsItems = "data" + case data = "data" case totalGrossSales = "total_gross_sales" case totalNetSales = "total_net_sales" case totalOrders = "total_orders" @@ -105,7 +107,6 @@ extension OrderStats: Comparable { lhs.unit == rhs.unit && lhs.quantity == rhs.quantity && lhs.fields == rhs.fields && - lhs.orderStatsItems == rhs.orderStatsItems && lhs.totalGrossSales == rhs.totalGrossSales && lhs.totalNetSales == rhs.totalNetSales && lhs.totalOrders == rhs.totalOrders && @@ -113,7 +114,8 @@ extension OrderStats: Comparable { lhs.averageGrossSales == rhs.averageGrossSales && lhs.averageNetSales == rhs.averageNetSales && lhs.averageOrders == rhs.averageOrders && - lhs.averageProducts == rhs.averageProducts + lhs.averageProducts == rhs.averageProducts && + lhs.items == rhs.items } public static func < (lhs: OrderStats, rhs: OrderStats) -> Bool { diff --git a/Networking/Networking/Model/OrderStatsItem.swift b/Networking/Networking/Model/OrderStatsItem.swift index 7736e627b21..57539873402 100644 --- a/Networking/Networking/Model/OrderStatsItem.swift +++ b/Networking/Networking/Model/OrderStatsItem.swift @@ -1,32 +1,168 @@ import Foundation -/// Represents an single order stat during a specific period. +/// Represents an single order stat for a specific period. /// -public struct OrderStatsItem: Decodable { - public let data: [AnyCodable] +public struct OrderStatsItem { + public let data: [Any] + public let fieldNames: [String] + /// OrderStatsItem struct initializer. /// - public init(data: [AnyCodable]) { - self.data = data + public init(fieldNames: [String], rawData: [AnyCodable]) { + self.fieldNames = fieldNames + self.data = rawData.map({ $0.value }) } - /// The public initializer for OrderStatsItem. - /// - public init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() - let data = try container.decode([AnyCodable].self) - self.init(data: data) // initialize the struct + // MARK: Computed Properties + + public var period: String { + return fetchStringValue(for: .period) + } + + public var orders: Int { + return fetchIntValue(for: .orders) + } + + public var products: Int { + return fetchIntValue(for: .products) + } + + public var coupons: Int { + return fetchIntValue(for: .coupons) + } + + public var couponDiscount: Double { + return fetchDoubleValue(for: .couponDiscount) + } + + public var totalSales: Double { + return fetchDoubleValue(for: .totalSales) + } + + public var totalTax: Double { + return fetchDoubleValue(for: .totalTax) + } + + public var totalShipping: Double { + return fetchDoubleValue(for: .totalShipping) + } + + public var totalShippingTax: Double { + return fetchDoubleValue(for: .totalShippingTax) + } + + public var totalRefund: Double { + return fetchDoubleValue(for: .totalRefund) + } + + public var totalTaxRefund: Double { + return fetchDoubleValue(for: .totalTaxRefund) + } + + public var totalShippingRefund: Double { + return fetchDoubleValue(for: .totalShippingRefund) + } + + public var totalShippingTaxRefund: Double { + return fetchDoubleValue(for: .totalShippingTaxRefund) + } + + public var currency: String { + return fetchStringValue(for: .currency) + } + + public var grossSales: Double { + return fetchDoubleValue(for: .grossSales) + } + + public var netSales: Double { + return fetchDoubleValue(for: .netSales) + } + + public var avgOrderValue: Double { + return fetchDoubleValue(for: .avgOrderValue) + } + + public var avgProductsPerOrder: Double { + return fetchDoubleValue(for: .avgProductsPerOrder) } } -// MARK: - Equatable Conformance +// MARK: - Comparable Conformance // -extension OrderStatsItem: Equatable { +extension OrderStatsItem: Comparable { public static func == (lhs: OrderStatsItem, rhs: OrderStatsItem) -> Bool { - return lhs.data == rhs.data + return lhs.fieldNames == rhs.fieldNames && + lhs.period == rhs.period && + lhs.orders == rhs.orders && + lhs.products == rhs.products && + lhs.coupons == rhs.coupons && + lhs.couponDiscount == rhs.couponDiscount && + lhs.totalSales == rhs.totalSales && + lhs.totalTax == rhs.totalTax && + lhs.totalShipping == rhs.totalShipping && + lhs.totalShippingTax == rhs.totalShippingTax && + lhs.totalRefund == rhs.totalRefund && + lhs.totalTaxRefund == rhs.totalTaxRefund && + lhs.totalShippingRefund == rhs.totalShippingRefund && + lhs.totalShippingTaxRefund == rhs.totalShippingTaxRefund && + lhs.currency == rhs.currency && + lhs.grossSales == rhs.grossSales && + lhs.netSales == rhs.netSales && + lhs.avgOrderValue == rhs.avgOrderValue && + lhs.avgProductsPerOrder == rhs.avgProductsPerOrder + } + + public static func < (lhs: OrderStatsItem, rhs: OrderStatsItem) -> Bool { + return lhs.period < rhs.period || + (lhs.period == rhs.period && lhs.totalSales < rhs.totalSales) || + (lhs.period == rhs.period && lhs.totalSales == rhs.totalSales && lhs.orders < rhs.orders) + } +} + +// MARK: - Private Helpers +// + +private extension OrderStatsItem { + func fetchStringValue(for field: FieldNames) -> String { + guard let index = fieldNames.index(of: field.rawValue) else { + return "" + } + + // 😢 As crazy as it sounds, sometimes the server occasionally returns + // String values as Ints — we need to account for this. + if self.data[index] is Int { + if let intValue = self.data[index] as? Int { + return String(intValue) + } + return "" + } else { + return self.data[index] as? String ?? "" + } + } + + func fetchIntValue(for field: FieldNames) -> Int { + guard let index = fieldNames.index(of: field.rawValue), + let returnValue = self.data[index] as? Int else { + return 0 + } + return returnValue + } + + func fetchDoubleValue(for field: FieldNames) -> Double { + guard let index = fieldNames.index(of: field.rawValue) else { + return 0 + } + + if self.data[index] is Int { + let intValue = self.data[index] as? Int ?? 0 + return Double(intValue) + } else { + return self.data[index] as? Double ?? 0 + } } } @@ -37,7 +173,7 @@ private extension OrderStatsItem { /// Defines all of the possbile fields for an OrderStatsItem. /// - enum Fields: String { + enum FieldNames: String { case period = "period" case orders = "orders" case products = "products" diff --git a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift index d93f82979ca..64f4f4da763 100644 --- a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift @@ -18,7 +18,6 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(dayStats.date, "2018-06-08") XCTAssertEqual(dayStats.quantity, "31") XCTAssertEqual(dayStats.fields.count, 18) - XCTAssertEqual(dayStats.orderStatsItems!.count, 31) XCTAssertEqual(dayStats.totalOrders, 9) XCTAssertEqual(dayStats.totalProducts, 13) XCTAssertEqual(dayStats.totalGrossSales, 439.23) @@ -27,6 +26,47 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(dayStats.averageNetSales, 14.1368) XCTAssertEqual(dayStats.averageOrders, 0.2903) XCTAssertEqual(dayStats.averageProducts, 0.4194) + XCTAssertEqual(dayStats.items!.count, 31) + + let sampleItem1 = dayStats.items![0] + XCTAssertEqual(sampleItem1.period, "2018-05-09") + XCTAssertEqual(sampleItem1.orders, 0) + XCTAssertEqual(sampleItem1.products, 0) + XCTAssertEqual(sampleItem1.coupons, 0) + XCTAssertEqual(sampleItem1.couponDiscount, 0) + XCTAssertEqual(sampleItem1.totalSales, 0) + XCTAssertEqual(sampleItem1.totalTax, 0) + XCTAssertEqual(sampleItem1.totalShipping, 0) + XCTAssertEqual(sampleItem1.totalShippingTax, 0) + XCTAssertEqual(sampleItem1.totalRefund, 0) + XCTAssertEqual(sampleItem1.totalTaxRefund, 0) + XCTAssertEqual(sampleItem1.totalShippingRefund, 0) + XCTAssertEqual(sampleItem1.totalShippingTaxRefund, 0) + XCTAssertEqual(sampleItem1.currency, "USD") + XCTAssertEqual(sampleItem1.grossSales, 0) + XCTAssertEqual(sampleItem1.netSales, 0) + XCTAssertEqual(sampleItem1.avgOrderValue, 0) + XCTAssertEqual(sampleItem1.avgProductsPerOrder, 0) + + let sampleItem2 = dayStats.items![23] + XCTAssertEqual(sampleItem2.period, "2018-06-01") + XCTAssertEqual(sampleItem2.orders, 2) + XCTAssertEqual(sampleItem2.products, 2) + XCTAssertEqual(sampleItem2.coupons, 0) + XCTAssertEqual(sampleItem2.couponDiscount, 0) + XCTAssertEqual(sampleItem2.totalSales, 14.24) + XCTAssertEqual(sampleItem2.totalTax, 0.12) + XCTAssertEqual(sampleItem2.totalShipping, 9.98) + XCTAssertEqual(sampleItem2.totalShippingTax, 0.28) + XCTAssertEqual(sampleItem2.totalRefund, 0) + XCTAssertEqual(sampleItem2.totalTaxRefund, 0) + XCTAssertEqual(sampleItem2.totalShippingRefund, 0) + XCTAssertEqual(sampleItem2.totalShippingTaxRefund, 0) + XCTAssertEqual(sampleItem2.currency, "USD") + XCTAssertEqual(sampleItem2.grossSales, 14.24) + XCTAssertEqual(sampleItem2.netSales, 14.120000000000001) + XCTAssertEqual(sampleItem2.avgOrderValue, 7.12) + XCTAssertEqual(sampleItem2.avgProductsPerOrder, 1) } /// Verifies that all of the week unit OrderStats fields are parsed correctly. @@ -41,7 +81,6 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(weekStats.date, "2018-W30") XCTAssertEqual(weekStats.quantity, "31") XCTAssertEqual(weekStats.fields.count, 18) - XCTAssertEqual(weekStats.orderStatsItems!.count, 31) XCTAssertEqual(weekStats.totalOrders, 65) XCTAssertEqual(weekStats.totalProducts, 87) XCTAssertEqual(weekStats.totalGrossSales, 2858.52) @@ -50,6 +89,67 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(weekStats.averageNetSales, 91.4048) XCTAssertEqual(weekStats.averageOrders, 2.0968) XCTAssertEqual(weekStats.averageProducts, 2.8065) + XCTAssertEqual(weekStats.items!.count, 31) + + let sampleItem1 = weekStats.items![0] + XCTAssertEqual(sampleItem1.period, "2017-W52") + XCTAssertEqual(sampleItem1.orders, 0) + XCTAssertEqual(sampleItem1.products, 0) + XCTAssertEqual(sampleItem1.coupons, 0) + XCTAssertEqual(sampleItem1.couponDiscount, 0) + XCTAssertEqual(sampleItem1.totalSales, 0) + XCTAssertEqual(sampleItem1.totalTax, 0) + XCTAssertEqual(sampleItem1.totalShipping, 0) + XCTAssertEqual(sampleItem1.totalShippingTax, 0) + XCTAssertEqual(sampleItem1.totalRefund, 0) + XCTAssertEqual(sampleItem1.totalTaxRefund, 0) + XCTAssertEqual(sampleItem1.totalShippingRefund, 0) + XCTAssertEqual(sampleItem1.totalShippingTaxRefund, 0) + XCTAssertEqual(sampleItem1.currency, "USD") + XCTAssertEqual(sampleItem1.grossSales, 0) + XCTAssertEqual(sampleItem1.netSales, 0) + XCTAssertEqual(sampleItem1.avgOrderValue, 0) + XCTAssertEqual(sampleItem1.avgProductsPerOrder, 0) + + let sampleItem2 = weekStats.items![1] + XCTAssertEqual(sampleItem2.period, "2018-W01") + XCTAssertEqual(sampleItem2.orders, 2) + XCTAssertEqual(sampleItem2.products, 4) + XCTAssertEqual(sampleItem2.coupons, 0) + XCTAssertEqual(sampleItem2.couponDiscount, 0) + XCTAssertEqual(sampleItem2.totalSales, 160) + XCTAssertEqual(sampleItem2.totalTax, 0) + XCTAssertEqual(sampleItem2.totalShipping, 0) + XCTAssertEqual(sampleItem2.totalShippingTax, 0) + XCTAssertEqual(sampleItem2.totalRefund, 0) + XCTAssertEqual(sampleItem2.totalTaxRefund, 0) + XCTAssertEqual(sampleItem2.totalShippingRefund, 0) + XCTAssertEqual(sampleItem2.totalShippingTaxRefund, 0) + XCTAssertEqual(sampleItem2.currency, "USD") + XCTAssertEqual(sampleItem2.grossSales, 160) + XCTAssertEqual(sampleItem2.netSales, 160) + XCTAssertEqual(sampleItem2.avgOrderValue, 80) + XCTAssertEqual(sampleItem2.avgProductsPerOrder, 2) + + let sampleItem3 = weekStats.items![2] + XCTAssertEqual(sampleItem3.period, "2018-W02") + XCTAssertEqual(sampleItem3.orders, 0) + XCTAssertEqual(sampleItem3.products, 0) + XCTAssertEqual(sampleItem3.coupons, 0) + XCTAssertEqual(sampleItem3.couponDiscount, 0) + XCTAssertEqual(sampleItem3.totalSales, 0) + XCTAssertEqual(sampleItem3.totalTax, 0) + XCTAssertEqual(sampleItem3.totalShipping, 0) + XCTAssertEqual(sampleItem3.totalShippingTax, 0) + XCTAssertEqual(sampleItem3.totalRefund, 160) + XCTAssertEqual(sampleItem3.totalTaxRefund, 0) + XCTAssertEqual(sampleItem3.totalShippingRefund, 0) + XCTAssertEqual(sampleItem3.totalShippingTaxRefund, 0) + XCTAssertEqual(sampleItem3.currency, "USD") + XCTAssertEqual(sampleItem3.grossSales, -160) + XCTAssertEqual(sampleItem3.netSales, -160) + XCTAssertEqual(sampleItem3.avgOrderValue, 0) + XCTAssertEqual(sampleItem3.avgProductsPerOrder, 0) } /// Verifies that all of the month unit OrderStats fields are parsed correctly. @@ -64,7 +164,6 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(monthStats.date, "2018-06") XCTAssertEqual(monthStats.quantity, "12") XCTAssertEqual(monthStats.fields.count, 18) - XCTAssertEqual(monthStats.orderStatsItems!.count, 12) XCTAssertEqual(monthStats.totalOrders, 159) XCTAssertEqual(monthStats.totalProducts, 243) XCTAssertEqual(monthStats.totalGrossSales, 6830.590000000002) @@ -73,6 +172,47 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(monthStats.averageNetSales, 559.7693) XCTAssertEqual(monthStats.averageOrders, 13.25) XCTAssertEqual(monthStats.averageProducts, 20.25) + XCTAssertEqual(monthStats.items!.count, 12) + + let sampleItem1 = monthStats.items![0] + XCTAssertEqual(sampleItem1.period, "2017-07") + XCTAssertEqual(sampleItem1.orders, 49) + XCTAssertEqual(sampleItem1.products, 74) + XCTAssertEqual(sampleItem1.coupons, 1) + XCTAssertEqual(sampleItem1.couponDiscount, 7.5) + XCTAssertEqual(sampleItem1.totalSales, 1602.2500000000014) + XCTAssertEqual(sampleItem1.totalTax, 50.78999999999998) + XCTAssertEqual(sampleItem1.totalShipping, 117.31) + XCTAssertEqual(sampleItem1.totalShippingTax, 4.200800000000001) + XCTAssertEqual(sampleItem1.totalRefund, 0) + XCTAssertEqual(sampleItem1.totalTaxRefund, 0) + XCTAssertEqual(sampleItem1.totalShippingRefund, 0) + XCTAssertEqual(sampleItem1.totalShippingTaxRefund, 0) + XCTAssertEqual(sampleItem1.currency, "USD") + XCTAssertEqual(sampleItem1.grossSales, 1602.2500000000014) + XCTAssertEqual(sampleItem1.netSales, 1551.4600000000014) + XCTAssertEqual(sampleItem1.avgOrderValue, 32.699) + XCTAssertEqual(sampleItem1.avgProductsPerOrder, 1.5102) + + let sampleItem2 = monthStats.items![11] + XCTAssertEqual(sampleItem2.period, "2018-06") + XCTAssertEqual(sampleItem2.orders, 10) + XCTAssertEqual(sampleItem2.products, 12) + XCTAssertEqual(sampleItem2.coupons, 0) + XCTAssertEqual(sampleItem2.couponDiscount, 0) + XCTAssertEqual(sampleItem2.totalSales, 511.58000000000004) + XCTAssertEqual(sampleItem2.totalTax, 1.28) + XCTAssertEqual(sampleItem2.totalShipping, 96.16000000000001) + XCTAssertEqual(sampleItem2.totalShippingTax, 0.28) + XCTAssertEqual(sampleItem2.totalRefund, 2.06) + XCTAssertEqual(sampleItem2.totalTaxRefund, 0.06) + XCTAssertEqual(sampleItem2.totalShippingRefund, 0) + XCTAssertEqual(sampleItem2.totalShippingTaxRefund, 0) + XCTAssertEqual(sampleItem2.currency, "USD") + XCTAssertEqual(sampleItem2.grossSales, 509.52000000000004) + XCTAssertEqual(sampleItem2.netSales, 508.24000000000007) + XCTAssertEqual(sampleItem2.avgOrderValue, 50.952) + XCTAssertEqual(sampleItem2.avgProductsPerOrder, 1.2) } /// Verifies that all of the year unit OrderStats fields are parsed correctly. @@ -87,7 +227,6 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(yearStats.date, "2018") XCTAssertEqual(yearStats.quantity, "4") XCTAssertEqual(yearStats.fields.count, 18) - XCTAssertEqual(yearStats.orderStatsItems!.count, 4) XCTAssertEqual(yearStats.totalOrders, 293) XCTAssertEqual(yearStats.totalProducts, 626) XCTAssertEqual(yearStats.totalGrossSales, 10928.91999999999) @@ -96,6 +235,47 @@ class OrderStatsMapperTests: XCTestCase { XCTAssertEqual(yearStats.averageNetSales, 2671.068) XCTAssertEqual(yearStats.averageOrders, 73.25) XCTAssertEqual(yearStats.averageProducts, 156.5) + XCTAssertEqual(yearStats.items!.count, 4) + + let sampleItem1 = yearStats.items![2] + XCTAssertEqual(sampleItem1.period, "2017") + XCTAssertEqual(sampleItem1.orders, 228) + XCTAssertEqual(sampleItem1.products, 539) + XCTAssertEqual(sampleItem1.coupons, 32) + XCTAssertEqual(sampleItem1.couponDiscount, 237.14000000000007) + XCTAssertEqual(sampleItem1.totalSales, 9813.699999999988) + XCTAssertEqual(sampleItem1.totalTax, 219.6780000000001) + XCTAssertEqual(sampleItem1.totalShipping, 1466.0300000000007) + XCTAssertEqual(sampleItem1.totalShippingTax, 27.6736) + XCTAssertEqual(sampleItem1.totalRefund, 1743.3) + XCTAssertEqual(sampleItem1.totalTaxRefund, 12.47) + XCTAssertEqual(sampleItem1.totalShippingRefund, 202.83) + XCTAssertEqual(sampleItem1.totalShippingTaxRefund, 4.386) + XCTAssertEqual(sampleItem1.currency, "USD") + XCTAssertEqual(sampleItem1.grossSales, 8070.399999999988) + XCTAssertEqual(sampleItem1.netSales, 7850.721999999988) + XCTAssertEqual(sampleItem1.avgOrderValue, 35.3965) + XCTAssertEqual(sampleItem1.avgProductsPerOrder, 2.364) + + let sampleItem2 = yearStats.items![3] + XCTAssertEqual(sampleItem2.period, "2018") + XCTAssertEqual(sampleItem2.orders, 65) + XCTAssertEqual(sampleItem2.products, 87) + XCTAssertEqual(sampleItem2.coupons, 4) + XCTAssertEqual(sampleItem2.couponDiscount, 140) + XCTAssertEqual(sampleItem2.totalSales, 3176.580000000001) + XCTAssertEqual(sampleItem2.totalTax, 24.97) + XCTAssertEqual(sampleItem2.totalShipping, 517.95) + XCTAssertEqual(sampleItem2.totalShippingTax, 17.3) + XCTAssertEqual(sampleItem2.totalRefund, 318.06) + XCTAssertEqual(sampleItem2.totalTaxRefund, 0.06) + XCTAssertEqual(sampleItem2.totalShippingRefund, 11) + XCTAssertEqual(sampleItem2.totalShippingTaxRefund, 0) + XCTAssertEqual(sampleItem2.currency, "USD") + XCTAssertEqual(sampleItem2.grossSales, 2858.520000000001) + XCTAssertEqual(sampleItem2.netSales, 2833.550000000001) + XCTAssertEqual(sampleItem2.avgOrderValue, 43.9772) + XCTAssertEqual(sampleItem2.avgProductsPerOrder, 1.3385) } } diff --git a/Networking/NetworkingTests/Responses/order-stats-year.json b/Networking/NetworkingTests/Responses/order-stats-year.json index 243eef0d85f..8ab3e38f17f 100644 --- a/Networking/NetworkingTests/Responses/order-stats-year.json +++ b/Networking/NetworkingTests/Responses/order-stats-year.json @@ -711,4 +711,4 @@ "avg_net_sales": 2671.068, "avg_orders": 73.25, "avg_products": 156.5 -} \ No newline at end of file +} From 06ec90576e3e3f47c592e04f8baf988b5b6b40d9 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 2 Aug 2018 11:12:53 -0500 Subject: [PATCH 035/112] Updated OrderStatGranularity --- .../Networking.xcodeproj/project.pbxproj | 4 + .../Model/OrderStatGranularity.swift | 79 +++++++++++++++++++ Networking/Networking/Model/OrderStats.swift | 25 ++---- .../Mapper/OrderStatsMapperTests.swift | 8 +- 4 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 Networking/Networking/Model/OrderStatGranularity.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 33b8d57a233..1b851cdd7aa 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 74C8F06C20EEBD5D00B6EDC9 /* broken-order.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */; }; 74C8F06E20EEC1E800B6EDC9 /* OrderNotesMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */; }; 74C8F07020EEC3A800B6EDC9 /* broken-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */; }; + 74D522B62113607F00042831 /* OrderStatGranularity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D522B52113607F00042831 /* OrderStatGranularity.swift */; }; B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */; }; B505F6CF20BEE38B00BB1B69 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6CE20BEE38B00BB1B69 /* Account.swift */; }; B505F6D120BEE39600BB1B69 /* AccountRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6D020BEE39600BB1B69 /* AccountRemote.swift */; }; @@ -109,6 +110,7 @@ 74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "broken-order.json"; sourceTree = ""; }; 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapperTests.swift; sourceTree = ""; }; 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "broken-notes.json"; sourceTree = ""; }; + 74D522B52113607F00042831 /* OrderStatGranularity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatGranularity.swift; sourceTree = ""; }; 753D6504FF01F09F6A33B73E /* Pods-Networking.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Networking.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Networking/Pods-Networking.debug.xcconfig"; sourceTree = ""; }; B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountMapper.swift; sourceTree = ""; }; B505F6CE20BEE38B00BB1B69 /* Account.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; @@ -339,6 +341,7 @@ 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */, B5BB1D1120A255EC00112D92 /* OrderStatus.swift */, 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */, + 74D522B52113607F00042831 /* OrderStatGranularity.swift */, 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */, B56C1EB720EA76F500D749F9 /* Site.swift */, ); @@ -625,6 +628,7 @@ 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */, 7452387321124B7700A973CD /* AnyCodable.swift in Sources */, B567AF2920A0FA1E00AB6C62 /* Mapper.swift in Sources */, + 74D522B62113607F00042831 /* OrderStatGranularity.swift in Sources */, B518662220A097C200037A38 /* Network.swift in Sources */, B518662420A099BF00037A38 /* AlamofireNetwork.swift in Sources */, B557DA1820979D51005962F4 /* Credentials.swift in Sources */, diff --git a/Networking/Networking/Model/OrderStatGranularity.swift b/Networking/Networking/Model/OrderStatGranularity.swift new file mode 100644 index 00000000000..4b1712718c2 --- /dev/null +++ b/Networking/Networking/Model/OrderStatGranularity.swift @@ -0,0 +1,79 @@ +import Foundation + + +/// Represents the data granularity for a specific `OrderStats` instance (e.g. day, week, month, year) +/// +public enum OrderStatGranularity: Decodable { + case day + case week + case month + case year +} + + +// MARK: - RawRepresentable Conformance +// +extension OrderStatGranularity: RawRepresentable { + + /// Designated Initializer. + /// + public init(rawValue: String) { + switch rawValue { + case Keys.week: + self = .week + case Keys.month: + self = .month + case Keys.year: + self = .year + default: + self = .day + } + } + + /// Returns the current Enum Case's Raw Value + /// + public var rawValue: String { + switch self { + case .day: return Keys.day + case .week: return Keys.week + case .month: return Keys.month + case .year: return Keys.year + } + } +} + + +// MARK: - StringConvertible Conformance +// +extension OrderStatGranularity: CustomStringConvertible { + + /// Returns a string describing the current OrderStatus Instance + /// + public var description: String { + switch self { + case .day: + return NSLocalizedString("Day", comment: "Order statistics unit - a single day") + case .week: + return NSLocalizedString("Week", comment: "Order statistics unit - a single week") + case .month: + return NSLocalizedString("Month", comment: "Order statistics unit - a single week") + case .year: + return NSLocalizedString("Year", comment: "Order statistics unit - a single year") + } + } +} + + +// MARK: - Constants! +// +private extension OrderStatGranularity { + + /// Enum containing the 'Known' OrderStatGranularity Keys + /// + private enum Keys { + static let day = "day" + static let week = "week" + static let month = "month" + static let year = "year" + } +} diff --git a/Networking/Networking/Model/OrderStats.swift b/Networking/Networking/Model/OrderStats.swift index 032f8a56a84..1104367a350 100644 --- a/Networking/Networking/Model/OrderStats.swift +++ b/Networking/Networking/Model/OrderStats.swift @@ -1,21 +1,11 @@ import Foundation -/// Represents the granularity of a specific `OrderStats` -/// -public enum OrderStatGranularity: String { - case day = "day" - case week = "week" - case month = "month" - case year = "year" -} - - /// Represents order stats over a specific period. /// public struct OrderStats: Decodable { public let date: String - public let unit: String + public let granularity: OrderStatGranularity public let quantity: String public let fields: [String] public let totalGrossSales: Float @@ -35,7 +25,7 @@ public struct OrderStats: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) let date = try container.decode(String.self, forKey: .date) - let unit = try container.decode(String.self, forKey: .unit) + let granularity = try container.decode(OrderStatGranularity.self, forKey: .unit) let quantity = try container.decode(String.self, forKey: .quantity) let fields = try container.decode([String].self, forKey: .fields) @@ -53,15 +43,15 @@ public struct OrderStats: Decodable { let items = rawData.map({ OrderStatsItem(fieldNames: fields, rawData: $0) }) - self.init(date: date, unit: unit, quantity: quantity, fields: fields, items: items, totalGrossSales: totalGrossSales, totalNetSales: totalNetSales, totalOrders: totalOrders, totalProducts: totalProducts, averageGrossSales: averageGrossSales, averageNetSales: averageNetSales, averageOrders: averageOrders, averageProducts: averageProducts) + self.init(date: date, granularity: granularity, quantity: quantity, fields: fields, items: items, totalGrossSales: totalGrossSales, totalNetSales: totalNetSales, totalOrders: totalOrders, totalProducts: totalProducts, averageGrossSales: averageGrossSales, averageNetSales: averageNetSales, averageOrders: averageOrders, averageProducts: averageProducts) } /// OrderStats struct initializer. /// - public init(date: String, unit: String, quantity: String, fields: [String], items: [OrderStatsItem]?, totalGrossSales: Float, totalNetSales: Float, totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { + public init(date: String, granularity: OrderStatGranularity, quantity: String, fields: [String], items: [OrderStatsItem]?, totalGrossSales: Float, totalNetSales: Float, totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { self.date = date - self.unit = unit + self.granularity = granularity self.quantity = quantity self.fields = fields self.totalGrossSales = totalGrossSales @@ -104,7 +94,7 @@ private extension OrderStats { extension OrderStats: Comparable { public static func == (lhs: OrderStats, rhs: OrderStats) -> Bool { return lhs.date == rhs.date && - lhs.unit == rhs.unit && + lhs.granularity == rhs.granularity && lhs.quantity == rhs.quantity && lhs.fields == rhs.fields && lhs.totalGrossSales == rhs.totalGrossSales && @@ -120,7 +110,6 @@ extension OrderStats: Comparable { public static func < (lhs: OrderStats, rhs: OrderStats) -> Bool { return lhs.date < rhs.date || - (lhs.date == rhs.date && lhs.unit < rhs.unit) || - (lhs.date == rhs.date && lhs.unit == rhs.unit && lhs.quantity < rhs.quantity) + (lhs.date == rhs.date && lhs.quantity < rhs.quantity) } } diff --git a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift index 64f4f4da763..24a85b80106 100644 --- a/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/OrderStatsMapperTests.swift @@ -14,7 +14,7 @@ class OrderStatsMapperTests: XCTestCase { return } - XCTAssertEqual(dayStats.unit, "day") + XCTAssertEqual(dayStats.granularity, .day) XCTAssertEqual(dayStats.date, "2018-06-08") XCTAssertEqual(dayStats.quantity, "31") XCTAssertEqual(dayStats.fields.count, 18) @@ -77,7 +77,7 @@ class OrderStatsMapperTests: XCTestCase { return } - XCTAssertEqual(weekStats.unit, "week") + XCTAssertEqual(weekStats.granularity, .week) XCTAssertEqual(weekStats.date, "2018-W30") XCTAssertEqual(weekStats.quantity, "31") XCTAssertEqual(weekStats.fields.count, 18) @@ -160,7 +160,7 @@ class OrderStatsMapperTests: XCTestCase { return } - XCTAssertEqual(monthStats.unit, "month") + XCTAssertEqual(monthStats.granularity, .month) XCTAssertEqual(monthStats.date, "2018-06") XCTAssertEqual(monthStats.quantity, "12") XCTAssertEqual(monthStats.fields.count, 18) @@ -223,7 +223,7 @@ class OrderStatsMapperTests: XCTestCase { return } - XCTAssertEqual(yearStats.unit, "year") + XCTAssertEqual(yearStats.granularity, .year) XCTAssertEqual(yearStats.date, "2018") XCTAssertEqual(yearStats.quantity, "4") XCTAssertEqual(yearStats.fields.count, 18) From 19a7dccf9f54a21e5b7f33043ae0370a617c407e Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 2 Aug 2018 12:18:23 -0500 Subject: [PATCH 036/112] Added OrderStatsRemoteTests --- .../Networking.xcodeproj/project.pbxproj | 4 ++ .../Networking/Remote/OrderStatsRemote.swift | 4 +- .../Remote/OrderStatsRemoteTests.swift | 56 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 Networking/NetworkingTests/Remote/OrderStatsRemoteTests.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 1b851cdd7aa..95919fb190a 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */; }; 748D424E210FB1F500CF7D1B /* order-stats-day.json in Resources */ = {isa = PBXBuildFile; fileRef = 748D424D210FB1F500CF7D1B /* order-stats-day.json */; }; 74A1196C2110F4BB00E1E5F0 /* OrderStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */; }; + 74AE244D2113704C00CA8C54 /* OrderStatsRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AE244C2113704C00CA8C54 /* OrderStatsRemoteTests.swift */; }; 74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */; }; 74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06520EEB76400B6EDC9 /* order-notes.json */; }; 74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */; }; @@ -103,6 +104,7 @@ 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsMapper.swift; sourceTree = ""; }; 748D424D210FB1F500CF7D1B /* order-stats-day.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-day.json"; sourceTree = ""; }; 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStats.swift; sourceTree = ""; }; + 74AE244C2113704C00CA8C54 /* OrderStatsRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsRemoteTests.swift; sourceTree = ""; }; 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNote.swift; sourceTree = ""; }; 74C8F06520EEB76400B6EDC9 /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = ""; }; 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapper.swift; sourceTree = ""; }; @@ -225,6 +227,7 @@ B5969E1420A47F99005E9DF1 /* RemoteTests.swift */, B505F6D620BEE58800BB1B69 /* AccountRemoteTests.swift */, B518662920A09C6F00037A38 /* OrdersRemoteTests.swift */, + 74AE244C2113704C00CA8C54 /* OrderStatsRemoteTests.swift */, ); path = Remote; sourceTree = ""; @@ -653,6 +656,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 74AE244D2113704C00CA8C54 /* OrderStatsRemoteTests.swift in Sources */, B505F6D320BEE3A500BB1B69 /* AccountMapperTests.swift in Sources */, B5C6FCC820A32E4800A4F8E4 /* DateFormatterWooTests.swift in Sources */, 74C8F06A20EEBC8C00B6EDC9 /* OrderMapperTests.swift in Sources */, diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift index 96205394fd5..1246908b515 100644 --- a/Networking/Networking/Remote/OrderStatsRemote.swift +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -15,11 +15,11 @@ public class OrderStatsRemote: Remote { /// - quantity: How many `unit`s to fetch /// - completion: Closure to be executed upon completion. /// - public func loadOrderStats(for siteID: Int, unit: OrderStatGranularity, latestDateToInclude: String, quantity: String, completion: @escaping (OrderStats?, Error?) -> Void) { + public func loadOrderStats(for siteID: Int, unit: OrderStatGranularity, latestDateToInclude: String, quantity: Int, completion: @escaping (OrderStats?, Error?) -> Void) { let path = "\(Constants.sitesPath)/\(siteID)/\(Constants.orderStatsPath)/" let parameters = [ParameterKeys.unit: unit.rawValue, ParameterKeys.date: latestDateToInclude, - ParameterKeys.quantity: quantity] + ParameterKeys.quantity: String(quantity)] let request = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .get, path: path, parameters: parameters) let mapper = OrderStatsMapper() enqueue(request, mapper: mapper, completion: completion) diff --git a/Networking/NetworkingTests/Remote/OrderStatsRemoteTests.swift b/Networking/NetworkingTests/Remote/OrderStatsRemoteTests.swift new file mode 100644 index 00000000000..0473367f200 --- /dev/null +++ b/Networking/NetworkingTests/Remote/OrderStatsRemoteTests.swift @@ -0,0 +1,56 @@ +import XCTest +@testable import Networking + + +/// OrderStatsRemote Unit Tests +/// +class OrderStatsRemoteTests: XCTestCase { + + /// Dummy Network Wrapper + /// + let network = MockupNetwork() + + /// Dummy Site ID + /// + let sampleSiteID = 1234 + + /// Repeat always! + /// + override func setUp() { + network.removeAllSimulatedResponses() + } + + + /// Verifies that loadOrderStats properly parses the `OrderStats` sample response. + /// + func testLoadOrderStatsProperlyReturnsParsedStats() { + let remote = OrderStatsRemote(network: network) + let expectation = self.expectation(description: "Load order stats") + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/orders/", filename: "order-stats-day") + + remote.loadOrderStats(for: sampleSiteID, unit: .day, latestDateToInclude: "1955-11-05", quantity: 31) { (orderStats, error) in + XCTAssertNil(error) + XCTAssertNotNil(orderStats) + XCTAssertEqual(orderStats?.items?.count, 31) + expectation.fulfill() + } + + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that loadOrderStats properly relays Networking Layer errors. + /// + func testLoadOrderStatsProperlyRelaysNetwokingErrors() { + let remote = OrderStatsRemote(network: network) + let expectation = self.expectation(description: "Load order stats contains errors") + + remote.loadOrderStats(for: sampleSiteID, unit: .day, latestDateToInclude: "1955-11-05", quantity: 31) { (orderStats, error) in + XCTAssertNil(orderStats) + XCTAssertNotNil(error) + expectation.fulfill() + } + + wait(for: [expectation], timeout: Constants.expectationTimeout) + } +} From 075209beadde48694703f179f7d53b793e15e541 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 2 Aug 2018 12:20:49 -0500 Subject: [PATCH 037/112] Fixed typo on order stats remote param name --- Networking/Networking/Remote/OrderStatsRemote.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift index 1246908b515..433f51a73cc 100644 --- a/Networking/Networking/Remote/OrderStatsRemote.swift +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -38,6 +38,6 @@ private extension OrderStatsRemote { enum ParameterKeys { static let unit: String = "unit" static let date: String = "date" - static let quantity: String = "page" + static let quantity: String = "quantity" } } From ada2e126a268526c25227be7dec2e827403d355b Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 14:33:04 -0300 Subject: [PATCH 038/112] OrderAction: Updates Callback --- Yosemite/Yosemite/Actions/OrderAction.swift | 2 +- Yosemite/Yosemite/Stores/OrderStore.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Yosemite/Yosemite/Actions/OrderAction.swift b/Yosemite/Yosemite/Actions/OrderAction.swift index cad752fc6bb..39999ea4692 100644 --- a/Yosemite/Yosemite/Actions/OrderAction.swift +++ b/Yosemite/Yosemite/Actions/OrderAction.swift @@ -6,7 +6,7 @@ import Networking // MARK: - OrderAction: Defines all of the Actions supported by the OrderStore. // public enum OrderAction: Action { - case retrieveOrders(siteID: Int, onCompletion: ([Order]?, Error?) -> Void) + case retrieveOrders(siteID: Int, onCompletion: (Error?) -> Void) case retrieveOrder(siteID: Int, orderID: Int, onCompletion: (Order?, Error?) -> Void) case updateOrder(siteID: Int, orderID: Int, status: OrderStatus, onCompletion: (Error?) -> Void) } diff --git a/Yosemite/Yosemite/Stores/OrderStore.swift b/Yosemite/Yosemite/Stores/OrderStore.swift index a81eaf23af7..3beac120ef0 100644 --- a/Yosemite/Yosemite/Stores/OrderStore.swift +++ b/Yosemite/Yosemite/Stores/OrderStore.swift @@ -39,17 +39,17 @@ private extension OrderStore { /// Retrieves the orders associated with a given Site ID (if any!). /// - func retrieveOrders(siteID: Int, onCompletion: @escaping ([Order]?, Error?) -> Void) { + func retrieveOrders(siteID: Int, onCompletion: @escaping (Error?) -> Void) { let remote = OrdersRemote(network: network) remote.loadAllOrders(for: siteID) { [weak self] (orders, error) in guard let orders = orders else { - onCompletion(nil, error) + onCompletion(error) return } self?.upsertStoredOrders(readOnlyOrders: orders) - onCompletion(orders, nil) + onCompletion(nil) } } From c1bde6ca4e7ddd846fe9c9bc7312071c3f218bac Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 14:33:18 -0300 Subject: [PATCH 039/112] ResultsController: New Public API --- .../Yosemite/Tools/ResultsController.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Yosemite/Yosemite/Tools/ResultsController.swift b/Yosemite/Yosemite/Tools/ResultsController.swift index ad6b860edf8..93d669b4672 100644 --- a/Yosemite/Yosemite/Tools/ResultsController.swift +++ b/Yosemite/Yosemite/Tools/ResultsController.swift @@ -23,7 +23,11 @@ public class ResultsController { /// Filtering Predicate to be applied to the Results. /// - private let predicate: NSPredicate? + public var predicate: NSPredicate? { + didSet { + refreshFetchedObjects(predicate: predicate) + } + } /// Results's Sort Descriptor. /// @@ -105,13 +109,17 @@ public class ResultsController { try controller.performFetch() } - /// Returns the fetched object at a given indexPath. /// public func object(at indexPath: IndexPath) -> T.ReadOnlyType { return controller.object(at: indexPath).toReadOnly() } + /// Indicates if there are any Objects matching the specified criteria. + /// + public var isEmpty: Bool { + return controller.fetchedObjects?.isEmpty ?? true + } /// Returns an array of all of the (ReadOnly) Fetched Objects. /// @@ -133,6 +141,12 @@ public class ResultsController { return readOnlySections ?? [] } + /// Refreshes all of the Fetched Objects, so that the new criteria is met. + /// + private func refreshFetchedObjects(predicate: NSPredicate?) { + controller.fetchRequest.predicate = predicate + try? controller.performFetch() + } /// Initializes the FetchedResultsController /// @@ -140,7 +154,6 @@ public class ResultsController { controller.delegate = internalDelegate } - /// Initializes FRC's Event Forwarding. /// private func setupEventsForwarding() { From 21b59b58202fbabe4c23c8dd2cda07df4b8be725 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 14:33:46 -0300 Subject: [PATCH 040/112] OrdersViewController: Wiring ResultsController --- .../Orders/OrdersViewController.swift | 207 +++++++++--------- 1 file changed, 99 insertions(+), 108 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift index 38c794ecee1..bfb61f9fa7b 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift @@ -8,59 +8,73 @@ import CocoaLumberjack /// class OrdersViewController: UIViewController { - // MARK: - IBOutlets - - @IBOutlet private weak var tableView: UITableView! - - // MARK: - Properties - - private var orders = [Order]() - private var filterResults = [Order]() - private var isUsingFilterAction = false + /// Main TableView. + /// + @IBOutlet private var tableView: UITableView! + /// Pull To Refresh Support. + /// private lazy var refreshControl: UIRefreshControl = { let refreshControl = UIRefreshControl() refreshControl.addTarget(self, action: #selector(pullToRefresh(sender:)), for: .valueChanged) return refreshControl }() + /// ResultsController: Surrounds us. Binds the galaxy together. And also, keeps the UITableView <> (Stored) Orders in sync. + /// + private lazy var resultsController: ResultsController = { + let storageManager = AppDelegate.shared.storageManager + let descriptor = NSSortDescriptor(keyPath: \StorageOrder.dateCreated, ascending: false) - // MARK: - Computed Properties + return ResultsController(storageManager: storageManager, sortedBy: [descriptor]) + }() + /// Indicates if there are any Objects matching the criteria (or not). + /// private var displaysNoResults: Bool { - return filterResults.isEmpty && isUsingFilterAction + return resultsController.isEmpty } - private var siteID: Int? { - return StoresManager.shared.sessionManager.defaultStoreID + /// Indicates if a Filters is in active, or not. + /// + private var displaysFilteredResults: Bool { + return resultsController.predicate != nil } // MARK: - View Lifecycle + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + fatalError() + } + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) + tabBarItem.title = NSLocalizedString("Orders", comment: "Orders title") tabBarItem.image = Gridicon.iconOfType(.pages) } override func viewDidLoad() { super.viewDidLoad() + configureNavigation() configureTableView() + configureResultsController() } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - if orders.isEmpty { - ensureRefreshControlIsVisible() - syncOrders() - } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + syncOrders() } +} - // MARK: - User Interface Initialization +// MARK: - User Interface Initialization +// +private extension OrdersViewController { func configureNavigation() { title = NSLocalizedString("Orders", comment: "Orders title") @@ -69,76 +83,67 @@ class OrdersViewController: UIViewController { target: self, action: #selector(rightButtonTapped)) rightBarButton.tintColor = .white - navigationItem.setRightBarButton(rightBarButton, animated: false) + navigationItem.rightBarButtonItem = rightBarButton // Don't show the Order title in the next-view's back button - let backButton = UIBarButtonItem(title: String(), - style: .plain, - target: nil, - action: nil) - - navigationItem.backBarButtonItem = backButton + navigationItem.backBarButtonItem = UIBarButtonItem(title: String(), style: .plain, target: nil, action: nil) } func configureTableView() { - tableView.estimatedRowHeight = Constants.rowHeight + tableView.estimatedRowHeight = Constants.estimatedRowHeight tableView.rowHeight = UITableViewAutomaticDimension - let nib = UINib(nibName: NoResultsTableViewCell.reuseIdentifier, bundle: nil) - tableView.register(nib, forCellReuseIdentifier: NoResultsTableViewCell.reuseIdentifier) tableView.refreshControl = refreshControl + tableView.register(NoResultsTableViewCell.loadNib(), forCellReuseIdentifier: NoResultsTableViewCell.reuseIdentifier) + } + + func configureResultsController() { + resultsController.startForwardingEvents(to: tableView) + try? resultsController.performFetch() } +} - // MARK: - Actions + +// MARK: - Actions +// +extension OrdersViewController { @objc func rightButtonTapped() { let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) actionSheet.view.tintColor = StyleManager.wooCommerceBrandColor - let dismissAction = UIAlertAction(title: NSLocalizedString("Dismiss", comment: "Dismiss the action sheet"), style: .cancel) - actionSheet.addAction(dismissAction) - let allAction = UIAlertAction(title: NSLocalizedString("All", comment: "All filter title"), style: .default) { [weak self ] action in - self?.isUsingFilterAction = false - self?.tableView.reloadData() + actionSheet.addCancelActionWithTitle(FilterAlert.cancelText) + actionSheet.addDefaultActionWithTitle(FilterAlert.allText) { [weak self] _ in + self?.resetOrderFilters() } - actionSheet.addAction(allAction) for status in OrderStatusViewModel.allOrderStatuses { - let action = UIAlertAction(title: status.description, style: .default) { action in - self.filterAction(status) + actionSheet.addDefaultActionWithTitle(status.description) { [weak self] _ in + self?.filterOrders(by: status) } - actionSheet.addAction(action) } + present(actionSheet, animated: true) } @objc func pullToRefresh(sender: UIRefreshControl) { - clearOrders() - syncOrders() + syncOrders { [weak self] in + self?.refreshControl.endRefreshing() + } } +} - func filterAction(_ status: OrderStatus) { - if case .custom(_) = status { - filterByAllCustomStatuses() - return - } - filterResults = orders.filter { order in - return order.status.description.contains(status.description) - } +// MARK: - Filters +// +private extension OrdersViewController { - isUsingFilterAction = filterResults.count != orders.count + func filterOrders(by status: OrderStatus) { + resultsController.predicate = NSPredicate(format: "status = %@", status.rawValue) tableView.reloadData() } - func filterByAllCustomStatuses() { - var customOrders = [Order]() - for order in orders { - if case .custom(_) = order.status { - customOrders.append(order) - } - } - filterResults = customOrders - isUsingFilterAction = filterResults.count != orders.count + func resetOrderFilters() { + resultsController.predicate = nil tableView.reloadData() } } @@ -147,42 +152,23 @@ class OrdersViewController: UIViewController { // MARK: - Sync'ing Helpers // private extension OrdersViewController { - func syncOrders() { - guard let siteID = siteID else { + + func syncOrders(onCompletion: (() -> Void)? = nil) { + guard let siteID = StoresManager.shared.sessionManager.defaultStoreID else { + onCompletion?() return } - let action = OrderAction.retrieveOrders(siteID: siteID) { [weak self] (orders, error) in - self?.refreshControl.endRefreshing() - - guard let orders = orders else { - DDLogError("⛔️ Error synchronizing orders: \(error.debugDescription)") - return + let action = OrderAction.retrieveOrders(siteID: siteID) { error in + if let error = error { + DDLogError("⛔️ Error synchronizing orders: \(error)") } - self?.orders = orders - self?.tableView.reloadData() + onCompletion?() } StoresManager.shared.dispatch(action) } - - func clearOrders() { - orders = [] - isUsingFilterAction = false - tableView.reloadData() - } - - - func ensureRefreshControlIsVisible() { - guard tableView.contentOffset.y == 0 else { - return - } - - let point = CGPoint(x: 0, y: -refreshControl.frame.height) - tableView.setContentOffset(point, animated: true) - refreshControl.beginRefreshing() - } } @@ -190,12 +176,9 @@ private extension OrdersViewController { // private extension OrdersViewController { - func order(at indexPath: IndexPath) -> Order { - return isUsingFilterAction ? filterResults[indexPath.row] : orders[indexPath.row] - } - func detailsViewModel(at indexPath: IndexPath) -> OrderDetailsViewModel { - return OrderDetailsViewModel(order: order(at: indexPath)) + let order = resultsController.object(at: indexPath) + return OrderDetailsViewModel(order: order) } } @@ -203,26 +186,29 @@ private extension OrdersViewController { // MARK: - UITableViewDataSource Conformance // extension OrdersViewController: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { - return 1 +// guard !displaysNoResults else { +// return Constants.filterResultsNotFoundSectionCount +// } + + return resultsController.sections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if isUsingFilterAction == true { - if filterResults.isEmpty { - return Constants.filterResultsNotFoundRowCount - } - return filterResults.count - } - return orders.count +// guard !displaysNoResults else { +// return Constants.filterResultsNotFoundRowCount +// } + + return resultsController.sections[section].numberOfObjects } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard !displaysNoResults else { - let cell = tableView.dequeueReusableCell(withIdentifier: NoResultsTableViewCell.reuseIdentifier, for: indexPath) as! NoResultsTableViewCell - cell.configure(text: NSLocalizedString("No results found. Clear the filter to try again.", comment: "Displays message to user when no filter results were found.")) - return cell - } +// guard !displaysNoResults else { +// let cell = tableView.dequeueReusableCell(withIdentifier: NoResultsTableViewCell.reuseIdentifier, for: indexPath) as! NoResultsTableViewCell +// cell.title = NSLocalizedString("No results found. Clear the filter to try again.", comment: "Displays message to user when no filter results were found.") +// return cell +// } guard let cell = tableView.dequeueReusableCell(withIdentifier: OrderListCell.reuseIdentifier, for: indexPath) as? OrderListCell else { fatalError() @@ -245,6 +231,7 @@ extension OrdersViewController: UITableViewDataSource { // MARK: - UITableViewDelegate Conformance // extension OrdersViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return UITableViewAutomaticDimension } @@ -272,9 +259,13 @@ extension OrdersViewController: UITableViewDelegate { // MARK: - Constants // private extension OrdersViewController { - struct Constants { - static let rowHeight = CGFloat(86) + enum FilterAlert { + static let cancelText = NSLocalizedString("Dismiss", comment: "Dismiss the action sheet") + static let allText = NSLocalizedString("All", comment: "All filter title") + } + + enum Constants { + static let estimatedRowHeight = CGFloat(86) static let orderDetailsSegue = "ShowOrderDetailsViewController" - static let filterResultsNotFoundRowCount = 1 } } From cb60bf8a8fef5c3558842755649260812d8f41bf Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 14:47:07 -0300 Subject: [PATCH 041/112] Implements OrderStatus+Woo --- WooCommerce/Classes/Model/OrderStatus+Woo.swift | 14 ++++++++++++++ WooCommerce/WooCommerce.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 18 insertions(+) create mode 100644 WooCommerce/Classes/Model/OrderStatus+Woo.swift diff --git a/WooCommerce/Classes/Model/OrderStatus+Woo.swift b/WooCommerce/Classes/Model/OrderStatus+Woo.swift new file mode 100644 index 00000000000..070dbdc9d6d --- /dev/null +++ b/WooCommerce/Classes/Model/OrderStatus+Woo.swift @@ -0,0 +1,14 @@ +import Foundation +import Yosemite + + +// MARK: - OrderStatus Helper Methods +// +extension OrderStatus { + + /// Returns a collection of all of the known Order Status + /// + static var knownStatus: [OrderStatus] { + return [.pending, .processing, .onHold, .failed, .cancelled, .completed, .refunded] + } +} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index d17e0246912..c797fefae6a 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -95,6 +95,7 @@ B5DBF3C320E1484400B53AED /* StoresManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3C220E1484400B53AED /* StoresManagerTests.swift */; }; B5DBF3C520E148E000B53AED /* DeauthenticatedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3C420E148E000B53AED /* DeauthenticatedState.swift */; }; B5DBF3CB20E149CC00B53AED /* AuthenticatedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3CA20E149CC00B53AED /* AuthenticatedState.swift */; }; + B5E96B3821137AA100DF68D0 /* OrderStatus+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E96B3721137AA100DF68D0 /* OrderStatus+Woo.swift */; }; CE17C2E220ACA06800AFBD20 /* BillingDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE17C2E020ACA06800AFBD20 /* BillingDetailsTableViewCell.swift */; }; CE17C2E320ACA06800AFBD20 /* BillingDetailsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE17C2E120ACA06800AFBD20 /* BillingDetailsTableViewCell.xib */; }; CE1CCB402056F21C000EE3AC /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1CCB3F2056F21C000EE3AC /* Style.swift */; }; @@ -269,6 +270,7 @@ B5DBF3C220E1484400B53AED /* StoresManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoresManagerTests.swift; sourceTree = ""; }; B5DBF3C420E148E000B53AED /* DeauthenticatedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeauthenticatedState.swift; sourceTree = ""; }; B5DBF3CA20E149CC00B53AED /* AuthenticatedState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticatedState.swift; sourceTree = ""; }; + B5E96B3721137AA100DF68D0 /* OrderStatus+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderStatus+Woo.swift"; sourceTree = ""; }; BABE5E07DD787ECA6D2A76DE /* Pods_WooCommerce.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WooCommerce.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CE17C2E020ACA06800AFBD20 /* BillingDetailsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BillingDetailsTableViewCell.swift; sourceTree = ""; }; CE17C2E120ACA06800AFBD20 /* BillingDetailsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BillingDetailsTableViewCell.xib; sourceTree = ""; }; @@ -533,6 +535,7 @@ children = ( B57B67892107546E00AF8905 /* Address+Woo.swift */, B57B678B2107638C00AF8905 /* Order+Woo.swift */, + B5E96B3721137AA100DF68D0 /* OrderStatus+Woo.swift */, ); path = Model; sourceTree = ""; @@ -1064,6 +1067,7 @@ B58B4AC02108FF6100076FDD /* Array+Helpers.swift in Sources */, CE4DDB7B20DD312400D32EC8 /* Date+Helpers.swift in Sources */, B50911322049E27A007D25DC /* SettingsViewController.swift in Sources */, + B5E96B3821137AA100DF68D0 /* OrderStatus+Woo.swift in Sources */, B57C744720F55BC800EEFC87 /* UIView+Helpers.swift in Sources */, B5A82EE221025C450053ADC8 /* FulfillViewController.swift in Sources */, B55D4C0620B6027200D7A50F /* AuthenticationManager.swift in Sources */, From 7489962ac92ffe01b91c59dc87780f286326da5e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 14:47:17 -0300 Subject: [PATCH 042/112] OrderStatusViewModel: Nukes Static Helpers --- WooCommerce/Classes/ViewModels/OrderStatusViewModel.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/WooCommerce/Classes/ViewModels/OrderStatusViewModel.swift b/WooCommerce/Classes/ViewModels/OrderStatusViewModel.swift index 4d5bb4a0581..d38df2873d4 100644 --- a/WooCommerce/Classes/ViewModels/OrderStatusViewModel.swift +++ b/WooCommerce/Classes/ViewModels/OrderStatusViewModel.swift @@ -8,14 +8,6 @@ class OrderStatusViewModel { self.orderStatus = orderStatus } - static var allOrderStatuses: [OrderStatus] { - return [.pending, .processing, .onHold, .failed, .cancelled, .completed, .refunded, .custom(NSLocalizedString("Custom", comment: "Title for button that catches all custom labels and displays them on the order list"))] - } - - static var allOrderStatusDescriptions: [String] { - return allOrderStatuses.map { $0.description } - } - var backgroundColor: UIColor { switch orderStatus { case .processing: From 75efeb763305600640d3c478f023e699a30c37e8 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 14:47:28 -0300 Subject: [PATCH 043/112] OrdersViewController: Updating Filtering Support --- .../Orders/OrdersViewController.swift | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift index bfb61f9fa7b..6b298fa95b5 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift @@ -116,12 +116,16 @@ extension OrdersViewController { self?.resetOrderFilters() } - for status in OrderStatusViewModel.allOrderStatuses { + for status in OrderStatus.knownStatus { actionSheet.addDefaultActionWithTitle(status.description) { [weak self] _ in - self?.filterOrders(by: status) + self?.displayOrders(with: status) } } + actionSheet.addDefaultActionWithTitle(FilterAlert.customText) { [weak self] _ in + self?.displayOrdersWithUnknownStatus() + } + present(actionSheet, animated: true) } @@ -137,11 +141,17 @@ extension OrdersViewController { // private extension OrdersViewController { - func filterOrders(by status: OrderStatus) { + func displayOrders(with status: OrderStatus) { resultsController.predicate = NSPredicate(format: "status = %@", status.rawValue) tableView.reloadData() } + func displayOrdersWithUnknownStatus() { + let knownStatus = OrderStatus.knownStatus.map { $0.rawValue } + resultsController.predicate = NSPredicate(format: "NOT (status in %@)", knownStatus) + tableView.reloadData() + } + func resetOrderFilters() { resultsController.predicate = nil tableView.reloadData() @@ -260,8 +270,9 @@ extension OrdersViewController: UITableViewDelegate { // private extension OrdersViewController { enum FilterAlert { - static let cancelText = NSLocalizedString("Dismiss", comment: "Dismiss the action sheet") static let allText = NSLocalizedString("All", comment: "All filter title") + static let cancelText = NSLocalizedString("Dismiss", comment: "Dismiss the action sheet") + static let customText = NSLocalizedString("Custom", comment: "Title for button that catches all custom labels and displays them on the order list") } enum Constants { From 5b1f41a4c936e61f9a6a78b3df6304a1490def48 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 14:52:15 -0300 Subject: [PATCH 044/112] OrderStoreTests: Fixing Unit Tests --- .../Stores/OrderStoreTests.swift | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift b/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift index 57f3e9a0753..4cb6c698c48 100644 --- a/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift @@ -51,17 +51,12 @@ class OrderStoreTests: XCTestCase { func testRetrieveOrdersReturnsExpectedFields() { let expectation = self.expectation(description: "Retrieve order list") let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) - let remoteOrder = sampleOrder() network.simulateResponse(requestUrlSuffix: "orders", filename: "orders") - let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { (orders, error) in + let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { error in XCTAssertNil(error) - guard let orders = orders else { - XCTFail() - return - } - XCTAssertEqual(orders.count, 3, "Orders count should be 3") - XCTAssertTrue(orders.contains(remoteOrder)) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Order.self), 3) + expectation.fulfill() } @@ -78,9 +73,10 @@ class OrderStoreTests: XCTestCase { network.simulateResponse(requestUrlSuffix: "orders", filename: "orders") XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Order.self), 0) - let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { (orders, error) in + let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { error in XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Order.self), 3) XCTAssertNil(error) + expectation.fulfill() } @@ -98,8 +94,7 @@ class OrderStoreTests: XCTestCase { network.simulateResponse(requestUrlSuffix: "orders", filename: "orders") XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Order.self), 0) - let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { (orders, error) in - XCTAssertNotNil(orders) + let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { error in XCTAssertNil(error) let predicate = NSPredicate(format: "orderID = %ld", remoteOrder.orderID) @@ -123,8 +118,7 @@ class OrderStoreTests: XCTestCase { let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) network.simulateResponse(requestUrlSuffix: "orders", filename: "generic_error") - let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { (orders, error) in - XCTAssertNil(orders) + let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { error in XCTAssertNotNil(error) expectation.fulfill() @@ -140,9 +134,8 @@ class OrderStoreTests: XCTestCase { let expectation = self.expectation(description: "Retrieve orders empty response") let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) - let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { (orders, error) in + let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { error in XCTAssertNotNil(error) - XCTAssertNil(orders) expectation.fulfill() } From 34acc954312ced13d2d697e8b9276002322248f8 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 14:56:19 -0300 Subject: [PATCH 045/112] OrdersViewController: Renames few methods --- .../ViewRelated/Orders/OrdersViewController.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift index 6b298fa95b5..497a42d8a3e 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift @@ -81,7 +81,7 @@ private extension OrdersViewController { let rightBarButton = UIBarButtonItem(image: Gridicon.iconOfType(.menus), style: .plain, target: self, - action: #selector(rightButtonTapped)) + action: #selector(displayFiltersAlert)) rightBarButton.tintColor = .white navigationItem.rightBarButtonItem = rightBarButton @@ -107,7 +107,7 @@ private extension OrdersViewController { // extension OrdersViewController { - @objc func rightButtonTapped() { + @IBAction func displayFiltersAlert() { let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) actionSheet.view.tintColor = StyleManager.wooCommerceBrandColor @@ -129,9 +129,9 @@ extension OrdersViewController { present(actionSheet, animated: true) } - @objc func pullToRefresh(sender: UIRefreshControl) { - syncOrders { [weak self] in - self?.refreshControl.endRefreshing() + @IBAction func pullToRefresh(sender: UIRefreshControl) { + syncOrders { + sender.endRefreshing() } } } From f4151769ca670afc4b3665df423599cc213121f0 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 15:02:54 -0300 Subject: [PATCH 046/112] OrdersViewController: Adds TODO(s) --- .../Classes/ViewRelated/Orders/OrdersViewController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift index 497a42d8a3e..b05ad183924 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift @@ -198,6 +198,7 @@ private extension OrdersViewController { extension OrdersViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { +// TODO: Support Loading State + Empty State // guard !displaysNoResults else { // return Constants.filterResultsNotFoundSectionCount // } @@ -206,6 +207,7 @@ extension OrdersViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { +// TODO: Support Loading State + Empty State // guard !displaysNoResults else { // return Constants.filterResultsNotFoundRowCount // } @@ -214,6 +216,7 @@ extension OrdersViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { +// TODO: Support Empty State // guard !displaysNoResults else { // let cell = tableView.dequeueReusableCell(withIdentifier: NoResultsTableViewCell.reuseIdentifier, for: indexPath) as! NoResultsTableViewCell // cell.title = NSLocalizedString("No results found. Clear the filter to try again.", comment: "Displays message to user when no filter results were found.") From efbb976609f977ffa98b5c3e6f7cd111d3c48c6e Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 2 Aug 2018 13:43:27 -0500 Subject: [PATCH 047/112] Cleaned up OrderStatGranularity --- .../Model/OrderStatGranularity.swift | 49 +------------------ 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/Networking/Networking/Model/OrderStatGranularity.swift b/Networking/Networking/Model/OrderStatGranularity.swift index 4b1712718c2..21e37a60b6b 100644 --- a/Networking/Networking/Model/OrderStatGranularity.swift +++ b/Networking/Networking/Model/OrderStatGranularity.swift @@ -3,46 +3,13 @@ import Foundation /// Represents the data granularity for a specific `OrderStats` instance (e.g. day, week, month, year) /// -public enum OrderStatGranularity: Decodable { +public enum OrderStatGranularity: String, Decodable { case day case week case month case year } - -// MARK: - RawRepresentable Conformance -// -extension OrderStatGranularity: RawRepresentable { - - /// Designated Initializer. - /// - public init(rawValue: String) { - switch rawValue { - case Keys.week: - self = .week - case Keys.month: - self = .month - case Keys.year: - self = .year - default: - self = .day - } - } - - /// Returns the current Enum Case's Raw Value - /// - public var rawValue: String { - switch self { - case .day: return Keys.day - case .week: return Keys.week - case .month: return Keys.month - case .year: return Keys.year - } - } -} - - // MARK: - StringConvertible Conformance // extension OrderStatGranularity: CustomStringConvertible { @@ -63,17 +30,3 @@ extension OrderStatGranularity: CustomStringConvertible { } } - -// MARK: - Constants! -// -private extension OrderStatGranularity { - - /// Enum containing the 'Known' OrderStatGranularity Keys - /// - private enum Keys { - static let day = "day" - static let week = "week" - static let month = "month" - static let year = "year" - } -} From b648b1ed2bb721cd84ec5fe2a7506a2b680d8fe6 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 16:00:24 -0300 Subject: [PATCH 048/112] ResultsController+UIKit: Updating Comment --- WooCommerce/Classes/Tools/ResultsController+UIKit.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/Tools/ResultsController+UIKit.swift b/WooCommerce/Classes/Tools/ResultsController+UIKit.swift index 152b11e4c13..057ccb72791 100644 --- a/WooCommerce/Classes/Tools/ResultsController+UIKit.swift +++ b/WooCommerce/Classes/Tools/ResultsController+UIKit.swift @@ -50,7 +50,7 @@ extension ResultsController { // private extension ResultsController { - /// Sets up all of the Content Events from the inner FRC over to the specified TableView. + /// Sets up all of the Content Events from the inner NSFetchedResultsController (FRC) over to the specified TableView. /// func startForwardingContentEvents(to tableView: UITableView, with animations: ResultsTableAnimations) { onWillChangeContent = { [weak tableView] in From 09bc60def3c1e10d4fe6eae26f00542abccc3a68 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 16:18:19 -0300 Subject: [PATCH 049/112] OrdersViewController: Renames nested type --- .../ViewRelated/Orders/OrdersViewController.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift index b05ad183924..a534e7af89d 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift @@ -111,8 +111,8 @@ extension OrdersViewController { let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) actionSheet.view.tintColor = StyleManager.wooCommerceBrandColor - actionSheet.addCancelActionWithTitle(FilterAlert.cancelText) - actionSheet.addDefaultActionWithTitle(FilterAlert.allText) { [weak self] _ in + actionSheet.addCancelActionWithTitle(FilterAction.dismiss) + actionSheet.addDefaultActionWithTitle(FilterAction.displayAll) { [weak self] _ in self?.resetOrderFilters() } @@ -122,7 +122,7 @@ extension OrdersViewController { } } - actionSheet.addDefaultActionWithTitle(FilterAlert.customText) { [weak self] _ in + actionSheet.addDefaultActionWithTitle(FilterAction.displayCustom) { [weak self] _ in self?.displayOrdersWithUnknownStatus() } @@ -272,10 +272,10 @@ extension OrdersViewController: UITableViewDelegate { // MARK: - Constants // private extension OrdersViewController { - enum FilterAlert { - static let allText = NSLocalizedString("All", comment: "All filter title") - static let cancelText = NSLocalizedString("Dismiss", comment: "Dismiss the action sheet") - static let customText = NSLocalizedString("Custom", comment: "Title for button that catches all custom labels and displays them on the order list") + enum FilterAction { + static let dismiss = NSLocalizedString("Dismiss", comment: "Dismiss the action sheet") + static let displayAll = NSLocalizedString("All", comment: "All filter title") + static let displayCustom = NSLocalizedString("Custom", comment: "Title for button that catches all custom labels and displays them on the order list") } enum Constants { From 46741cce78d7db3315d71a3f0997e2ce3afdd777 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Thu, 2 Aug 2018 15:09:45 -0500 Subject: [PATCH 050/112] Added OrderStatsStore and OrderStatsAction --- .../Extensions/DateFormatter+Woo.swift | 54 ++++++++++++++- .../Networking/Remote/OrderStatsRemote.swift | 2 +- .../Extensions/DateFormatterWooTests.swift | 54 ++++++++++++++- Yosemite/Yosemite.xcodeproj/project.pbxproj | 8 +++ .../Yosemite/Actions/OrderStatsAction.swift | 9 +++ .../Yosemite/Stores/OrderStatsStore.swift | 65 +++++++++++++++++++ 6 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 Yosemite/Yosemite/Actions/OrderStatsAction.swift create mode 100644 Yosemite/Yosemite/Stores/OrderStatsStore.swift diff --git a/Networking/Networking/Extensions/DateFormatter+Woo.swift b/Networking/Networking/Extensions/DateFormatter+Woo.swift index 577f0ff6485..8a3d93dfd06 100644 --- a/Networking/Networking/Extensions/DateFormatter+Woo.swift +++ b/Networking/Networking/Extensions/DateFormatter+Woo.swift @@ -3,7 +3,7 @@ import Foundation /// DateFormatter Extensions /// -extension DateFormatter { +public extension DateFormatter { /// Default Formatters /// @@ -11,7 +11,7 @@ extension DateFormatter { /// Date And Time Formatter /// - static let dateTimeFormatter: DateFormatter = { + public static let dateTimeFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(identifier: "GMT") @@ -19,4 +19,54 @@ extension DateFormatter { return formatter }() } + + + /// Stats Formatters + /// + struct Stats { + + /// Date formatter used for creating the properly-formatted date string for **day** granularity. Typically + /// used when setting the `latestDateToInclude` on `OrderStatsRemote`. + /// + public static let statsDayFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(identifier: "GMT") + formatter.dateFormat = "yyyy'-'MM'-'dd" + return formatter + }() + + /// Date formatter used for creating the properly-formatted date string for **week** granularity. Typically + /// used when setting the `latestDateToInclude` on `OrderStatsRemote`. + /// + public static let statsWeekFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(identifier: "GMT") + formatter.dateFormat = "yyyy'-W'ww" + return formatter + }() + + /// Date formatter used for creating the properly-formatted date string for **month** granularity. Typically + /// used when setting the `latestDateToInclude` on `OrderStatsRemote`. + /// + public static let statsMonthFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(identifier: "GMT") + formatter.dateFormat = "yyyy'-'MM" + return formatter + }() + + /// Date formatter used for creating the properly-formatted date string for **year** granularity. Typically + /// used when setting the `latestDateToInclude` on `OrderStatsRemote`. + /// + public static let statsYearFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(identifier: "GMT") + formatter.dateFormat = "yyyy" + return formatter + }() + } } diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift index 433f51a73cc..23afaf719ab 100644 --- a/Networking/Networking/Remote/OrderStatsRemote.swift +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -11,7 +11,7 @@ public class OrderStatsRemote: Remote { /// - Parameters: /// - siteID: The site ID /// - unit: Defines the granularity of the stats we are fetching (one of 'day', 'week', 'month', or 'year') - /// - latestDateToInclude: The latest date to include in the results. This string should match the `unit`, e.g.: 'day':'1955-11-05', 'week':'1955-W44', 'month':'1955-11', 'year':'1955' + /// - latestDateToInclude: The latest date to include in the results. This string should match the `unit`, e.g.: 'day':'1955-11-05', 'week':'1955-W44', 'month':'1955-11', 'year':'1955'. /// - quantity: How many `unit`s to fetch /// - completion: Closure to be executed upon completion. /// diff --git a/Networking/NetworkingTests/Extensions/DateFormatterWooTests.swift b/Networking/NetworkingTests/Extensions/DateFormatterWooTests.swift index 98bc362ff1c..4f6875efc72 100644 --- a/Networking/NetworkingTests/Extensions/DateFormatterWooTests.swift +++ b/Networking/NetworkingTests/Extensions/DateFormatterWooTests.swift @@ -11,7 +11,7 @@ class DateFormatterWooTests: XCTestCase { let datetimeAsString = "2018-01-24T16:21:48" - /// Verifies that a Woo Datetime is properly parsed by `DateFormatter.Defaults.dateTimeFormatter. + /// Verifies that a Woo Datetime is properly parsed by `DateFormatter.Defaults.dateTimeFormatter`. /// func testWooFormattedDateIsProperlyParsed() { guard let date = DateFormatter.Defaults.dateTimeFormatter.date(from: datetimeAsString) else { @@ -24,4 +24,56 @@ class DateFormatterWooTests: XCTestCase { XCTAssertEqual(calendar.component(.month, from: date), 1) XCTAssertEqual(calendar.component(.day, from: date), 24) } + + /// Verifies that a valid stats **day** string is produced by `DateFormatter.Defaults.statsDayFormatter`. + /// + func testStatsDayDateStringIsProperlyParsed() { + guard let date = DateFormatter.Defaults.dateTimeFormatter.date(from: datetimeAsString) else { + XCTFail() + return + } + + let dayDateString = DateFormatter.Stats.statsDayFormatter.string(from: date) + XCTAssertFalse(dayDateString.isEmpty) + XCTAssertEqual(dayDateString, "2018-01-24") + } + + /// Verifies that a valid stats **week** string is produced by `DateFormatter.Defaults.statsWeekFormatter`. + /// + func testStatsWeekDateStringIsProperlyParsed() { + guard let date = DateFormatter.Defaults.dateTimeFormatter.date(from: datetimeAsString) else { + XCTFail() + return + } + + let weekDateString = DateFormatter.Stats.statsWeekFormatter.string(from: date) + XCTAssertFalse(weekDateString.isEmpty) + XCTAssertEqual(weekDateString, "2018-W04") + } + + /// Verifies that a valid stats **month** string is produced by `DateFormatter.Defaults.statsMonthFormatter`. + /// + func testStatsMonthDateStringIsProperlyParsed() { + guard let date = DateFormatter.Defaults.dateTimeFormatter.date(from: datetimeAsString) else { + XCTFail() + return + } + + let monthDateString = DateFormatter.Stats.statsMonthFormatter.string(from: date) + XCTAssertFalse(monthDateString.isEmpty) + XCTAssertEqual(monthDateString, "2018-01") + } + + /// Verifies that a valid stats **year** string is produced by `DateFormatter.Defaults.statsYearFormatter`. + /// + func testStatsYearDateStringIsProperlyParsed() { + guard let date = DateFormatter.Defaults.dateTimeFormatter.date(from: datetimeAsString) else { + XCTFail() + return + } + + let yearDateString = DateFormatter.Stats.statsYearFormatter.string(from: date) + XCTAssertFalse(yearDateString.isEmpty) + XCTAssertEqual(yearDateString, "2018") + } } diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 80617bbea07..0e37c9ff7ba 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */; }; 7499936820EFC0ED00CF01CD /* OrderNoteStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */; }; 7499936C20EFC20100CF01CD /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 7499936B20EFC20100CF01CD /* order-notes.json */; }; + 74A18C562113827E00DCF8A8 /* OrderStatsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A18C552113827E00DCF8A8 /* OrderStatsStore.swift */; }; + 74A18C58211382A000DCF8A8 /* OrderStatsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A18C57211382A000DCF8A8 /* OrderStatsAction.swift */; }; 74A7688C20D45EBA00F9D437 /* OrderStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688B20D45EBA00F9D437 /* OrderStore.swift */; }; 74A7688E20D45ED400F9D437 /* OrderStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */; }; 74A7689020D45F9300F9D437 /* OrderAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688F20D45F9300F9D437 /* OrderAction.swift */; }; @@ -70,6 +72,8 @@ 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteStore.swift; sourceTree = ""; }; 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteStoreTests.swift; sourceTree = ""; }; 7499936B20EFC20100CF01CD /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = ""; }; + 74A18C552113827E00DCF8A8 /* OrderStatsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsStore.swift; sourceTree = ""; }; + 74A18C57211382A000DCF8A8 /* OrderStatsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsAction.swift; sourceTree = ""; }; 74A7688B20D45EBA00F9D437 /* OrderStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStore.swift; sourceTree = ""; }; 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStoreTests.swift; sourceTree = ""; }; 74A7688F20D45F9300F9D437 /* OrderAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAction.swift; sourceTree = ""; }; @@ -167,6 +171,7 @@ B5BC736420D1A98500B5B6FA /* AccountStore.swift */, 74A7688B20D45EBA00F9D437 /* OrderStore.swift */, 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */, + 74A18C552113827E00DCF8A8 /* OrderStatsStore.swift */, ); path = Stores; sourceTree = ""; @@ -306,6 +311,7 @@ B5DC3CB020D1B8720063AC41 /* AccountAction.swift */, 74A7688F20D45F9300F9D437 /* OrderAction.swift */, 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */, + 74A18C57211382A000DCF8A8 /* OrderStatsAction.swift */, ); path = Actions; sourceTree = ""; @@ -510,6 +516,7 @@ B5DC3CB120D1B8720063AC41 /* AccountAction.swift in Sources */, B5BC736520D1A98500B5B6FA /* AccountStore.swift in Sources */, B56C1EC220EAE2E500D749F9 /* ReadOnlyConvertible.swift in Sources */, + 74A18C562113827E00DCF8A8 /* OrderStatsStore.swift in Sources */, B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */, B5B5C797208E49B600642956 /* Action+Internal.swift in Sources */, 74A7688C20D45EBA00F9D437 /* OrderStore.swift in Sources */, @@ -523,6 +530,7 @@ B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */, B5EED1A820F4F3CF00652449 /* Account+ReadOnlyConvertible.swift in Sources */, B5F2AE9720EBB54A00FEDC59 /* FetchedResultsControllerDelegateWrapper.swift in Sources */, + 74A18C58211382A000DCF8A8 /* OrderStatsAction.swift in Sources */, 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Yosemite/Yosemite/Actions/OrderStatsAction.swift b/Yosemite/Yosemite/Actions/OrderStatsAction.swift new file mode 100644 index 00000000000..1f988f42cdb --- /dev/null +++ b/Yosemite/Yosemite/Actions/OrderStatsAction.swift @@ -0,0 +1,9 @@ +import Foundation +import Networking + + +// MARK: - OrderStatsAction: Defines all of the Actions supported by the OrderStatsStore. +// +public enum OrderStatsAction: Action { + case retrieveOrderStats(siteID: Int, granularity: OrderStatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: (OrderStats?, Error?) -> Void) +} diff --git a/Yosemite/Yosemite/Stores/OrderStatsStore.swift b/Yosemite/Yosemite/Stores/OrderStatsStore.swift new file mode 100644 index 00000000000..d7fa29c4511 --- /dev/null +++ b/Yosemite/Yosemite/Stores/OrderStatsStore.swift @@ -0,0 +1,65 @@ +import Foundation +import Networking + +// MARK: - OrderStatsStore +// +public class OrderStatsStore: Store { + + /// Registers for supported Actions. + /// + override public func registerSupportedActions(in dispatcher: Dispatcher) { + dispatcher.register(processor: self, for: OrderStatsAction.self) + } + + /// Receives and executes Actions. + /// + override public func onAction(_ action: Action) { + guard let action = action as? OrderStatsAction else { + assertionFailure("OrderStatsStore received an unsupported action") + return + } + + switch action { + case .retrieveOrderStats(let siteID, let granularity, let latestDateToInclude, let quantity, let onCompletion): + retrieveOrderStats(siteID: siteID, granularity: granularity, latestDateToInclude: latestDateToInclude, quantity: quantity, onCompletion: onCompletion) + } + } +} + + +// MARK: - Services! +// +private extension OrderStatsStore { + + /// Retrieves the order stats associated with the provided Site ID (if any!). + /// + func retrieveOrderStats(siteID: Int, granularity: OrderStatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: @escaping (OrderStats?, Error?) -> Void) { + + let remote = OrderStatsRemote(network: network) + let formattedDateString = buildDateString(from: latestDateToInclude, with: granularity) + + remote.loadOrderStats(for: siteID, unit: granularity, latestDateToInclude: formattedDateString, quantity: quantity) { (orderStats, error) in + guard let orderStats = orderStats else { + onCompletion(nil, error) + return + } + + onCompletion(orderStats, nil) + } + } + + /// Converts a Date into the appropriatly formatted string based on the `OrderStatGranularity` + /// + func buildDateString(from date: Date, with granularity: OrderStatGranularity) -> String { + switch granularity { + case .day: + return DateFormatter.Stats.statsDayFormatter.string(from: date) + case .week: + return DateFormatter.Stats.statsWeekFormatter.string(from: date) + case .month: + return DateFormatter.Stats.statsMonthFormatter.string(from: date) + case .year: + return DateFormatter.Stats.statsYearFormatter.string(from: date) + } + } +} From ae0c6296f71b5988cfbfea32d9532ea3962dd149 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 17:10:23 -0300 Subject: [PATCH 051/112] Nukes Duplicated JSON(s) --- Yosemite/YosemiteTests/Resources/me.json | 37 -- .../YosemiteTests/Resources/order-notes.json | 436 --------------- Yosemite/YosemiteTests/Resources/order.json | 230 -------- Yosemite/YosemiteTests/Resources/orders.json | 520 ------------------ Yosemite/YosemiteTests/Resources/sites.json | 219 -------- 5 files changed, 1442 deletions(-) delete mode 100644 Yosemite/YosemiteTests/Resources/me.json delete mode 100644 Yosemite/YosemiteTests/Resources/order-notes.json delete mode 100644 Yosemite/YosemiteTests/Resources/order.json delete mode 100644 Yosemite/YosemiteTests/Resources/orders.json delete mode 100644 Yosemite/YosemiteTests/Resources/sites.json diff --git a/Yosemite/YosemiteTests/Resources/me.json b/Yosemite/YosemiteTests/Resources/me.json deleted file mode 100644 index 2008794399f..00000000000 --- a/Yosemite/YosemiteTests/Resources/me.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "ID": 78972699, - "display_name": "apiexamples", - "username": "apiexamples", - "email": "example@example.blog", - "primary_blog": 82974409, - "primary_blog_url": "http:\/\/apiexamples.wordpress.com", - "primary_blog_is_jetpack": false, - "language": "en", - "locale_variant": "", - "token_site_id": 82974409, - "token_scope": [ - "global" - ], - "avatar_URL": "https:\/\/1.gravatar.com\/avatar\/a2afb7b6c0e23e5d363d8612fb1bd5ad?s=96&d=identicon&r=G", - "profile_URL": "http:\/\/en.gravatar.com\/apiexamples", - "verified": true, - "email_verified": true, - "date": "2015-01-15T06:56:07+00:00", - "site_count": 2, - "visible_site_count": 2, - "has_unseen_notes": false, - "newest_note_type": "", - "phone_account": false, - "meta": { - "links": { - "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/me", - "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/me\/help", - "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/82974409", - "flags": "https:\/\/public-api.wordpress.com\/rest\/v1\/me\/flags" - } - }, - "is_valid_google_apps_country": true, - "user_ip_country_code": "US", - "social_signup_service": "", - "abtests": {} -} diff --git a/Yosemite/YosemiteTests/Resources/order-notes.json b/Yosemite/YosemiteTests/Resources/order-notes.json deleted file mode 100644 index 721c42cef39..00000000000 --- a/Yosemite/YosemiteTests/Resources/order-notes.json +++ /dev/null @@ -1,436 +0,0 @@ -{ - "data": [ - { - "id": 2261, - "date_created": "2018-06-23T13:06:55", - "date_created_gmt": "2018-06-23T17:06:55", - "note": "I love your products!", - "customer_note": true, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2261" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2099, - "date_created": "2018-05-28T23:07:46", - "date_created_gmt": "2018-05-29T03:07:46", - "note": "Order status changed from Completed to Processing.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2099" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2073, - "date_created": "2018-05-26T01:00:24", - "date_created_gmt": "2018-05-26T05:00:24", - "note": "Order status changed from Processing to Completed.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2073" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2072, - "date_created": "2018-05-26T00:59:48", - "date_created_gmt": "2018-05-26T04:59:48", - "note": "Order status changed from Completed to Processing.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2072" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2071, - "date_created": "2018-05-26T00:59:44", - "date_created_gmt": "2018-05-26T04:59:44", - "note": "Order status changed from Processing to Completed.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2071" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2055, - "date_created": "2018-05-26T00:39:42", - "date_created_gmt": "2018-05-26T04:39:42", - "note": "Order status changed from Completed to Processing.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2055" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2054, - "date_created": "2018-05-26T00:39:39", - "date_created_gmt": "2018-05-26T04:39:39", - "note": "Order status changed from Processing to Completed.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2054" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2043, - "date_created": "2018-05-26T00:02:55", - "date_created_gmt": "2018-05-26T04:02:55", - "note": "Order status changed from Completed to Processing.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2043" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2037, - "date_created": "2018-05-25T22:03:19", - "date_created_gmt": "2018-05-26T02:03:19", - "note": "Order status changed from Processing to Completed.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2037" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 2030, - "date_created": "2018-05-25T18:24:42", - "date_created_gmt": "2018-05-25T22:24:42", - "note": "Order status changed from Completed to Processing.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/2030" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 1992, - "date_created": "2018-05-23T13:39:33", - "date_created_gmt": "2018-05-23T17:39:33", - "note": "Order status changed from Processing to Completed.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/1992" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 1984, - "date_created": "2018-05-22T17:29:47", - "date_created_gmt": "2018-05-22T21:29:47", - "note": "Order status changed from Completed to Processing.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/1984" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 1980, - "date_created": "2018-05-22T11:54:39", - "date_created_gmt": "2018-05-22T15:54:39", - "note": "Order status changed from Processing to Completed.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/1980" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 1976, - "date_created": "2018-05-09T10:39:08", - "date_created_gmt": "2018-05-09T14:39:08", - "note": "Is this a real name?", - "customer_note": true, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/1976" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 1947, - "date_created": "2018-04-30T12:00:28", - "date_created_gmt": "2018-04-30T16:00:28", - "note": "Order has been exported to Shipstation", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/1947" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 1934, - "date_created": "2018-04-25T09:24:15", - "date_created_gmt": "2018-04-25T13:24:15", - "note": "Stripe charge complete (Charge ID: ch_1CKnbUJK48mSkzFLKh1h23Ln)", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/1934" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 1933, - "date_created": "2018-04-25T09:24:15", - "date_created_gmt": "2018-04-25T13:24:15", - "note": "Fabric (Variable Product) (#264)Fabric: Italian<\/span> stock reduced from 11 to 10.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/1933" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - }, - { - "id": 1932, - "date_created": "2018-04-25T09:24:15", - "date_created_gmt": "2018-04-25T13:24:15", - "note": "Order status changed from Pending payment to Processing.", - "customer_note": false, - "_links": { - "self": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes\/1932" - } - ], - "collection": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044\/notes" - } - ], - "up": [ - { - "href": "https:\/\/jamosova3.mystagingwebsite.com\/wp-json\/wc\/v2\/orders\/1044" - } - ] - } - } - ] -} diff --git a/Yosemite/YosemiteTests/Resources/order.json b/Yosemite/YosemiteTests/Resources/order.json deleted file mode 100644 index 9819feddba3..00000000000 --- a/Yosemite/YosemiteTests/Resources/order.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "data": { - "id": 963, - "parent_id": 0, - "number": "963", - "order_key": "wc_order_5ac408a83dba3", - "created_via": "checkout", - "version": "3.3.4", - "status": "processing", - "currency": "USD", - "date_created": "2018-04-03T19:05:12", - "date_created_gmt": "2018-04-03T23:05:12", - "date_modified": "2018-04-03T19:05:14", - "date_modified_gmt": "2018-04-03T23:05:14", - "discount_total": "30.00", - "discount_tax": "1.20", - "shipping_total": "0.00", - "shipping_tax": "0.00", - "cart_tax": "1.20", - "total": "31.20", - "total_tax": "1.20", - "prices_include_tax": false, - "customer_id": 11, - "customer_ip_address": "24.52.34.206", - "customer_user_agent": "mozilla/5.0 (macintosh; intel mac os x 10_13_3) applewebkit/537.36 (khtml, like gecko) chrome/65.0.3325.181 safari/537.36", - "customer_note": "", - "billing": { - "first_name": "Johnny", - "last_name": "Appleseed", - "company": "", - "address_1": "234 70th Street", - "address_2": "", - "city": "Niagara Falls", - "state": "NY", - "postcode": "14304", - "country": "US", - "email": "scrambled@scrambled.com", - "phone": "333-333-3333" - }, - "shipping": { - "first_name": "Johnny", - "last_name": "Appleseed", - "company": "", - "address_1": "234 70th Street", - "address_2": "", - "city": "Niagara Falls", - "state": "NY", - "postcode": "14304", - "country": "US", - "email": "scrambled@scrambled.com", - "phone": "333-333-3333" - }, - "payment_method": "stripe", - "payment_method_title": "Credit Card (Stripe)", - "transaction_id": "ch_1CCyBiJK48mSkzFLoXQiCziz", - "date_paid": "2018-04-03T19:05:14", - "date_paid_gmt": "2018-04-03T23:05:14", - "date_completed": null, - "date_completed_gmt": null, - "cart_hash": "c51a4e7d1d0762abbe726b4f6d489391", - "meta_data": [ - { - "id": 24155, - "key": "_stripe_source_id", - "value": "src_1CCyBeJK48mSkzFLxOpLtdNk" - }, - { - "id": 24156, - "key": "_stripe_charge_captured", - "value": "yes" - }, - { - "id": 24157, - "key": "Stripe Fee", - "value": "1.20" - }, - { - "id": 24158, - "key": "Net Revenue From Stripe", - "value": "30.00" - } - ], - "line_items": [ - { - "id": 890, - "name": "Fruits Basket (Mix & Match Product)", - "product_id": 52, - "variation_id": 0, - "quantity": 1, - "tax_class": "", - "subtotal": "50.00", - "subtotal_tax": "2.00", - "total": "30.00", - "total_tax": "1.20", - "taxes": [ - { - "id": 1, - "total": "1.2", - "subtotal": "2" - } - ], - "meta_data": [], - "sku": "", - "price": 30 - }, - { - "id": 891, - "name": "Fruits Bundle", - "product_id": 234, - "variation_id": 0, - "quantity": 1, - "tax_class": "", - "subtotal": "10.00", - "subtotal_tax": "0.40", - "total": "0.00", - "total_tax": "0.00", - "taxes": [ - { - "id": 1, - "total": "0", - "subtotal": "0.4" - } - ], - "meta_data": [], - "sku": "5555-A", - "price": 0 - } - ], - "tax_lines": [ - { - "id": 893, - "rate_code": "US-NY-STATE TAX-1", - "rate_id": 1, - "label": "State Tax", - "compound": false, - "tax_total": "1.20", - "shipping_tax_total": "0.00", - "meta_data": [] - } - ], - "shipping_lines": [ - { - "id": 892, - "method_title": "Free shipping", - "method_id": "free_shipping:26", - "total": "0.00", - "total_tax": "0.00", - "taxes": [], - "meta_data": [ - { - "id": 6507, - "key": "Items", - "value": "Fruits Basket (Mix & Match Product) × 1, Fruits Bundle × 1" - } - ] - } - ], - "fee_lines": [], - "coupon_lines": [ - { - "id": 894, - "code": "30$off", - "discount": "30", - "discount_tax": "1.2", - "meta_data": [ - { - "id": 6515, - "key": "coupon_data", - "value": { - "id": 673, - "code": "30$off", - "amount": "30", - "date_created": { - "date": "2017-10-29 18:20:07.000000", - "timezone_type": 3, - "timezone": "America/New_York" - }, - "date_modified": { - "date": "2017-10-29 18:20:07.000000", - "timezone_type": 3, - "timezone": "America/New_York" - }, - "date_expires": null, - "discount_type": "fixed_cart", - "description": "", - "usage_count": 2, - "individual_use": false, - "product_ids": [], - "excluded_product_ids": [], - "usage_limit": 0, - "usage_limit_per_user": 0, - "limit_usage_to_x_items": null, - "free_shipping": false, - "product_categories": [], - "excluded_product_categories": [], - "exclude_sale_items": false, - "minimum_amount": "", - "maximum_amount": "", - "email_restrictions": [], - "used_by": [ - "1", - "1" - ], - "virtual": false, - "meta_data": [] - } - } - ] - } - ], - "refunds": [], - "_links": { - "self": [ - { - "href": "https://scrambled/wp-json/wc/v2/orders/963" - } - ], - "collection": [ - { - "href": "https://scrambled/wp-json/wc/v2/orders" - } - ], - "customer": [ - { - "href": "https://scrambled/wp-json/wc/v2/customers/11" - } - ] - } - } -} diff --git a/Yosemite/YosemiteTests/Resources/orders.json b/Yosemite/YosemiteTests/Resources/orders.json deleted file mode 100644 index afcec461bc7..00000000000 --- a/Yosemite/YosemiteTests/Resources/orders.json +++ /dev/null @@ -1,520 +0,0 @@ -{ - "data": [ - { - "id": 963, - "parent_id": 0, - "number": "963", - "order_key": "wc_order_5ac408a83dba3", - "created_via": "checkout", - "version": "3.3.4", - "status": "processing", - "currency": "USD", - "date_created": "2018-04-03T19:05:12", - "date_created_gmt": "2018-04-03T23:05:12", - "date_modified": "2018-04-03T19:05:14", - "date_modified_gmt": "2018-04-03T23:05:14", - "discount_total": "30.00", - "discount_tax": "1.20", - "shipping_total": "0.00", - "shipping_tax": "0.00", - "cart_tax": "1.20", - "total": "31.20", - "total_tax": "1.20", - "prices_include_tax": false, - "customer_id": 11, - "customer_ip_address": "24.52.34.206", - "customer_user_agent": "mozilla/5.0 (macintosh; intel mac os x 10_13_3) applewebkit/537.36 (khtml, like gecko) chrome/65.0.3325.181 safari/537.36", - "customer_note": "", - "billing": { - "first_name": "Johnny", - "last_name": "Appleseed", - "company": "", - "address_1": "234 70th Street", - "address_2": "", - "city": "Niagara Falls", - "state": "NY", - "postcode": "14304", - "country": "US", - "email": "scrambled@scrambled.com", - "phone": "333-333-3333" - }, - "shipping": { - "first_name": "Johnny", - "last_name": "Appleseed", - "company": "", - "address_1": "234 70th Street", - "address_2": "", - "city": "Niagara Falls", - "state": "NY", - "postcode": "14304", - "country": "US", - "email": "scrambled@scrambled.com", - "phone": "333-333-3333" - }, - "payment_method": "stripe", - "payment_method_title": "Credit Card (Stripe)", - "transaction_id": "ch_1CCyBiJK48mSkzFLoXQiCziz", - "date_paid": "2018-04-03T19:05:14", - "date_paid_gmt": "2018-04-03T23:05:14", - "date_completed": null, - "date_completed_gmt": null, - "cart_hash": "c51a4e7d1d0762abbe726b4f6d489391", - "meta_data": [ - { - "id": 24155, - "key": "_stripe_source_id", - "value": "src_1CCyBeJK48mSkzFLxOpLtdNk" - }, - { - "id": 24156, - "key": "_stripe_charge_captured", - "value": "yes" - }, - { - "id": 24157, - "key": "Stripe Fee", - "value": "1.20" - }, - { - "id": 24158, - "key": "Net Revenue From Stripe", - "value": "30.00" - } - ], - "line_items": [ - { - "id": 890, - "name": "Fruits Basket (Mix & Match Product)", - "product_id": 52, - "variation_id": 0, - "quantity": 1, - "tax_class": "", - "subtotal": "50.00", - "subtotal_tax": "2.00", - "total": "30.00", - "total_tax": "1.20", - "taxes": [ - { - "id": 1, - "total": "1.2", - "subtotal": "2" - } - ], - "meta_data": [], - "sku": "", - "price": 30 - }, - { - "id": 891, - "name": "Fruits Bundle", - "product_id": 234, - "variation_id": 0, - "quantity": 1, - "tax_class": "", - "subtotal": "10.00", - "subtotal_tax": "0.40", - "total": "0.00", - "total_tax": "0.00", - "taxes": [ - { - "id": 1, - "total": "0", - "subtotal": "0.4" - } - ], - "meta_data": [], - "sku": "5555-A", - "price": 0 - } - ], - "tax_lines": [ - { - "id": 893, - "rate_code": "US-NY-STATE TAX-1", - "rate_id": 1, - "label": "State Tax", - "compound": false, - "tax_total": "1.20", - "shipping_tax_total": "0.00", - "meta_data": [] - } - ], - "shipping_lines": [ - { - "id": 892, - "method_title": "Free shipping", - "method_id": "free_shipping:26", - "total": "0.00", - "total_tax": "0.00", - "taxes": [], - "meta_data": [ - { - "id": 6507, - "key": "Items", - "value": "Fruits Basket (Mix & Match Product) × 1, Fruits Bundle × 1" - } - ] - } - ], - "fee_lines": [], - "coupon_lines": [ - { - "id": 894, - "code": "30$off", - "discount": "30", - "discount_tax": "1.2", - "meta_data": [ - { - "id": 6515, - "key": "coupon_data", - "value": { - "id": 673, - "code": "30$off", - "amount": "30", - "date_created": { - "date": "2017-10-29 18:20:07.000000", - "timezone_type": 3, - "timezone": "America/New_York" - }, - "date_modified": { - "date": "2017-10-29 18:20:07.000000", - "timezone_type": 3, - "timezone": "America/New_York" - }, - "date_expires": null, - "discount_type": "fixed_cart", - "description": "", - "usage_count": 2, - "individual_use": false, - "product_ids": [], - "excluded_product_ids": [], - "usage_limit": 0, - "usage_limit_per_user": 0, - "limit_usage_to_x_items": null, - "free_shipping": false, - "product_categories": [], - "excluded_product_categories": [], - "exclude_sale_items": false, - "minimum_amount": "", - "maximum_amount": "", - "email_restrictions": [], - "used_by": [ - "1", - "1" - ], - "virtual": false, - "meta_data": [] - } - } - ] - } - ], - "refunds": [], - "_links": { - "self": [ - { - "href": "https://scrambled/wp-json/wc/v2/orders/963" - } - ], - "collection": [ - { - "href": "https://scrambled/wp-json/wc/v2/orders" - } - ], - "customer": [ - { - "href": "https://scrambled/wp-json/wc/v2/customers/11" - } - ] - } - }, - { - "id": 961, - "parent_id": 0, - "number": "961", - "order_key": "wc_order_5ac3e9fd7c8b9", - "created_via": "checkout", - "version": "3.3.4", - "status": "processing", - "currency": "USD", - "date_created": "2018-04-03T16:54:21", - "date_created_gmt": "2018-04-03T20:54:21", - "date_modified": "2018-04-03T16:54:23", - "date_modified_gmt": "2018-04-03T20:54:23", - "discount_total": "0.00", - "discount_tax": "0.00", - "shipping_total": "0.00", - "shipping_tax": "0.00", - "cart_tax": "0.00", - "total": "20.00", - "total_tax": "0.00", - "prices_include_tax": false, - "customer_id": 1, - "customer_ip_address": "100.2.138.170", - "customer_user_agent": "mozilla/5.0 (macintosh; intel mac os x 10_13_3) applewebkit/537.36 (khtml, like gecko) chrome/65.0.3325.181 safari/537.36", - "customer_note": "", - "billing": { - "first_name": "Julia", - "last_name": "Woo", - "company": "The Nuttery", - "address_1": "200 Collins Ave", - "address_2": "", - "city": "Miami Beach", - "state": "FL", - "postcode": "33139", - "country": "US", - "email": "scrambled@scrambled", - "phone": "333-333-3333" - }, - "shipping": { - "first_name": "Julia", - "last_name": "Woo", - "company": "The Nuttery", - "address_1": "200 Collins Ave", - "address_2": "", - "city": "Miami Beach", - "state": "FL", - "postcode": "33139", - "country": "US" - }, - "payment_method": "stripe", - "payment_method_title": "Credit Card (Stripe)", - "transaction_id": "ch_scrambled", - "date_paid": "2018-04-03T16:54:23", - "date_paid_gmt": "2018-04-03T20:54:23", - "date_completed": null, - "date_completed_gmt": null, - "cart_hash": "b615a3a375d574ed3ccfedea4b91e854", - "meta_data": [ - { - "id": 24086, - "key": "_stripe_customer_id", - "value": "cus_B9aZmYbtbK1ryN" - }, - { - "id": 24087, - "key": "_stripe_source_id", - "value": "src_1CCw92JK48mSkzFLYrjh01FM" - }, - { - "id": 24088, - "key": "_stripe_charge_captured", - "value": "yes" - }, - { - "id": 24089, - "key": "Stripe Fee", - "value": "0.88" - }, - { - "id": 24090, - "key": "Net Revenue From Stripe", - "value": "19.12" - } - ], - "line_items": [ - { - "id": 888, - "name": "Poster (Product Add-On)", - "product_id": 956, - "variation_id": 0, - "quantity": 1, - "tax_class": "", - "subtotal": "20.00", - "subtotal_tax": "0.00", - "total": "20.00", - "total_tax": "0.00", - "taxes": [], - "meta_data": [], - "sku": "", - "price": 20 - } - ], - "tax_lines": [], - "shipping_lines": [ - { - "id": 889, - "method_title": "Free shipping", - "method_id": "free_shipping:26", - "total": "0.00", - "total_tax": "0.00", - "taxes": [], - "meta_data": [ - { - "id": 6484, - "key": "Items", - "value": "Poster (Product Add-On) × 1" - } - ] - } - ], - "fee_lines": [], - "coupon_lines": [], - "refunds": [], - "_links": { - "self": [ - { - "href": "https://scrambled/wp-json/wc/v2/orders/961" - } - ], - "collection": [ - { - "href": "https://scrambled/wp-json/wc/v2/orders" - } - ], - "customer": [ - { - "href": "https://scrambled/wp-json/wc/v2/customers/1" - } - ] - } - }, - { - "id": 960, - "parent_id": 0, - "number": "960", - "order_key": "wc_order_5ac3e9968343a", - "created_via": "checkout", - "version": "3.3.4", - "status": "processing", - "currency": "USD", - "date_created": "2018-04-03T16:52:38", - "date_created_gmt": "2018-04-03T20:52:38", - "date_modified": "2018-04-03T16:52:41", - "date_modified_gmt": "2018-04-03T20:52:41", - "discount_total": "0.00", - "discount_tax": "0.00", - "shipping_total": "0.00", - "shipping_tax": "0.00", - "cart_tax": "0.00", - "total": "20.00", - "total_tax": "0.00", - "prices_include_tax": false, - "customer_id": 1, - "customer_ip_address": "100.2.138.170", - "customer_user_agent": "mozilla/5.0 (macintosh; intel mac os x 10_13_3) applewebkit/537.36 (khtml, like gecko) chrome/65.0.3325.181 safari/537.36", - "customer_note": "", - "billing": { - "first_name": "Julia", - "last_name": "Woo", - "company": "The Nuttery", - "address_1": "200 Collins Ave", - "address_2": "", - "city": "Miami Beach", - "state": "FL", - "postcode": "33139", - "country": "US", - "email": "scrambled@scrambled.com", - "phone": "333-333-3333" - }, - "shipping": { - "first_name": "Julia", - "last_name": "Woo", - "company": "The Nuttery", - "address_1": "200 Collins Ave", - "address_2": "", - "city": "Miami Beach", - "state": "FL", - "postcode": "33139", - "country": "US" - }, - "payment_method": "stripe", - "payment_method_title": "Credit Card (Stripe)", - "transaction_id": "ch_1CCw7QJK48mSkzFLIbQnSl7Z", - "date_paid": "2018-04-03T16:52:41", - "date_paid_gmt": "2018-04-03T20:52:41", - "date_completed": null, - "date_completed_gmt": null, - "cart_hash": "f2143aeb75a9d378b29ddfa165ee67fd", - "meta_data": [ - { - "id": 24030, - "key": "_stripe_customer_id", - "value": "cus_B9aZmYbtbK1ryN" - }, - { - "id": 24031, - "key": "_stripe_source_id", - "value": "src_1CCw7NJK48mSkzFLyas6aoCp" - }, - { - "id": 24032, - "key": "_stripe_charge_captured", - "value": "yes" - }, - { - "id": 24033, - "key": "Stripe Fee", - "value": "0.88" - }, - { - "id": 24034, - "key": "Net Revenue From Stripe", - "value": "19.12" - } - ], - "line_items": [ - { - "id": 886, - "name": "Poster (Product Add-On)", - "product_id": 956, - "variation_id": 0, - "quantity": 1, - "tax_class": "", - "subtotal": "20.00", - "subtotal_tax": "0.00", - "total": "20.00", - "total_tax": "0.00", - "taxes": [], - "meta_data": [ - { - "id": 6465, - "key": "Poster Image", - "value": "https://scrambled.com/wp-content/uploads/product_addons_uploads/c4ca4238a0b923820dcc509a6f75849b/ST-Upload-1.jpg" - } - ], - "sku": "", - "price": 20 - } - ], - "tax_lines": [], - "shipping_lines": [ - { - "id": 887, - "method_title": "Free shipping", - "method_id": "free_shipping:26", - "total": "0.00", - "total_tax": "0.00", - "taxes": [], - "meta_data": [ - { - "id": 6470, - "key": "Items", - "value": "Poster (Product Add-On) × 1" - } - ] - } - ], - "fee_lines": [], - "coupon_lines": [], - "refunds": [], - "_links": { - "self": [ - { - "href": "https://scrambled.com/wp-json/wc/v2/orders/960" - } - ], - "collection": [ - { - "href": "https://scrambled.com/wp-json/wc/v2/orders" - } - ], - "customer": [ - { - "href": "https://scrambled.com/wp-json/wc/v2/customers/1" - } - ] - } - } - ] -} diff --git a/Yosemite/YosemiteTests/Resources/sites.json b/Yosemite/YosemiteTests/Resources/sites.json deleted file mode 100644 index 1ac6b3ee09d..00000000000 --- a/Yosemite/YosemiteTests/Resources/sites.json +++ /dev/null @@ -1,219 +0,0 @@ -{ - "sites": [ - { - "ID": 1112233334444555, - "name": "Testing Blog", - "description": "Testing Tagline", - "URL": "https:\/\/some-testing-url.testing.blog", - "options": { - "timezone": "", - "gmt_offset": 0, - "blog_public": 1, - "videopress_enabled": false, - "upgraded_filetypes_enabled": true, - "login_url": "https:\/\/some-testing-url.here\/wp-login.php", - "admin_url": "https:\/\/some-testing-url.here\/wp-admin\/", - "is_mapped_domain": true, - "is_redirect": false, - "unmapped_url": "https:\/\/some-testing-url.here", - "featured_images_enabled": false, - "theme_slug": "storefront", - "header_image": false, - "background_color": false, - "image_default_link_type": "file", - "image_thumbnail_width": 150, - "image_thumbnail_height": 150, - "image_thumbnail_crop": 0, - "image_medium_width": 300, - "image_medium_height": 300, - "image_large_width": 1024, - "image_large_height": 1024, - "permalink_structure": "\/%year%\/%monthnum%\/%day%\/%postname%\/", - "post_formats": [], - "default_post_format": "0", - "default_category": 12, - "allowed_file_types": [ - "jpg", - "jpeg", - "png", - "gif", - "pdf", - "doc", - "ppt", - "odt", - "pptx", - "docx", - "pps", - "ppsx", - "xls", - "xlsx", - "key", - "ogv", - "mp4", - "m4v", - "mov", - "wmv", - "avi", - "mpg", - "3gp", - "3g2" - ], - "show_on_front": "page", - "default_likes_enabled": true, - "default_sharing_status": true, - "default_comment_status": true, - "default_ping_status": true, - "software_version": "4.9.6", - "created_at": "2014-03-28T15:03:03+00:00", - "wordads": false, - "publicize_permanently_disabled": false, - "frame_nonce": "73ae7163d8", - "page_on_front": 1455, - "page_for_posts": 0, - "headstart": false, - "headstart_is_fresh": false, - "ak_vp_bundle_enabled": false, - "advanced_seo_front_page_description": "", - "advanced_seo_title_formats": [], - "verification_services_codes": null, - "podcasting_archive": null, - "is_domain_only": false, - "is_automated_transfer": true, - "is_wpcom_store": true, - "woocommerce_is_active": true, - "design_type": null, - "site_goals": null, - "jetpack_version": "6.2.1", - "main_network_site": "https:\/\/some-testing-url.here", - "active_modules": [ - "after-the-deadline", - "contact-form", - "custom-content-types", - "custom-css", - "enhanced-distribution", - "gravatar-hovercards", - "json-api", - "latex", - "manage", - "notes", - "post-by-email", - "protect", - "publicize", - "sharedaddy", - "shortcodes", - "shortlinks", - "sitemaps", - "stats", - "subscriptions", - "verification-tools", - "widget-visibility", - "widgets", - "sso", - "tiled-gallery", - "likes", - "comments", - "comment-likes", - "videopress", - "carousel", - "photon", - "seo-tools", - "google-analytics", - "infinite-scroll", - "masterbar", - "vaultpress" - ], - "max_upload_size": false, - "wp_memory_limit": "268435456", - "wp_max_memory_limit": "268435456", - "is_multi_network": false, - "is_multi_site": false, - "file_mod_disabled": false - }, - "updates": { - "plugins": 3, - "themes": 1, - "wordpress": 0, - "translations": 0, - "total": 4 - } - }, - { - "ID": 11122333344446666, - "name": "Thoughts", - "description": "Your Favorite Blog", - "URL": "https:\/\/thoughts.testing.blog", - "options": { - "timezone": "", - "gmt_offset": 0, - "blog_public": 1, - "videopress_enabled": false, - "upgraded_filetypes_enabled": false, - "login_url": "https:\/\/thoughts.testing.blog\/wp-login.php", - "admin_url": "https:\/\/thoughts.testing.blog\/wp-admin\/", - "is_mapped_domain": false, - "is_redirect": false, - "unmapped_url": "https:\/\/thoughts.testing.blog", - "featured_images_enabled": false, - "theme_slug": "pub\/p2-breathe", - "header_image": { - "thumbnail_url": "https:\/\/thoughts.testing.blog\/2016\/07\/blur.jpg?resize=520,108.33333333333", - "url": "https:\/\/thoughts.testing.blog\/2016\/07\/blur.jpg?resize=1200,250", - "description": "Blurred Lights" - }, - "background_color": "2b2b2b", - "image_default_link_type": "file", - "image_thumbnail_width": 150, - "image_thumbnail_height": 150, - "image_thumbnail_crop": 0, - "image_medium_width": 300, - "image_medium_height": 300, - "image_large_width": 1024, - "image_large_height": 1024, - "permalink_structure": "\/%year%\/%monthnum%\/%day%\/%postname%\/", - "post_formats": [], - "default_post_format": "standard", - "default_category": 1, - "allowed_file_types": [ - "jpg", - "jpeg", - "png", - "gif", - "pdf", - "doc", - "ppt", - "odt", - "pptx", - "docx", - "pps", - "ppsx", - "xls", - "xlsx", - "key" - ], - "show_on_front": "posts", - "default_likes_enabled": true, - "default_sharing_status": false, - "default_comment_status": true, - "default_ping_status": true, - "software_version": "4.9.7-alpha-43298", - "created_at": "2013-08-05T16:39:48+00:00", - "wordads": false, - "publicize_permanently_disabled": false, - "frame_nonce": "e7bfd785f0", - "headstart": false, - "headstart_is_fresh": false, - "ak_vp_bundle_enabled": null, - "advanced_seo_front_page_description": "", - "advanced_seo_title_formats": [], - "verification_services_codes": null, - "podcasting_archive": null, - "is_domain_only": false, - "is_automated_transfer": false, - "is_wpcom_store": false, - "woocommerce_is_active": false, - "design_type": null, - "site_goals": null - } - } - ] -} From 758800d98fa5df7cc0913ebac624cc693a468fc5 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 17:10:38 -0300 Subject: [PATCH 052/112] Updating Sample JSON(s) --- .../NetworkingTests/Responses/order.json | 257 ++++++++++++------ .../Responses/orders-load-all.json | 8 +- 2 files changed, 175 insertions(+), 90 deletions(-) diff --git a/Networking/NetworkingTests/Responses/order.json b/Networking/NetworkingTests/Responses/order.json index a63ba4c55ec..ab80616684d 100644 --- a/Networking/NetworkingTests/Responses/order.json +++ b/Networking/NetworkingTests/Responses/order.json @@ -1,147 +1,230 @@ { - "data": { - "id": 1467, + "data": { + "id": 963, "parent_id": 0, - "number": "1467", - "order_key": "wc_order_5a68b29ca587a", + "number": "963", + "order_key": "wc_order_5ac408a83dba3", "created_via": "checkout", - "version": "3.2.6", + "version": "3.3.4", "status": "processing", "currency": "USD", - "date_created": "2018-01-24T16:21:48", - "date_created_gmt": "2018-01-24T16:21:48", - "date_modified": "2018-05-09T18:15:30", - "date_modified_gmt": "2018-05-09T18:15:30", - "discount_total": "0.00", - "discount_tax": "0.00", + "date_created": "2018-04-03T19:05:12", + "date_created_gmt": "2018-04-03T23:05:12", + "date_modified": "2018-04-03T19:05:14", + "date_modified_gmt": "2018-04-03T23:05:14", + "discount_total": "30.00", + "discount_tax": "1.20", "shipping_total": "0.00", "shipping_tax": "0.00", - "cart_tax": "0.00", - "total": "102.00", - "total_tax": "2.00", + "cart_tax": "1.20", + "total": "31.20", + "total_tax": "1.20", "prices_include_tax": false, - "customer_id": 100, - "customer_ip_address": "174.227.1.93", - "customer_user_agent": "mozilla\/5.0 (macintosh; intel mac os x 10_12_6) applewebkit\/537.36 (khtml, like gecko) chrome\/63.0.3239.132 safari\/537.36", + "customer_id": 11, + "customer_ip_address": "24.52.34.206", + "customer_user_agent": "mozilla/5.0 (macintosh; intel mac os x 10_13_3) applewebkit/537.36 (khtml, like gecko) chrome/65.0.3325.181 safari/537.36", "customer_note": "", "billing": { - "first_name": "Maria", - "last_name": "Scrambled", - "company": "Logged Out", - "address_1": "9999 Scrambled", + "first_name": "Johnny", + "last_name": "Appleseed", + "company": "", + "address_1": "234 70th Street", "address_2": "", - "city": "Omaha", - "state": "NE", - "postcode": "68124", + "city": "Niagara Falls", + "state": "NY", + "postcode": "14304", "country": "US", - "email": "scrambled", - "phone": "99999999" + "email": "scrambled@scrambled.com", + "phone": "333-333-3333" }, "shipping": { - "first_name": "Maria", - "last_name": "Scrambled", - "company": "Logged Out", - "address_1": "9999 Scrambled", + "first_name": "Johnny", + "last_name": "Appleseed", + "company": "", + "address_1": "234 70th Street", "address_2": "", - "city": "Omaha", - "state": "NE", - "postcode": "68124", - "country": "US" + "city": "Niagara Falls", + "state": "NY", + "postcode": "14304", + "country": "US", + "email": "scrambled@scrambled.com", + "phone": "333-333-3333" }, - "payment_method": "cod", - "payment_method_title": "Cash on delivery", - "transaction_id": "", - "date_paid": "2018-05-03T19:24:55", - "date_paid_gmt": "2018-05-03T19:24:55", - "date_completed": "2018-05-04T01:22:06", - "date_completed_gmt": "2018-05-04T01:22:06", - "cart_hash": "35ca1b0b9d3ec0f3d511179dda8c7c19", + "payment_method": "stripe", + "payment_method_title": "Credit Card (Stripe)", + "transaction_id": "ch_1CCyBiJK48mSkzFLoXQiCziz", + "date_paid": "2018-04-03T19:05:14", + "date_paid_gmt": "2018-04-03T23:05:14", + "date_completed": null, + "date_completed_gmt": null, + "cart_hash": "c51a4e7d1d0762abbe726b4f6d489391", "meta_data": [ { - "id": 8439, - "key": "_shipping_phone", - "value": "4025986670" + "id": 24155, + "key": "_stripe_source_id", + "value": "src_1CCyBeJK48mSkzFLxOpLtdNk" }, { - "id": 8440, - "key": "mailchimp_woocommerce_is_subscribed", - "value": "1" + "id": 24156, + "key": "_stripe_charge_captured", + "value": "yes" }, { - "id": 8441, - "key": "_download_permissions_granted", - "value": "yes" + "id": 24157, + "key": "Stripe Fee", + "value": "1.20" }, { - "id": 8444, - "key": "_order_stock_reduced", - "value": "yes" + "id": 24158, + "key": "Net Revenue From Stripe", + "value": "30.00" } ], "line_items": [ { - "id": 3, - "name": "ARC Reactor", - "product_id": 1450, + "id": 890, + "name": "Fruits Basket (Mix & Match Product)", + "product_id": 52, "variation_id": 0, "quantity": 1, "tax_class": "", - "subtotal": "100.00", + "subtotal": "50.00", "subtotal_tax": "2.00", - "total": "100.00", - "total_tax": "2.00", + "total": "30.00", + "total_tax": "1.20", "taxes": [ - - ], - "meta_data": [ - - ], - "sku": "100", - "price": 100 + { + "id": 1, + "total": "1.2", + "subtotal": "2" + } + ], + "meta_data": [], + "sku": "", + "price": 30 + }, + { + "id": 891, + "name": "Fruits Bundle", + "product_id": 234, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "10.00", + "subtotal_tax": "0.40", + "total": "0.00", + "total_tax": "0.00", + "taxes": [ + { + "id": 1, + "total": "0", + "subtotal": "0.4" + } + ], + "meta_data": [], + "sku": "5555-A", + "price": 0 } ], "tax_lines": [ - - ], + { + "id": 893, + "rate_code": "US-NY-STATE TAX-1", + "rate_id": 1, + "label": "State Tax", + "compound": false, + "tax_total": "1.20", + "shipping_tax_total": "0.00", + "meta_data": [] + } + ], "shipping_lines": [ { - "id": 4, + "id": 892, "method_title": "Free shipping", - "method_id": "free_shipping:1", + "method_id": "free_shipping:26", "total": "0.00", "total_tax": "0.00", - "taxes": [ - - ], + "taxes": [], "meta_data": [ { - "id": 28, + "id": 6507, "key": "Items", - "value": "ARC Reactor × 1" + "value": "Fruits Basket (Mix & Match Product) × 1, Fruits Bundle × 1" } ] } ], - "fee_lines": [ - - ], + "fee_lines": [], "coupon_lines": [ - - ], - "refunds": [ - - ], + { + "id": 894, + "code": "30$off", + "discount": "30", + "discount_tax": "1.2", + "meta_data": [ + { + "id": 6515, + "key": "coupon_data", + "value": { + "id": 673, + "code": "30$off", + "amount": "30", + "date_created": { + "date": "2017-10-29 18:20:07.000000", + "timezone_type": 3, + "timezone": "America/New_York" + }, + "date_modified": { + "date": "2017-10-29 18:20:07.000000", + "timezone_type": 3, + "timezone": "America/New_York" + }, + "date_expires": null, + "discount_type": "fixed_cart", + "description": "", + "usage_count": 2, + "individual_use": false, + "product_ids": [], + "excluded_product_ids": [], + "usage_limit": 0, + "usage_limit_per_user": 0, + "limit_usage_to_x_items": null, + "free_shipping": false, + "product_categories": [], + "excluded_product_categories": [], + "exclude_sale_items": false, + "minimum_amount": "", + "maximum_amount": "", + "email_restrictions": [], + "used_by": [ + "1", + "1" + ], + "virtual": false, + "meta_data": [] + } + } + ] + } + ], + "refunds": [], "_links": { "self": [ { - "href": "https:\/\/scrambled\/wp-json\/wc\/v2\/orders\/1467" + "href": "https://scrambled/wp-json/wc/v2/orders/963" } ], "collection": [ { - "href": "https:\/\/scrambled\/wp-json\/wc\/v2\/orders" + "href": "https://scrambled/wp-json/wc/v2/orders" } - ] + ], + "customer": [ + { + "href": "https://scrambled/wp-json/wc/v2/customers/11" + } + ] } } } diff --git a/Networking/NetworkingTests/Responses/orders-load-all.json b/Networking/NetworkingTests/Responses/orders-load-all.json index 36af07da1fc..afcec461bc7 100644 --- a/Networking/NetworkingTests/Responses/orders-load-all.json +++ b/Networking/NetworkingTests/Responses/orders-load-all.json @@ -35,8 +35,8 @@ "state": "NY", "postcode": "14304", "country": "US", - "email": "amanda.riu+wootester@automattic.com", - "phone": "7165551234" + "email": "scrambled@scrambled.com", + "phone": "333-333-3333" }, "shipping": { "first_name": "Johnny", @@ -47,7 +47,9 @@ "city": "Niagara Falls", "state": "NY", "postcode": "14304", - "country": "US" + "country": "US", + "email": "scrambled@scrambled.com", + "phone": "333-333-3333" }, "payment_method": "stripe", "payment_method_title": "Credit Card (Stripe)", From b264fe9ac5d549f1be30e52c194bc4334ec7d81d Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 17:10:48 -0300 Subject: [PATCH 053/112] Networking: Fixing Unit Tests --- .../Mapper/OrderMapperTests.swift | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Networking/NetworkingTests/Mapper/OrderMapperTests.swift b/Networking/NetworkingTests/Mapper/OrderMapperTests.swift index 3836160f2b6..789d37202ad 100644 --- a/Networking/NetworkingTests/Mapper/OrderMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/OrderMapperTests.swift @@ -19,27 +19,27 @@ class OrderMapperTests: XCTestCase { return } - let dateCreated = DateFormatter.Defaults.dateTimeFormatter.date(from: "2018-01-24T16:21:48") - let dateModified = DateFormatter.Defaults.dateTimeFormatter.date(from: "2018-05-09T18:15:30") - let datePaid = DateFormatter.Defaults.dateTimeFormatter.date(from: "2018-05-03T19:24:55") + let dateCreated = DateFormatter.Defaults.dateTimeFormatter.date(from: "2018-04-03T23:05:12") + let dateModified = DateFormatter.Defaults.dateTimeFormatter.date(from: "2018-04-03T23:05:14") + let datePaid = DateFormatter.Defaults.dateTimeFormatter.date(from: "2018-04-03T23:05:14") XCTAssertEqual(order.siteID, dummySiteID) - XCTAssertEqual(order.orderID, 1467) + XCTAssertEqual(order.orderID, 963) XCTAssertEqual(order.parentID, 0) - XCTAssertEqual(order.customerID, 100) - XCTAssertEqual(order.number, "1467") + XCTAssertEqual(order.customerID, 11) + XCTAssertEqual(order.number, "963") XCTAssert(order.status == .processing) XCTAssertEqual(order.currency, "USD") XCTAssertEqual(order.customerNote, "") XCTAssertEqual(order.dateCreated, dateCreated) XCTAssertEqual(order.dateModified, dateModified) XCTAssertEqual(order.datePaid, datePaid) - XCTAssertEqual(order.discountTotal, "0.00") - XCTAssertEqual(order.discountTax, "0.00") + XCTAssertEqual(order.discountTotal, "30.00") + XCTAssertEqual(order.discountTax, "1.20") XCTAssertEqual(order.shippingTotal, "0.00") XCTAssertEqual(order.shippingTax, "0.00") - XCTAssertEqual(order.total, "102.00") - XCTAssertEqual(order.totalTax, "2.00") + XCTAssertEqual(order.total, "31.20") + XCTAssertEqual(order.totalTax, "1.20") } /// Verifies that all of the Order Address fields are parsed correctly. @@ -53,14 +53,14 @@ class OrderMapperTests: XCTestCase { let dummyAddresses = [order.billingAddress, order.shippingAddress] for address in dummyAddresses { - XCTAssertEqual(address.firstName, "Maria") - XCTAssertEqual(address.lastName, "Scrambled") - XCTAssertEqual(address.company, "Logged Out") - XCTAssertEqual(address.address1, "9999 Scrambled") + XCTAssertEqual(address.firstName, "Johnny") + XCTAssertEqual(address.lastName, "Appleseed") + XCTAssertEqual(address.company, "") + XCTAssertEqual(address.address1, "234 70th Street") XCTAssertEqual(address.address2, "") - XCTAssertEqual(address.city, "Omaha") - XCTAssertEqual(address.state, "NE") - XCTAssertEqual(address.postcode, "68124") + XCTAssertEqual(address.city, "Niagara Falls") + XCTAssertEqual(address.state, "NY") + XCTAssertEqual(address.postcode, "14304") XCTAssertEqual(address.country, "US") } } @@ -74,16 +74,16 @@ class OrderMapperTests: XCTestCase { } let firstItem = order.items[0] - XCTAssertEqual(firstItem.itemID, 3) - XCTAssertEqual(firstItem.name, "ARC Reactor") - XCTAssertEqual(firstItem.productID, 1450) + XCTAssertEqual(firstItem.itemID, 890) + XCTAssertEqual(firstItem.name, "Fruits Basket (Mix & Match Product)") + XCTAssertEqual(firstItem.productID, 52) XCTAssertEqual(firstItem.quantity, 1) - XCTAssertEqual(firstItem.sku, "100") - XCTAssertEqual(firstItem.subtotal, "100.00") + XCTAssertEqual(firstItem.sku, "") + XCTAssertEqual(firstItem.subtotal, "50.00") XCTAssertEqual(firstItem.subtotalTax, "2.00") XCTAssertEqual(firstItem.taxClass, "") - XCTAssertEqual(firstItem.total, "100.00") - XCTAssertEqual(firstItem.totalTax, "2.00") + XCTAssertEqual(firstItem.total, "30.00") + XCTAssertEqual(firstItem.totalTax, "1.20") XCTAssertEqual(firstItem.variationID, 0) } From 6fa5c2987f61b76e88db2b698d4ed9da0457b784 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 17:10:55 -0300 Subject: [PATCH 054/112] Yosemite: Fixing Unit Tests --- Yosemite/YosemiteTests/Stores/OrderStoreTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift b/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift index 57f3e9a0753..dfba697341f 100644 --- a/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift @@ -53,7 +53,7 @@ class OrderStoreTests: XCTestCase { let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) let remoteOrder = sampleOrder() - network.simulateResponse(requestUrlSuffix: "orders", filename: "orders") + network.simulateResponse(requestUrlSuffix: "orders", filename: "orders-load-all") let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { (orders, error) in XCTAssertNil(error) guard let orders = orders else { @@ -75,7 +75,7 @@ class OrderStoreTests: XCTestCase { let expectation = self.expectation(description: "Persist order list") let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) - network.simulateResponse(requestUrlSuffix: "orders", filename: "orders") + network.simulateResponse(requestUrlSuffix: "orders", filename: "orders-load-all") XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Order.self), 0) let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { (orders, error) in @@ -95,7 +95,7 @@ class OrderStoreTests: XCTestCase { let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) let remoteOrder = sampleOrder() - network.simulateResponse(requestUrlSuffix: "orders", filename: "orders") + network.simulateResponse(requestUrlSuffix: "orders", filename: "orders-load-all") XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Order.self), 0) let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { (orders, error) in From b94a87921a8ef0d61e3f31e8e60d82c5ebf2e591 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 18:22:26 -0300 Subject: [PATCH 055/112] Loader: Recursive Lookup --- .../Networking/Network/Tools/Loader.swift | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/Networking/Networking/Network/Tools/Loader.swift b/Networking/Networking/Network/Tools/Loader.swift index b9044fe8d30..d693ff8d154 100644 --- a/Networking/Networking/Network/Tools/Loader.swift +++ b/Networking/Networking/Network/Tools/Loader.swift @@ -30,22 +30,41 @@ class Loader { /// Loads the contents of the specified file (in the current bundle), and returns it's contents as `Data`. /// static func contentsOf(_ filename: String, extension: String = jsonExtension) -> Data? { - guard let url = path(for: filename, extension: `extension`) else { + guard let url = url(for: filename, extension: `extension`) else { return nil } return try? Data(contentsOf: url) } - /// Fins the specified resource in *all* of the available bundles. + /// Finds the specified resource in *all* of the available bundles, recursively. /// - private static func path(for filename: String, extension: String = jsonExtension) -> URL? { + private static func url(for filename: String, extension: String = jsonExtension) -> URL? { + let targetLastComponent = filename + "." + `extension` + for bundle in Bundle.allBundles { - guard let path = bundle.url(forResource: filename, withExtension: `extension`) else { + guard let targetURL = url(with: targetLastComponent, in: bundle) else { + continue + } + + return targetURL + } + + return nil + } + + /// Finds the specified resource within a given Bundle, *recursively*. + /// + private static func url(with lastPathComponent: String, in bundle: Bundle) -> URL? { + let resourcePaths = FileManager.default.subpaths(atPath: bundle.bundlePath) ?? [] + + for resourcePath in resourcePaths { + let url = bundle.bundleURL.appendingPathComponent(resourcePath) + guard url.lastPathComponent == lastPathComponent else { continue } - return path + return url } return nil From 3695da3275a44cc32c4ea1befc769ce913f562ca Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 18:22:37 -0300 Subject: [PATCH 056/112] Project Cleanup --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 34 +++------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 975d620e12a..08c1270c169 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -9,17 +9,14 @@ /* Begin PBXBuildFile section */ 0E67B79585034C4DD75C8117 /* Pods_Yosemite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C25501C7F936D2FD32FAF3F4 /* Pods_Yosemite.framework */; }; 36941EA7B9242CAB1FF828BC /* Pods_YosemiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 991BBCE6E4A92F0A028885D8 /* Pods_YosemiteTests.framework */; }; - 7424B49420EAD37C00CC62F6 /* order.json in Resources */ = {isa = PBXBuildFile; fileRef = 7424B49320EAD37C00CC62F6 /* order.json */; }; 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */; }; 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */; }; 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */; }; 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */; }; 7499936820EFC0ED00CF01CD /* OrderNoteStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */; }; - 7499936C20EFC20100CF01CD /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 7499936B20EFC20100CF01CD /* order-notes.json */; }; 74A7688C20D45EBA00F9D437 /* OrderStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688B20D45EBA00F9D437 /* OrderStore.swift */; }; 74A7688E20D45ED400F9D437 /* OrderStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */; }; 74A7689020D45F9300F9D437 /* OrderAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688F20D45F9300F9D437 /* OrderAction.swift */; }; - 74A7689220D47F9E00F9D437 /* orders.json in Resources */ = {isa = PBXBuildFile; fileRef = 74A7689120D47F9E00F9D437 /* orders.json */; }; 74B7D6B020F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B7D6AF20F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift */; }; 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */; }; B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */; }; @@ -32,10 +29,9 @@ B5B19DE020E6A45900899568 /* Storage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5B19DDF20E6A45900899568 /* Storage.framework */; }; B5B19DE220E6A45E00899568 /* Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5B19DE120E6A45E00899568 /* Networking.framework */; }; B5B5C797208E49B600642956 /* Action+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B5C796208E49B600642956 /* Action+Internal.swift */; }; - B5B914C220EFE03200F2F832 /* sites.json in Resources */ = {isa = PBXBuildFile; fileRef = B5B914C120EFE03200F2F832 /* sites.json */; }; + B5BC71DD21139EDD005CF5AA /* Responses in Resources */ = {isa = PBXBuildFile; fileRef = B5BC71DA21139CF2005CF5AA /* Responses */; }; B5BC736520D1A98500B5B6FA /* AccountStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC736420D1A98500B5B6FA /* AccountStore.swift */; }; B5BC736820D1AA8F00B5B6FA /* AccountStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC736720D1AA8F00B5B6FA /* AccountStoreTests.swift */; }; - B5BC736B20D1AAE900B5B6FA /* me.json in Resources */ = {isa = PBXBuildFile; fileRef = B5BC736A20D1AAE900B5B6FA /* me.json */; }; B5BC736E20D1AB3600B5B6FA /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC736D20D1AB3500B5B6FA /* Constants.swift */; }; B5C9DDFF2087FEC0006B910A /* Yosemite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5C9DDF52087FEC0006B910A /* Yosemite.framework */; }; B5C9DE152087FF0E006B910A /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C9DE102087FF0E006B910A /* Dispatcher.swift */; }; @@ -63,18 +59,14 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 7424B49320EAD37C00CC62F6 /* order.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = order.json; sourceTree = ""; }; - 745D21C120D8043A00BBE7C3 /* generic_error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = generic_error.json; sourceTree = ""; }; 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+ReadOnlyConvertible.swift"; sourceTree = ""; }; 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteAction.swift; sourceTree = ""; }; 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteStore.swift; sourceTree = ""; }; 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteStoreTests.swift; sourceTree = ""; }; - 7499936B20EFC20100CF01CD /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = ""; }; 74A7688B20D45EBA00F9D437 /* OrderStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStore.swift; sourceTree = ""; }; 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStoreTests.swift; sourceTree = ""; }; 74A7688F20D45F9300F9D437 /* OrderAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAction.swift; sourceTree = ""; }; - 74A7689120D47F9E00F9D437 /* orders.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = orders.json; sourceTree = ""; }; 74B7D6AF20F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderNote+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Order+ReadOnlyConvertible.swift"; sourceTree = ""; }; 79402E7AD394EEB60C39A4B8 /* Pods-YosemiteTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YosemiteTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-YosemiteTests/Pods-YosemiteTests.debug.xcconfig"; sourceTree = ""; }; @@ -92,10 +84,9 @@ B5B19DDF20E6A45900899568 /* Storage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Storage.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B5B19DE120E6A45E00899568 /* Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B5B5C796208E49B600642956 /* Action+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Action+Internal.swift"; sourceTree = ""; }; - B5B914C120EFE03200F2F832 /* sites.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = sites.json; sourceTree = ""; }; + B5BC71DA21139CF2005CF5AA /* Responses */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Responses; path = ../../Networking/NetworkingTests/Responses; sourceTree = ""; }; B5BC736420D1A98500B5B6FA /* AccountStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStore.swift; sourceTree = ""; }; B5BC736720D1AA8F00B5B6FA /* AccountStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStoreTests.swift; sourceTree = ""; }; - B5BC736A20D1AAE900B5B6FA /* me.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = me.json; sourceTree = ""; }; B5BC736D20D1AB3500B5B6FA /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; B5C9DDF52087FEC0006B910A /* Yosemite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Yosemite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B5C9DDF92087FEC0006B910A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -183,19 +174,6 @@ path = Stores; sourceTree = ""; }; - B5BC736920D1AAE900B5B6FA /* Resources */ = { - isa = PBXGroup; - children = ( - 745D21C120D8043A00BBE7C3 /* generic_error.json */, - B5BC736A20D1AAE900B5B6FA /* me.json */, - 7424B49320EAD37C00CC62F6 /* order.json */, - 7499936B20EFC20100CF01CD /* order-notes.json */, - 74A7689120D47F9E00F9D437 /* orders.json */, - B5B914C120EFE03200F2F832 /* sites.json */, - ); - path = Resources; - sourceTree = ""; - }; B5BC736C20D1AB3500B5B6FA /* Settings */ = { isa = PBXGroup; children = ( @@ -253,7 +231,7 @@ B5C9DE022087FEC0006B910A /* YosemiteTests */ = { isa = PBXGroup; children = ( - B5BC736920D1AAE900B5B6FA /* Resources */, + B5BC71DA21139CF2005CF5AA /* Responses */, B5BC736C20D1AB3500B5B6FA /* Settings */, B5C9DE1D2087FF20006B910A /* Mockups */, B5C9DE192087FF20006B910A /* Base */, @@ -433,11 +411,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 74A7689220D47F9E00F9D437 /* orders.json in Resources */, - B5BC736B20D1AAE900B5B6FA /* me.json in Resources */, - B5B914C220EFE03200F2F832 /* sites.json in Resources */, - 7499936C20EFC20100CF01CD /* order-notes.json in Resources */, - 7424B49420EAD37C00CC62F6 /* order.json in Resources */, + B5BC71DD21139EDD005CF5AA /* Responses in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 11ad91bd2f4c41565bf3729f7262a17d3b6a5974 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 20:37:06 -0300 Subject: [PATCH 057/112] Podfile: Dropping ARMv7 Workaround --- Podfile | 10 ---------- Podfile.lock | 6 +++--- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Podfile b/Podfile index 6980894db45..512c2601b49 100644 --- a/Podfile +++ b/Podfile @@ -98,13 +98,3 @@ target 'Storage' do inherit! :search_paths end end - - -# Workaround: Drop ARMv7 Architecture: -# ==================================== -# -post_install do |installer| - installer.pods_project.build_configuration_list.build_configurations.each do |configuration| - configuration.build_settings['VALID_ARCHS'] = 'arm64 armv7s' - end -end diff --git a/Podfile.lock b/Podfile.lock index 7c4bc800e35..1d61b91fa31 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -52,7 +52,7 @@ PODS: - WordPressShared (~> 1.0) - WordPressUI (~> 1.0) - wpxmlrpc (~> 0.8) - - WordPressKit (1.2): + - WordPressKit (1.2.1): - Alamofire (~> 4.7) - CocoaLumberjack (= 3.4.2) - NSObject-SafeExpectations (= 0.0.3) @@ -128,11 +128,11 @@ SPEC CHECKSUMS: SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 UIDeviceIdentifier: a959a6d4f51036b4180dd31fb26483a820f1cc46 WordPressAuthenticator: 2825f0c56f83a17470564dbec427991fa5cac5af - WordPressKit: 68eaa8df5ceedeed03ba796afc4b825f0bed4fe2 + WordPressKit: a4a3849684f631a3abf579f6d3f15a32677cbb30 WordPressShared: 063e1e8b1a7aaf635abf17f091a2d235a068abdc WordPressUI: af141587ec444f9af753a00605bd0d3f14d8d8a3 wpxmlrpc: bfc572f62ce7ee897f6f38b098d2ba08732ecef4 -PODFILE CHECKSUM: d74ce74de818d7610b3ee7a23df5bf7890d6197e +PODFILE CHECKSUM: c627b723e165dabc83c6716871f3199190464f2c COCOAPODS: 1.5.3 From de13388364a5a0ab233caef746e031e37b4273ed Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 20:37:29 -0300 Subject: [PATCH 058/112] Nuking explicit architecture settings --- Networking/Networking.xcodeproj/project.pbxproj | 6 ++++-- Storage/Storage.xcodeproj/project.pbxproj | 6 ++++-- WooCommerce/WooCommerce.xcodeproj/project.pbxproj | 12 ++++++++---- Yosemite/Yosemite.xcodeproj/project.pbxproj | 6 ++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 95919fb190a..5d63c07cf6d 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -830,7 +830,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 armv7s"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -859,7 +859,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 armv7s"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; @@ -879,6 +879,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -898,6 +899,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; diff --git a/Storage/Storage.xcodeproj/project.pbxproj b/Storage/Storage.xcodeproj/project.pbxproj index 179de3b5924..7945972568c 100644 --- a/Storage/Storage.xcodeproj/project.pbxproj +++ b/Storage/Storage.xcodeproj/project.pbxproj @@ -624,7 +624,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 armv7s"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -653,7 +653,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 armv7s"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; @@ -678,6 +678,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -701,6 +702,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index a8e987da833..1a43d45a042 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1176,6 +1176,7 @@ INFOPLIST_PREFIX_HEADER = InfoPlist.h; PRODUCT_NAME = "$(TARGET_NAME)"; SECRETS_PATH = $HOME/.woo_app_credentials.json; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -1186,6 +1187,7 @@ INFOPLIST_PREFIX_HEADER = InfoPlist.h; PRODUCT_NAME = "$(TARGET_NAME)"; SECRETS_PATH = $HOME/.woo_app_credentials.json; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; @@ -1246,7 +1248,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "Classes/System/WooCommerce-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VALID_ARCHS = "arm64 armv7 armv7s x86_64"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -1300,7 +1302,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "Classes/System/WooCommerce-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; - VALID_ARCHS = "arm64 armv7 armv7s x86_64"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; @@ -1326,7 +1328,7 @@ SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = ""; - VALID_ARCHS = "arm64 armv7s x86_64"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -1352,7 +1354,7 @@ SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = ""; - VALID_ARCHS = "arm64 armv7s x86_64"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; @@ -1372,6 +1374,7 @@ SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WooCommerce.app/WooCommerce"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -1390,6 +1393,7 @@ SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WooCommerce.app/WooCommerce"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 975d620e12a..94059b8cb0d 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -704,7 +704,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 armv7s"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -732,7 +732,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 armv7s"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; @@ -752,6 +752,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(inherited)"; }; name = Debug; }; @@ -771,6 +772,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "$(inherited)"; }; name = Release; }; From ba90b0e0377d422f428ad7f2ed835b7f189857c9 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 21:15:36 -0300 Subject: [PATCH 059/112] DotcomRequestTests: Fixing Tests with random failures --- .../Requests/DotcomRequestTests.swift | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Networking/NetworkingTests/Requests/DotcomRequestTests.swift b/Networking/NetworkingTests/Requests/DotcomRequestTests.swift index b3ec0329020..c63f66e2d2a 100644 --- a/Networking/NetworkingTests/Requests/DotcomRequestTests.swift +++ b/Networking/NetworkingTests/Requests/DotcomRequestTests.swift @@ -46,7 +46,20 @@ class DotcomRequestTests: XCTestCase { let expectedURL = URL(string: DotcomRequest.wordpressApiBaseURL + request.wordpressApiVersion.path + sampleRPC + sampleParametersForQuery)! let generatedURL = try! request.asURLRequest().url! - XCTAssertEqual(expectedURL, generatedURL) + + /// Note: Why not compare URL's directly?. As of iOS 12, URLQueryItem's serialization to string can result in swizzled entries. + /// (Exact same result, but shuffled order!). For that reason we compare piece by piece!. + /// + let expectedComponents = URLComponents(url: expectedURL, resolvingAgainstBaseURL: false)! + let generatedComponents = URLComponents(url: generatedURL, resolvingAgainstBaseURL: false)! + + let expectedQueryItems = Set(generatedComponents.queryItems!) + let generatedQueryItems = Set(expectedComponents.queryItems!) + + XCTAssertEqual(expectedComponents.scheme, generatedComponents.scheme) + XCTAssertEqual(expectedComponents.percentEncodedHost, generatedComponents.percentEncodedHost) + XCTAssertEqual(expectedComponents.percentEncodedPath, generatedComponents.percentEncodedPath) + XCTAssertEqual(expectedQueryItems, generatedQueryItems) } /// Verifies that the DotcomRequest's generated URL does NOT contain the Parameters serialized as part of the query, when the method is `post`. @@ -66,7 +79,17 @@ class DotcomRequestTests: XCTestCase { let generatedBodyAsData = try! request.asURLRequest().httpBody! let generatedBodyAsString = String(data: generatedBodyAsData, encoding: .utf8) - XCTAssertEqual(generatedBodyAsString, sampleParametersForBody) + let generatedBodyParameters = generatedBodyAsString!.split(separator: Character("&")) + + /// Note: As of iOS 12 the parameters were being serialized at random positions. That's *why* this test is a bit extra complex! + /// + for parameter in generatedBodyParameters { + let components = parameter.split(separator: Character("=")) + let key = String(components[0]) + let value = String(components[1]) + + XCTAssertEqual(value, sampleParameters[key]) + } } } From 6e6b034d82fb3e585ec96872ba7a86fdc88de005 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 21:16:27 -0300 Subject: [PATCH 060/112] Fixing Xcode 10's AutoLinking Framework(s) Missing --- Podfile | 10 +++++++++- Podfile.lock | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Podfile b/Podfile index 512c2601b49..99678627f0d 100644 --- a/Podfile +++ b/Podfile @@ -29,6 +29,14 @@ target 'WooCommerce' do pod 'Crashlytics', '~> 3.10' pod 'KeychainAccess', '~> 3.1' pod 'CocoaLumberjack/Swift', '~> 3.4' + + # Unit Tests + # ========== + # + target 'WooCommerceTests' do + inherit! :search_paths + end + end @@ -42,6 +50,7 @@ target 'Yosemite' do # External Libraries # ================== # + pod 'Alamofire', '~> 4.7' pod 'CocoaLumberjack/Swift', '~> 3.4' # Unit Tests @@ -49,7 +58,6 @@ target 'Yosemite' do # target 'YosemiteTests' do inherit! :search_paths - pod 'Alamofire', '~> 4.7' end end diff --git a/Podfile.lock b/Podfile.lock index 1d61b91fa31..e6d7e86ea78 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -133,6 +133,6 @@ SPEC CHECKSUMS: WordPressUI: af141587ec444f9af753a00605bd0d3f14d8d8a3 wpxmlrpc: bfc572f62ce7ee897f6f38b098d2ba08732ecef4 -PODFILE CHECKSUM: c627b723e165dabc83c6716871f3199190464f2c +PODFILE CHECKSUM: bfda0b2c44f6c084bf2f2053a4f64e394fec7007 COCOAPODS: 1.5.3 From 4a100d2617c5d32978fc9c76e80c3f319a993f89 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 21:16:40 -0300 Subject: [PATCH 061/112] Updating Project --- .../WooCommerce.xcodeproj/project.pbxproj | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 1a43d45a042..980be07c4b2 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -95,6 +95,7 @@ B5DBF3C320E1484400B53AED /* StoresManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3C220E1484400B53AED /* StoresManagerTests.swift */; }; B5DBF3C520E148E000B53AED /* DeauthenticatedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3C420E148E000B53AED /* DeauthenticatedState.swift */; }; B5DBF3CB20E149CC00B53AED /* AuthenticatedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3CA20E149CC00B53AED /* AuthenticatedState.swift */; }; + B873E8F8E103966D2182EE67 /* Pods_WooCommerceTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DC4526F9A7357761197EBF0 /* Pods_WooCommerceTests.framework */; }; CE17C2E220ACA06800AFBD20 /* BillingDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE17C2E020ACA06800AFBD20 /* BillingDetailsTableViewCell.swift */; }; CE17C2E320ACA06800AFBD20 /* BillingDetailsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE17C2E120ACA06800AFBD20 /* BillingDetailsTableViewCell.xib */; }; CE1CCB402056F21C000EE3AC /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1CCB3F2056F21C000EE3AC /* Style.swift */; }; @@ -189,14 +190,17 @@ /* Begin PBXFileReference section */ 33035144757869DE5E4DC88A /* Pods-WooCommerce.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WooCommerce.release.xcconfig"; path = "../Pods/Target Support Files/Pods-WooCommerce/Pods-WooCommerce.release.xcconfig"; sourceTree = ""; }; + 6DC4526F9A7357761197EBF0 /* Pods_WooCommerceTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WooCommerceTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7403F7E120EC04070097198F /* OrderStatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatusViewModel.swift; sourceTree = ""; }; 74213449210A323C00C13890 /* WooAnalyticsStat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooAnalyticsStat.swift; sourceTree = ""; }; 746791622108D7C0007CF1DC /* WooAnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooAnalyticsTests.swift; sourceTree = ""; }; 746791652108D87B007CF1DC /* MockupAnalyticsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockupAnalyticsProvider.swift; sourceTree = ""; }; 747AA0882107CEC60047A89B /* AnalyticsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsProvider.swift; sourceTree = ""; }; 747AA08A2107CF8D0047A89B /* TracksProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracksProvider.swift; sourceTree = ""; }; + 8A659E65308A3D9DD79A95F9 /* Pods-WooCommerceTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WooCommerceTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-WooCommerceTests/Pods-WooCommerceTests.release.xcconfig"; sourceTree = ""; }; 90AC1C0B391E04A837BDC64E /* Pods-WooCommerce.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WooCommerce.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-WooCommerce/Pods-WooCommerce.debug.xcconfig"; sourceTree = ""; }; 93BCF01E20DC2CE200EBF7A1 /* bash_secrets.tpl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = bash_secrets.tpl; sourceTree = ""; }; + 9D2992FEF3D1246B8CCC2EBB /* Pods-WooCommerceTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WooCommerceTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-WooCommerceTests/Pods-WooCommerceTests.debug.xcconfig"; sourceTree = ""; }; B509112D2049E27A007D25DC /* DashboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashboardViewController.swift; sourceTree = ""; }; B509112E2049E27A007D25DC /* OrdersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrdersViewController.swift; sourceTree = ""; }; B509112F2049E27A007D25DC /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; @@ -345,6 +349,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B873E8F8E103966D2182EE67 /* Pods_WooCommerceTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -380,6 +385,7 @@ B5C3B5E420D189EA0072CB9D /* Storage.framework */, B5C3B5E220D189E60072CB9D /* Networking.framework */, BABE5E07DD787ECA6D2A76DE /* Pods_WooCommerce.framework */, + 6DC4526F9A7357761197EBF0 /* Pods_WooCommerceTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -748,6 +754,8 @@ children = ( 90AC1C0B391E04A837BDC64E /* Pods-WooCommerce.debug.xcconfig */, 33035144757869DE5E4DC88A /* Pods-WooCommerce.release.xcconfig */, + 9D2992FEF3D1246B8CCC2EBB /* Pods-WooCommerceTests.debug.xcconfig */, + 8A659E65308A3D9DD79A95F9 /* Pods-WooCommerceTests.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -782,6 +790,7 @@ isa = PBXNativeTarget; buildConfigurationList = B56DB3E92049BFAA00D4AA8E /* Build configuration list for PBXNativeTarget "WooCommerceTests" */; buildPhases = ( + E8FC62641D61F33F705BC760 /* [CP] Check Pods Manifest.lock */, B56DB3D92049BFAA00D4AA8E /* Sources */, B56DB3DA2049BFAA00D4AA8E /* Frameworks */, B56DB3DB2049BFAA00D4AA8E /* Resources */, @@ -1029,6 +1038,24 @@ shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-WooCommerce/Pods-WooCommerce-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + E8FC62641D61F33F705BC760 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-WooCommerceTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1175,7 +1202,7 @@ CODE_SIGN_STYLE = Automatic; INFOPLIST_PREFIX_HEADER = InfoPlist.h; PRODUCT_NAME = "$(TARGET_NAME)"; - SECRETS_PATH = $HOME/.woo_app_credentials.json; + SECRETS_PATH = "$HOME/.woo_app_credentials.json"; VALID_ARCHS = "$(inherited)"; }; name = Debug; @@ -1186,7 +1213,7 @@ CODE_SIGN_STYLE = Automatic; INFOPLIST_PREFIX_HEADER = InfoPlist.h; PRODUCT_NAME = "$(TARGET_NAME)"; - SECRETS_PATH = $HOME/.woo_app_credentials.json; + SECRETS_PATH = "$HOME/.woo_app_credentials.json"; VALID_ARCHS = "$(inherited)"; }; name = Release; @@ -1324,7 +1351,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = "WooCommerce Development"; - SECRETS_PATH = $HOME/.woo_app_credentials.json; + SECRETS_PATH = "$HOME/.woo_app_credentials.json"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = ""; @@ -1350,7 +1377,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.automattic.woocommerce; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "WooCommerce App Store"; - SECRETS_PATH = $HOME/.woo_app_credentials.json; + SECRETS_PATH = "$HOME/.woo_app_credentials.json"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = ""; @@ -1360,6 +1387,7 @@ }; B56DB3EA2049BFAA00D4AA8E /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 9D2992FEF3D1246B8CCC2EBB /* Pods-WooCommerceTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -1380,6 +1408,7 @@ }; B56DB3EB2049BFAA00D4AA8E /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 8A659E65308A3D9DD79A95F9 /* Pods-WooCommerceTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; From 357b48c3ac85179b4667c6feb353e861551a4ad7 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 21:38:32 -0300 Subject: [PATCH 062/112] WooAnalyticsTests: Fixing Xcode 10 Random Failure --- .../WooCommerceTests/System/WooAnalyticsTests.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift b/WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift index 01bfbb1384a..00b65c069dc 100644 --- a/WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift +++ b/WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift @@ -68,10 +68,17 @@ class WooAnalyticsTests: XCTestCase { XCTAssertEqual(testingProvider?.receivedEvents.count, 1) XCTAssertEqual(testingProvider?.receivedProperties.count, 1) XCTAssertEqual(testingProvider?.receivedEvents.first, WooAnalyticsStat.applicationOpened.rawValue) - if let receivedProperty1 = testingProvider?.receivedProperties[0] as? [String: String] { - XCTAssertEqual(receivedProperty1, Constants.testErrorReceivedProperty) - } else { + + guard let receivedProperty1 = testingProvider?.receivedProperties[0] as? [String: String] else { XCTFail() + return + } + + /// Note: iOS 12 is shuffling several dictionaries (specially when it comes to serializing [:] > URL Parameters). + /// For that reason, we'll proceed with a bit of a more lengthy but robust check. + /// + for (key, value) in receivedProperty1 { + XCTAssertEqual(value, Constants.testErrorReceivedProperty[key]) } } } From 472db2b781e4d503649aa3c675028ebc8292ae39 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 2 Aug 2018 21:40:34 -0300 Subject: [PATCH 063/112] NSManagedObject+Object: Fixing iOS 12 Console Errors (in Unit Tests) --- Storage/Storage/Extensions/NSManagedObject+Object.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Storage/Storage/Extensions/NSManagedObject+Object.swift b/Storage/Storage/Extensions/NSManagedObject+Object.swift index 19570b0e368..573538bce4f 100644 --- a/Storage/Storage/Extensions/NSManagedObject+Object.swift +++ b/Storage/Storage/Extensions/NSManagedObject+Object.swift @@ -12,6 +12,10 @@ extension NSManagedObject: Object { /// Note: entity().name returns nil as per iOS 10, in Unit Testing Targets. Awesome. /// public class var entityName: String { - return entity().name ?? classNameWithoutNamespaces() + /// Note: As of iOS 12, spawning multiple CoreData Stack instances in Unit Tests may result in Console Errors + /// ("This class is already claimed by another NSEntityDescription"). This error is triggered by the `entity()` method. + /// For that reason, we're falling back to `classNameWithoutNamespaces()`, which keeps our console tidy. + /// + return classNameWithoutNamespaces() } } From 785a35e0eb3d54337737a11722da5773e2f3a5dc Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Fri, 3 Aug 2018 10:15:32 -0500 Subject: [PATCH 064/112] Added tests for the OrderStatsStore+action --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 8 + .../YosemiteTests/Resources/order-stats.json | 380 ++++++++++++++++++ .../Stores/OrderStatsStoreTests.swift | 146 +++++++ 3 files changed, 534 insertions(+) create mode 100644 Yosemite/YosemiteTests/Resources/order-stats.json create mode 100644 Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 0b1edff02b8..c2766230a2a 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 7424B49420EAD37C00CC62F6 /* order.json in Resources */ = {isa = PBXBuildFile; fileRef = 7424B49320EAD37C00CC62F6 /* order.json */; }; 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */; }; 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */; }; + 7495C5262114977600CDD33B /* order-stats.json in Resources */ = {isa = PBXBuildFile; fileRef = 7495C5222114977600CDD33B /* order-stats.json */; }; + 7495C5292114979D00CDD33B /* OrderStatsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */; }; 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */; }; 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */; }; 7499936820EFC0ED00CF01CD /* OrderNoteStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */; }; @@ -69,6 +71,8 @@ 745D21C120D8043A00BBE7C3 /* generic_error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = generic_error.json; sourceTree = ""; }; 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+ReadOnlyConvertible.swift"; sourceTree = ""; }; + 7495C5222114977600CDD33B /* order-stats.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats.json"; sourceTree = ""; }; + 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsStoreTests.swift; sourceTree = ""; }; 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteAction.swift; sourceTree = ""; }; 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteStore.swift; sourceTree = ""; }; 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteStoreTests.swift; sourceTree = ""; }; @@ -184,6 +188,7 @@ B5BC736720D1AA8F00B5B6FA /* AccountStoreTests.swift */, 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */, 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */, + 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */, ); path = Stores; sourceTree = ""; @@ -197,6 +202,7 @@ 7499936B20EFC20100CF01CD /* order-notes.json */, 74A7689120D47F9E00F9D437 /* orders.json */, B5B914C120EFE03200F2F832 /* sites.json */, + 7495C5222114977600CDD33B /* order-stats.json */, ); path = Resources; sourceTree = ""; @@ -444,6 +450,7 @@ B5B914C220EFE03200F2F832 /* sites.json in Resources */, 7499936C20EFC20100CF01CD /* order-notes.json in Resources */, 7424B49420EAD37C00CC62F6 /* order.json in Resources */, + 7495C5262114977600CDD33B /* order-stats.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -542,6 +549,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7495C5292114979D00CDD33B /* OrderStatsStoreTests.swift in Sources */, 74A7688E20D45ED400F9D437 /* OrderStoreTests.swift in Sources */, B5C9DE272087FF20006B910A /* MockupAcount.swift in Sources */, B5C9DE222087FF20006B910A /* DispatcherTests.swift in Sources */, diff --git a/Yosemite/YosemiteTests/Resources/order-stats.json b/Yosemite/YosemiteTests/Resources/order-stats.json new file mode 100644 index 00000000000..275c71d380b --- /dev/null +++ b/Yosemite/YosemiteTests/Resources/order-stats.json @@ -0,0 +1,380 @@ +{ + "date": "2018-06-02", + "unit": "day", + "quantity": "2", + "fields": [ + "period", + "orders", + "products", + "coupons", + "coupon_discount", + "total_sales", + "total_tax", + "total_shipping", + "total_shipping_tax", + "total_refund", + "total_tax_refund", + "total_shipping_refund", + "total_shipping_tax_refund", + "currency", + "gross_sales", + "net_sales", + "avg_order_value", + "avg_products_per_order" + ], + "data": [ + [ + "2018-06-01", + 2, + 2, + 0, + 0, + 14.24, + 0.12, + 9.98, + 0.28, + 0, + 0, + 0, + 0, + "USD", + 14.24, + 14.120000000000001, + 7.12, + 1 + ], + [ + "2018-06-02", + 1, + 1, + 0, + 0, + 30.87, + 0.87, + 0, + 0, + 0, + 0, + 0, + 0, + "USD", + 30.87, + 30, + 30.87, + 1 + ] + ], + "delta_fields": [ + "period", + "delta", + "percentage_change", + "reference_period", + "favorable", + "direction", + "currency" + ], + "deltas": [ + { + "period": "2018-06-01", + "orders": [ + "2018-06-01", + 1, + 1, + "2018-05-31", + "is-favorable", + "is-increase", + "USD" + ], + "products": [ + "2018-06-01", + -3, + -0.6, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-01", + -135.76, + -0.9051, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_tax": [ + "2018-06-01", + 0.12, + 0, + "2018-05-31", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping": [ + "2018-06-01", + 9.98, + 0, + "2018-05-31", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_shipping_tax": [ + "2018-06-01", + 0.28, + 0, + "2018-05-31", + "is-favorable", + "is-undefined-increase", + "USD" + ], + "total_refund": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-01", + 0, + 0, + "2018-05-31", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-01", + -135.76, + -0.9051, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "net_sales": [ + "2018-06-01", + -135.88, + -0.9059, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_order_value": [ + "2018-06-01", + -142.88, + -0.9525, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ], + "avg_products_per_order": [ + "2018-06-01", + -4, + -0.8, + "2018-05-31", + "is-unfavorable", + "is-decrease", + "USD" + ] + }, + { + "period": "2018-06-02", + "orders": [ + "2018-06-02", + -1, + -0.5, + "2018-06-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "products": [ + "2018-06-02", + -1, + -0.5, + "2018-06-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "coupons": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "coupon_discount": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "total_sales": [ + "2018-06-02", + 16.630000000000003, + 1.1678, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "total_tax": [ + "2018-06-02", + 0.75, + 6.25, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "total_shipping": [ + "2018-06-02", + -9.98, + -1, + "2018-06-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_shipping_tax": [ + "2018-06-02", + -0.28, + -1, + "2018-06-01", + "is-unfavorable", + "is-decrease", + "USD" + ], + "total_refund": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "total_tax_refund": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "total_shipping_refund": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "total_shipping_tax_refund": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ], + "gross_sales": [ + "2018-06-02", + 16.630000000000003, + 1.1678, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "net_sales": [ + "2018-06-02", + 15.879999999999999, + 1.1246, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "avg_order_value": [ + "2018-06-02", + 23.75, + 3.3357, + "2018-06-01", + "is-favorable", + "is-increase", + "USD" + ], + "avg_products_per_order": [ + "2018-06-02", + 0, + 0, + "2018-06-01", + "", + "is-neutral", + "USD" + ] + } + ], + "total_gross_sales": 439.23, + "total_net_sales": 438.24, + "total_orders": 9, + "total_products": 13, + "avg_gross_sales": 14.1687, + "avg_net_sales": 14.1368, + "avg_orders": 0.2903, + "avg_products": 0.4194 +} diff --git a/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift b/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift new file mode 100644 index 00000000000..422b78d7e8a --- /dev/null +++ b/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift @@ -0,0 +1,146 @@ +import XCTest +@testable import Yosemite +@testable import Networking + + +/// OrderStatsStore Unit Tests +/// +class OrderStatsStoreTests: XCTestCase { + + /// Mockup Dispatcher! + /// + private var dispatcher: Dispatcher! + + /// Mockup Network: Allows us to inject predefined responses! + /// + private var network: MockupNetwork! + + /// Mockup Storage: InMemory + /// + private var storageManager: MockupStorageManager! + + /// Dummy Site ID + /// + private let sampleSiteID = 123 + + + override func setUp() { + super.setUp() + dispatcher = Dispatcher() + storageManager = MockupStorageManager() + network = MockupNetwork() + } + + /// Verifies that OrderStatsAction.retrieveOrderStats returns the expected stats. + /// + func testRetrieveOrderStatsReturnsExpectedFields() { + let expectation = self.expectation(description: "Retrieve order stats") + let orderStatsStore = OrderStatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let remoteOrderStats = sampleOrderStats() + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/orders/", filename: "order-stats") + let action = OrderStatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, + latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (orderStats, error) in + XCTAssertNil(error) + guard let orderStats = orderStats, + let items = orderStats.items else { + XCTFail() + return + } + XCTAssertEqual(items.count, 2) + XCTAssertEqual(orderStats, remoteOrderStats) + expectation.fulfill() + } + + orderStatsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that OrderStatsAction.retrieveOrderStats returns an error whenever there is an error response from the backend. + /// + func testRetrieveOrderStatsReturnsErrorUponReponseError() { + let expectation = self.expectation(description: "Retrieve order stats error response") + let orderStatsStore = OrderStatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/orders/", filename: "generic_error") + let action = OrderStatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, + latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (orderStats, error) in + XCTAssertNil(orderStats) + XCTAssertNotNil(error) + guard let _ = error else { + XCTFail() + return + } + expectation.fulfill() + } + + orderStatsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that OrderStatsAction.retrieveOrderStats returns an error whenever there is no backend response. + /// + func testRetrieveOrderNotesReturnsErrorUponEmptyResponse() { + let expectation = self.expectation(description: "Retrieve order stats empty response") + let orderStatsStore = OrderStatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + let action = OrderStatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, + latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (orderStats, error) in + XCTAssertNotNil(error) + XCTAssertNil(orderStats) + guard let _ = error else { + XCTFail() + return + } + expectation.fulfill() + } + + orderStatsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } +} + + +// MARK: - Private Methods +// +private extension OrderStatsStoreTests { + + func sampleOrderStats() -> OrderStats { + return OrderStats(date: "2018-06-02", + granularity: .day, + quantity: "2", + fields: ["period", "orders", "products", "coupons", "coupon_discount", "total_sales", "total_tax", "total_shipping", + "total_shipping_tax", "total_refund", "total_tax_refund", "total_shipping_refund", "total_shipping_tax_refund", + "currency", "gross_sales", "net_sales", "avg_order_value", "avg_products_per_order"], + items: [sampleOrderStatsItem1(), sampleOrderStatsItem2()], + totalGrossSales: 439.23, + totalNetSales: 438.24, + totalOrders: 9, + totalProducts: 13, + averageGrossSales: 14.1687, + averageNetSales: 14.1368, + averageOrders: 0.2903, + averageProducts: 0.4194) + } + + func sampleOrderStatsItem1() -> OrderStatsItem { + return OrderStatsItem(fieldNames: ["period", "orders", "products", "coupons", "coupon_discount", "total_sales", "total_tax", "total_shipping", + "total_shipping_tax", "total_refund", "total_tax_refund", "total_shipping_refund", "total_shipping_tax_refund", + "currency", "gross_sales", "net_sales", "avg_order_value", "avg_products_per_order"], + rawData: ["2018-06-01", 2, 2, 0, 0, 14.24, 0.12, 9.9800000000000004, 0.28000000000000003, 0, 0, 0, 0, "USD", 14.24, 14.120000000000001, 7.1200000000000001, 1]) + } + + func sampleOrderStatsItem2() -> OrderStatsItem { + return OrderStatsItem(fieldNames: ["period", "orders", "products", "coupons", "coupon_discount", "total_sales", "total_tax", "total_shipping", + "total_shipping_tax", "total_refund", "total_tax_refund", "total_shipping_refund", "total_shipping_tax_refund", + "currency", "gross_sales", "net_sales", "avg_order_value", "avg_products_per_order"], + rawData: ["2018-06-02", 1, 1, 0, 0, 30.870000000000001, 0.87, 0, 0, 0, 0, 0, 0, "USD", 30.870000000000001, 30, 30.870000000000001, 1]) + } + + func date(with dateString: String) -> Date { + guard let date = DateFormatter.Defaults.dateTimeFormatter.date(from: dateString) else { + return Date() + } + return date + } +} From fa7f7ecf6694e14f2b8380e695cefd68c0e797f0 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 3 Aug 2018 13:31:05 -0300 Subject: [PATCH 065/112] Implements FabricManager --- .../Classes/Tools/Fabric/FabricManager.swift | 48 +++++++++++++++++++ .../WooCommerce.xcodeproj/project.pbxproj | 12 +++++ 2 files changed, 60 insertions(+) create mode 100644 WooCommerce/Classes/Tools/Fabric/FabricManager.swift diff --git a/WooCommerce/Classes/Tools/Fabric/FabricManager.swift b/WooCommerce/Classes/Tools/Fabric/FabricManager.swift new file mode 100644 index 00000000000..4c9861569ac --- /dev/null +++ b/WooCommerce/Classes/Tools/Fabric/FabricManager.swift @@ -0,0 +1,48 @@ +import Foundation +import UIKit + +import CocoaLumberjack +import Crashlytics +import Fabric +import Yosemite + + + +/// FabricManager: Encapsulates all of the Fabric SDK Setup. +/// +class FabricManager { + + deinit { + NotificationCenter.default.removeObserver(self) + } + + /// Initializes the Fabric SDK. + /// + func initialize() { + Fabric.with([Crashlytics.self]) + } + + /// Starts listening to Authentication Notifications: Fabric's metadata will be refreshed accordingly. + /// + func startListeningToAuthNotifications() { + let nc = NotificationCenter.default + nc.addObserver(self, selector: #selector(defaultAccountWasUpdated), name: .defaultAccountWasUpdated, object: nil) + } + + /// Handles the `.sessionWasAuthenticated` notification. + /// + @objc func defaultAccountWasUpdated(sender: Notification) { + let account = sender.object as? Yosemite.Account + let crashlytics = Crashlytics.sharedInstance() + + crashlytics.setUserName(account?.username) + crashlytics.setUserEmail(account?.email) + crashlytics.setUserIdentifier(account?.userID.description) + + if let username = account?.username { + DDLogInfo("🌡 Fabric Account: [\(username)]") + } else { + DDLogInfo("🌡 Fabric Account Nuked!") + } + } +} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 8b18344684b..f62703af128 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -92,6 +92,7 @@ B5D1AFC020BC67C200DB0E8C /* WooConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1AFBF20BC67C200DB0E8C /* WooConstants.swift */; }; B5D1AFC620BC7B7300DB0E8C /* StorePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1AFC520BC7B7300DB0E8C /* StorePickerViewController.swift */; }; B5D1AFC820BC7B9600DB0E8C /* StorePickerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5D1AFC720BC7B9600DB0E8C /* StorePickerViewController.xib */; }; + B5DB01B52114AB2D00A4F797 /* FabricManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DB01B42114AB2D00A4F797 /* FabricManager.swift */; }; B5DBF3C320E1484400B53AED /* StoresManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3C220E1484400B53AED /* StoresManagerTests.swift */; }; B5DBF3C520E148E000B53AED /* DeauthenticatedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3C420E148E000B53AED /* DeauthenticatedState.swift */; }; B5DBF3CB20E149CC00B53AED /* AuthenticatedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3CA20E149CC00B53AED /* AuthenticatedState.swift */; }; @@ -266,6 +267,7 @@ B5D1AFBF20BC67C200DB0E8C /* WooConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooConstants.swift; sourceTree = ""; }; B5D1AFC520BC7B7300DB0E8C /* StorePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePickerViewController.swift; sourceTree = ""; }; B5D1AFC720BC7B9600DB0E8C /* StorePickerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StorePickerViewController.xib; sourceTree = ""; }; + B5DB01B42114AB2D00A4F797 /* FabricManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FabricManager.swift; sourceTree = ""; }; B5DBF3C220E1484400B53AED /* StoresManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoresManagerTests.swift; sourceTree = ""; }; B5DBF3C420E148E000B53AED /* DeauthenticatedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeauthenticatedState.swift; sourceTree = ""; }; B5DBF3CA20E149CC00B53AED /* AuthenticatedState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticatedState.swift; sourceTree = ""; }; @@ -437,6 +439,7 @@ B55D4C2220B716CE00D7A50F /* Tools */ = { isa = PBXGroup; children = ( + B5DB01B32114AB2D00A4F797 /* Fabric */, B58B4ABC2108F7F800076FDD /* Notices */, B55D4C2620B717C000D7A50F /* UserAgent.swift */, B5BE368D20BED80B00BE0A8C /* SafariViewController.swift */, @@ -600,6 +603,14 @@ path = Epilogue; sourceTree = ""; }; + B5DB01B32114AB2D00A4F797 /* Fabric */ = { + isa = PBXGroup; + children = ( + B5DB01B42114AB2D00A4F797 /* FabricManager.swift */, + ); + path = Fabric; + sourceTree = ""; + }; B5DBF3C120E1482900B53AED /* Yosemite */ = { isa = PBXGroup; children = ( @@ -1041,6 +1052,7 @@ files = ( CE85535D209B5BB700938BDC /* OrderDetailsViewModel.swift in Sources */, CE21B3E020FFC59700A259D5 /* ProductDetailsTableViewCell.swift in Sources */, + B5DB01B52114AB2D00A4F797 /* FabricManager.swift in Sources */, CE85FD5F20F7BE8D0080B73E /* LogOutTableViewCell.swift in Sources */, CE32B11A20BF8E32006FBCF4 /* UIButton+Helpers.swift in Sources */, CE263DE6206ACD220015A693 /* NotificationsViewController.swift in Sources */, From 691d46c51b328ae4bac34f9aaa688954033366dc Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 3 Aug 2018 13:31:47 -0300 Subject: [PATCH 066/112] SessionManager: Posting Account Update Notifications --- WooCommerce/Classes/System/SessionManager.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/WooCommerce/Classes/System/SessionManager.swift b/WooCommerce/Classes/System/SessionManager.swift index cac2f085fa1..dda77645dbb 100644 --- a/WooCommerce/Classes/System/SessionManager.swift +++ b/WooCommerce/Classes/System/SessionManager.swift @@ -4,6 +4,16 @@ import KeychainAccess +// MARK: - SessionManager Notifications +// +extension NSNotification.Name { + + /// Posted whenever the Default Account is updated. + /// + public static let defaultAccountWasUpdated = Foundation.Notification.Name(rawValue: "DefaultAccountWasUpdated") +} + + /// SessionManager provides persistent storage for Session-Y Properties. /// struct SessionManager { @@ -47,6 +57,7 @@ struct SessionManager { var defaultAccount: Yosemite.Account? { didSet { defaults[.defaultAccountID] = defaultAccount?.userID + NotificationCenter.default.post(name: .defaultAccountWasUpdated, object: defaultAccount) } } From 3ad58a2968b3c9af55c649142cadc93494726434 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 3 Aug 2018 13:31:59 -0300 Subject: [PATCH 067/112] AppDelegate: Wiring FabricManager --- WooCommerce/Classes/AppDelegate.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/WooCommerce/Classes/AppDelegate.swift b/WooCommerce/Classes/AppDelegate.swift index 7d8dc93c2af..9f4ed10aac3 100644 --- a/WooCommerce/Classes/AppDelegate.swift +++ b/WooCommerce/Classes/AppDelegate.swift @@ -3,8 +3,6 @@ import CoreData import Storage import CocoaLumberjack -import Crashlytics -import Fabric import WordPressUI import WordPressKit import WordPressAuthenticator @@ -30,6 +28,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { /// let authenticationManager = AuthenticationManager() + /// Fabric: Crash Reporting + /// + let fabricManager = FabricManager() + /// In-App Notifications Presenter /// let noticePresenter = NoticePresenter() @@ -49,6 +51,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { setupComponentsAppearance() // Setup Components + setupFabric() setupAnalytics() setupAuthenticationManager() setupCocoaLumberjack() @@ -67,8 +70,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - Fabric.with([Crashlytics.self]) - return true } @@ -141,6 +142,13 @@ private extension AppDelegate { appearance.primaryHighlightBorderColor = StyleManager.buttonPrimaryHighlightedColor } + /// Sets up the Fabric SDK. + /// + func setupFabric() { + fabricManager.initialize() + fabricManager.startListeningToAuthNotifications() + } + /// Sets up the WordPress Authenticator. /// func setupAnalytics() { From 7f2152016c56ca1c2c39bf76f0fe18da64a9d1e2 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Fri, 3 Aug 2018 13:00:10 -0500 Subject: [PATCH 068/112] Added FIXME comment --- Yosemite/Yosemite/Actions/OrderStatsAction.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yosemite/Yosemite/Actions/OrderStatsAction.swift b/Yosemite/Yosemite/Actions/OrderStatsAction.swift index 1f988f42cdb..29b819fb234 100644 --- a/Yosemite/Yosemite/Actions/OrderStatsAction.swift +++ b/Yosemite/Yosemite/Actions/OrderStatsAction.swift @@ -5,5 +5,8 @@ import Networking // MARK: - OrderStatsAction: Defines all of the Actions supported by the OrderStatsStore. // public enum OrderStatsAction: Action { + + // FIXME: We are returning OrderStats in the completion handler...this needs to eventually return an error/nil much like + // OrderAction.retrieveOrders. Update this once OrderStats storage is in place. case retrieveOrderStats(siteID: Int, granularity: OrderStatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: (OrderStats?, Error?) -> Void) } From 40b4dfe24843e2d9d24ac2d651559bd1c221161e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 3 Aug 2018 15:02:55 -0300 Subject: [PATCH 069/112] Removing Extra Spaces --- Networking/Networking/Requests/DotcomRequest.swift | 2 +- Networking/Networking/Requests/JetpackRequest.swift | 2 +- Networking/Networking/Settings/Credentials.swift | 2 +- Storage/Storage/CoreData/CoreDataManager.swift | 2 +- Storage/StorageTests/Tools/DummyStack.swift | 2 +- .../Classes/Authentication/Epilogue/AccountHeaderView.swift | 4 ++-- .../Classes/Authentication/Epilogue/StoreTableViewCell.swift | 2 +- WooCommerce/Classes/Tools/Notices/NoticeView.swift | 2 +- WooCommerce/Classes/ViewModels/ContactViewModel.swift | 4 ++-- .../Orders/OrderDetails/ProductDetailsTableViewCell.swift | 2 +- Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift | 2 +- Yosemite/Yosemite/Stores/OrderNoteStore.swift | 2 +- Yosemite/Yosemite/Stores/OrderStore.swift | 2 +- Yosemite/YosemiteTests/Mockups/MockupStorage.swift | 2 +- Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Networking/Networking/Requests/DotcomRequest.swift b/Networking/Networking/Requests/DotcomRequest.swift index e27a4b630b9..a4dc369d63c 100644 --- a/Networking/Networking/Requests/DotcomRequest.swift +++ b/Networking/Networking/Requests/DotcomRequest.swift @@ -4,7 +4,7 @@ import Alamofire /// Represents a WordPress.com Request /// -struct DotcomRequest: URLRequestConvertible { +struct DotcomRequest: URLRequestConvertible { /// WordPress.com Base URL /// diff --git a/Networking/Networking/Requests/JetpackRequest.swift b/Networking/Networking/Requests/JetpackRequest.swift index 6b4a2210307..a33b961388f 100644 --- a/Networking/Networking/Requests/JetpackRequest.swift +++ b/Networking/Networking/Requests/JetpackRequest.swift @@ -4,7 +4,7 @@ import Alamofire /// Represents a Jetpack-Tunneled WordPress.com Endpoint /// -struct JetpackRequest: URLRequestConvertible { +struct JetpackRequest: URLRequestConvertible { /// WordPress.com API Version: By Default, we'll go thru Mark 1.1. /// diff --git a/Networking/Networking/Settings/Credentials.swift b/Networking/Networking/Settings/Credentials.swift index c5db1127b82..2d20b7bc913 100644 --- a/Networking/Networking/Settings/Credentials.swift +++ b/Networking/Networking/Settings/Credentials.swift @@ -17,7 +17,7 @@ public struct Credentials: Equatable { /// Designated Initializer /// public init(username: String, authToken: String) { - self.username = username + self.username = username self.authToken = authToken } } diff --git a/Storage/Storage/CoreData/CoreDataManager.swift b/Storage/Storage/CoreData/CoreDataManager.swift index d832b1c37de..dd3861bfc91 100644 --- a/Storage/Storage/CoreData/CoreDataManager.swift +++ b/Storage/Storage/CoreData/CoreDataManager.swift @@ -108,7 +108,7 @@ extension CoreDataManager { /// Returns the ManagedObjectModel's URL /// - var modelURL: URL { + var modelURL: URL { let bundle = Bundle(for: type(of: self)) guard let url = bundle.url(forResource: name, withExtension: "momd") else { fatalError("Missing Model Resource") diff --git a/Storage/StorageTests/Tools/DummyStack.swift b/Storage/StorageTests/Tools/DummyStack.swift index 5d8ffc7c623..6e08b7188fe 100644 --- a/Storage/StorageTests/Tools/DummyStack.swift +++ b/Storage/StorageTests/Tools/DummyStack.swift @@ -21,7 +21,7 @@ class DummyStack { keyAttribute.name = "key" keyAttribute.attributeType = .stringAttributeType - let valueAttribute = NSAttributeDescription() + let valueAttribute = NSAttributeDescription() valueAttribute.name = "value" valueAttribute.attributeType = .integer64AttributeType diff --git a/WooCommerce/Classes/Authentication/Epilogue/AccountHeaderView.swift b/WooCommerce/Classes/Authentication/Epilogue/AccountHeaderView.swift index 679d19f992e..90de4235a04 100644 --- a/WooCommerce/Classes/Authentication/Epilogue/AccountHeaderView.swift +++ b/WooCommerce/Classes/Authentication/Epilogue/AccountHeaderView.swift @@ -41,7 +41,7 @@ extension AccountHeaderView { /// var username: String? { set { - usernameLabel.text = newValue + usernameLabel.text = newValue } get { return usernameLabel.text @@ -52,7 +52,7 @@ extension AccountHeaderView { /// var fullname: String? { set { - fullnameLabel.text = newValue + fullnameLabel.text = newValue } get { return fullnameLabel.text diff --git a/WooCommerce/Classes/Authentication/Epilogue/StoreTableViewCell.swift b/WooCommerce/Classes/Authentication/Epilogue/StoreTableViewCell.swift index 8f799fd214c..b3d10f95d43 100644 --- a/WooCommerce/Classes/Authentication/Epilogue/StoreTableViewCell.swift +++ b/WooCommerce/Classes/Authentication/Epilogue/StoreTableViewCell.swift @@ -43,7 +43,7 @@ class StoreTableViewCell: UITableViewCell { return urlLabel?.text } set { - urlLabel?.text = newValue + urlLabel?.text = newValue } } diff --git a/WooCommerce/Classes/Tools/Notices/NoticeView.swift b/WooCommerce/Classes/Tools/Notices/NoticeView.swift index 1ce834f7533..88dbe4c1456 100644 --- a/WooCommerce/Classes/Tools/Notices/NoticeView.swift +++ b/WooCommerce/Classes/Tools/Notices/NoticeView.swift @@ -7,7 +7,7 @@ class NoticeView: UIView { private let contentStackView = UIStackView() private let backgroundContainerView = UIView() - private let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight)) + private let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight)) private let actionBackgroundView = UIView() private let shadowLayer = CAShapeLayer() private let shadowMaskLayer = CAShapeLayer() diff --git a/WooCommerce/Classes/ViewModels/ContactViewModel.swift b/WooCommerce/Classes/ViewModels/ContactViewModel.swift index 9346aa48ef3..16069c5f5c0 100644 --- a/WooCommerce/Classes/ViewModels/ContactViewModel.swift +++ b/WooCommerce/Classes/ViewModels/ContactViewModel.swift @@ -20,9 +20,9 @@ class ContactViewModel { init(with address: Address, contactType: ContactType) { switch contactType { case .billing: - title = NSLocalizedString("Billing details", comment: "Billing title for customer info cell") + title = NSLocalizedString("Billing details", comment: "Billing title for customer info cell") case .shipping: - title = NSLocalizedString("Shipping details", comment: "Shipping title for customer info cell") + title = NSLocalizedString("Shipping details", comment: "Shipping title for customer info cell") } let contact = CNContact.from(address: address) fullName = CNContactFormatter.string(from: contact, style: .fullName) ?? address.firstName + " " + address.lastName diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/ProductDetailsTableViewCell.swift b/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/ProductDetailsTableViewCell.swift index ff3d2bda75d..f7b036f616b 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/ProductDetailsTableViewCell.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/ProductDetailsTableViewCell.swift @@ -83,7 +83,7 @@ class ProductDetailsTableViewCell: UITableViewCell { return skuLabel.text } set { - skuLabel.text = newValue + skuLabel.text = newValue } } diff --git a/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift index fa24f557fc2..0defe0acc9f 100644 --- a/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift @@ -9,7 +9,7 @@ extension Storage.Order: ReadOnlyConvertible { /// Updates the Storage.Order with the ReadOnly. /// public func update(with order: Yosemite.Order) { - siteID = Int64(order.siteID) + siteID = Int64(order.siteID) orderID = Int64(order.orderID) parentID = Int64(order.parentID) customerID = Int64(order.customerID) diff --git a/Yosemite/Yosemite/Stores/OrderNoteStore.swift b/Yosemite/Yosemite/Stores/OrderNoteStore.swift index 78f978fa654..086bc9ba5ac 100644 --- a/Yosemite/Yosemite/Stores/OrderNoteStore.swift +++ b/Yosemite/Yosemite/Stores/OrderNoteStore.swift @@ -33,7 +33,7 @@ public class OrderNoteStore: Store { // MARK: - Services! // -private extension OrderNoteStore { +private extension OrderNoteStore { /// Retrieves the order notes associated with the provided Site ID & Order ID (if any!). /// diff --git a/Yosemite/Yosemite/Stores/OrderStore.swift b/Yosemite/Yosemite/Stores/OrderStore.swift index 3beac120ef0..af2f2068948 100644 --- a/Yosemite/Yosemite/Stores/OrderStore.swift +++ b/Yosemite/Yosemite/Stores/OrderStore.swift @@ -35,7 +35,7 @@ public class OrderStore: Store { // MARK: - Services! // -private extension OrderStore { +private extension OrderStore { /// Retrieves the orders associated with a given Site ID (if any!). /// diff --git a/Yosemite/YosemiteTests/Mockups/MockupStorage.swift b/Yosemite/YosemiteTests/Mockups/MockupStorage.swift index a9689284338..8912152e67a 100644 --- a/Yosemite/YosemiteTests/Mockups/MockupStorage.swift +++ b/Yosemite/YosemiteTests/Mockups/MockupStorage.swift @@ -72,7 +72,7 @@ extension MockupStorageManager { /// Returns the ManagedObjectModel's URL: Pick this up from the Storage bundle. OKAY? /// - var modelURL: URL { + var modelURL: URL { let bundle = Bundle(for: CoreDataManager.self) guard let url = bundle.url(forResource: name, withExtension: "momd") else { fatalError("Missing Model Resource") diff --git a/Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift b/Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift index 9237c7bcafd..1c04c3106ad 100644 --- a/Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift +++ b/Yosemite/YosemiteTests/Tools/ResultsControllerTests.swift @@ -206,7 +206,7 @@ class ResultsControllerTests: XCTestCase { /// Verifies that `fetchedObjects` effectively returns all of the (readOnly) objects that are expected to be available. /// func testFetchedObjectsEffectivelyReturnsAvailableEntities() { - let sortDescriptor = NSSortDescriptor(key: #selector(getter: Storage.Account.userID).description, ascending: true) + let sortDescriptor = NSSortDescriptor(key: #selector(getter: Storage.Account.userID).description, ascending: true) let resultsController = ResultsController(viewContext: viewContext, sortedBy: [sortDescriptor]) try? resultsController.performFetch() From 4cc9ab0e55121f4f3feb6b6f7cfab0c742a88e40 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 3 Aug 2018 15:12:44 -0300 Subject: [PATCH 070/112] Relocating Responses JSON files --- .../Networking.xcodeproj/project.pbxproj | 28 ++++++++++++------- .../Responses}/generic_error.json | 0 .../Responses}/order-stats.json | 0 3 files changed, 18 insertions(+), 10 deletions(-) rename {Yosemite/YosemiteTests/Resources => Networking/NetworkingTests/Responses}/generic_error.json (100%) rename {Yosemite/YosemiteTests/Resources => Networking/NetworkingTests/Responses}/order-stats.json (100%) diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 95919fb190a..9e90d3de78c 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -64,6 +64,9 @@ B56C1EB620EA757B00D749F9 /* SiteListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56C1EB520EA757B00D749F9 /* SiteListMapper.swift */; }; B56C1EB820EA76F500D749F9 /* Site.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56C1EB720EA76F500D749F9 /* Site.swift */; }; B56C1EBA20EA7D2C00D749F9 /* sites.json in Resources */ = {isa = PBXBuildFile; fileRef = B56C1EB920EA7D2C00D749F9 /* sites.json */; }; + B58D10C62114D1F200107ED4 /* order-stats.json in Resources */ = {isa = PBXBuildFile; fileRef = B58D10C52114D1F100107ED4 /* order-stats.json */; }; + B58D10C82114D21D00107ED4 /* generic_error.json in Resources */ = {isa = PBXBuildFile; fileRef = B58D10C72114D21C00107ED4 /* generic_error.json */; }; + B58D10CA2114D22E00107ED4 /* new-order-note.json in Resources */ = {isa = PBXBuildFile; fileRef = B58D10C92114D22E00107ED4 /* new-order-note.json */; }; B58E5BEA20FFB3D0003C986E /* CodingUserInfoKey+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58E5BE920FFB3D0003C986E /* CodingUserInfoKey+Woo.swift */; }; B5969E1520A47F99005E9DF1 /* RemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5969E1420A47F99005E9DF1 /* RemoteTests.swift */; }; B5BB1D0C20A2050300112D92 /* DateFormatter+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB1D0B20A2050300112D92 /* DateFormatter+Woo.swift */; }; @@ -75,7 +78,6 @@ B5C6FCD420A373BB00A4F8E4 /* OrderMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C6FCD320A373BA00A4F8E4 /* OrderMapper.swift */; }; B5C6FCD620A3768900A4F8E4 /* order.json in Resources */ = {isa = PBXBuildFile; fileRef = B5C6FCD520A3768900A4F8E4 /* order.json */; }; CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */ = {isa = PBXBuildFile; fileRef = CE20179220E3EFA7005B4C18 /* broken-orders.json */; }; - CE21B3E72106811000A259D5 /* new-order-note.json in Resources */ = {isa = PBXBuildFile; fileRef = CE21B3E62106811000A259D5 /* new-order-note.json */; }; CE583A0E2109154500D73C1C /* OrderNoteMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE583A0D2109154500D73C1C /* OrderNoteMapper.swift */; }; /* End PBXBuildFile section */ @@ -150,6 +152,9 @@ B56C1EB520EA757B00D749F9 /* SiteListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListMapper.swift; sourceTree = ""; }; B56C1EB720EA76F500D749F9 /* Site.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Site.swift; sourceTree = ""; }; B56C1EB920EA7D2C00D749F9 /* sites.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = sites.json; sourceTree = ""; }; + B58D10C52114D1F100107ED4 /* order-stats.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats.json"; sourceTree = ""; }; + B58D10C72114D21C00107ED4 /* generic_error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = generic_error.json; sourceTree = ""; }; + B58D10C92114D22E00107ED4 /* new-order-note.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "new-order-note.json"; sourceTree = ""; }; B58E5BE920FFB3D0003C986E /* CodingUserInfoKey+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CodingUserInfoKey+Woo.swift"; sourceTree = ""; }; B5969E1420A47F99005E9DF1 /* RemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteTests.swift; sourceTree = ""; }; B5BB1D0B20A2050300112D92 /* DateFormatter+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Woo.swift"; sourceTree = ""; }; @@ -163,7 +168,6 @@ BD9439D9B8F2C1ED2EADAA51 /* Pods-NetworkingTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkingTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-NetworkingTests/Pods-NetworkingTests.debug.xcconfig"; sourceTree = ""; }; C8F9A8CC6F90A8C9B5EF2EE2 /* Pods-Networking.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Networking.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Networking/Pods-Networking.release.xcconfig"; sourceTree = ""; }; CE20179220E3EFA7005B4C18 /* broken-orders.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "broken-orders.json"; sourceTree = ""; }; - CE21B3E62106811000A259D5 /* new-order-note.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "new-order-note.json"; sourceTree = ""; }; CE583A0D2109154500D73C1C /* OrderNoteMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteMapper.swift; sourceTree = ""; }; F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F6CEE1CA2AD376C0C28AE9F6 /* Pods-NetworkingTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkingTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-NetworkingTests/Pods-NetworkingTests.release.xcconfig"; sourceTree = ""; }; @@ -354,18 +358,20 @@ B559EBA820A0B5B100836CD4 /* Responses */ = { isa = PBXGroup; children = ( + 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */, + 74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */, + CE20179220E3EFA7005B4C18 /* broken-orders.json */, + B58D10C72114D21C00107ED4 /* generic_error.json */, + B505F6D420BEE4E600BB1B69 /* me.json */, + B58D10C92114D22E00107ED4 /* new-order-note.json */, + 74C8F06520EEB76400B6EDC9 /* order-notes.json */, 748D424D210FB1F500CF7D1B /* order-stats-day.json */, 743FDB99210FB36900AC737F /* order-stats-month.json */, 743FDB9B210FB36900AC737F /* order-stats-week.json */, 743FDB9A210FB36900AC737F /* order-stats-year.json */, - CE21B3E62106811000A259D5 /* new-order-note.json */, - B505F6D420BEE4E600BB1B69 /* me.json */, - B559EBA920A0B5CD00836CD4 /* orders-load-all.json */, + B58D10C52114D1F100107ED4 /* order-stats.json */, B5C6FCD520A3768900A4F8E4 /* order.json */, - CE20179220E3EFA7005B4C18 /* broken-orders.json */, - 74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */, - 74C8F06520EEB76400B6EDC9 /* order-notes.json */, - 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */, + B559EBA920A0B5CD00836CD4 /* orders-load-all.json */, B56C1EB920EA7D2C00D749F9 /* sites.json */, ); path = Responses; @@ -529,8 +535,9 @@ buildActionMask = 2147483647; files = ( 74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */, + B58D10CA2114D22E00107ED4 /* new-order-note.json in Resources */, 74C8F06C20EEBD5D00B6EDC9 /* broken-order.json in Resources */, - CE21B3E72106811000A259D5 /* new-order-note.json in Resources */, + B58D10C82114D21D00107ED4 /* generic_error.json in Resources */, B505F6D520BEE4E700BB1B69 /* me.json in Resources */, B5C6FCD620A3768900A4F8E4 /* order.json in Resources */, 743FDB9C210FB36900AC737F /* order-stats-month.json in Resources */, @@ -540,6 +547,7 @@ B559EBAA20A0B5CD00836CD4 /* orders-load-all.json in Resources */, B56C1EBA20EA7D2C00D749F9 /* sites.json in Resources */, CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */, + B58D10C62114D1F200107ED4 /* order-stats.json in Resources */, 74C8F07020EEC3A800B6EDC9 /* broken-notes.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Yosemite/YosemiteTests/Resources/generic_error.json b/Networking/NetworkingTests/Responses/generic_error.json similarity index 100% rename from Yosemite/YosemiteTests/Resources/generic_error.json rename to Networking/NetworkingTests/Responses/generic_error.json diff --git a/Yosemite/YosemiteTests/Resources/order-stats.json b/Networking/NetworkingTests/Responses/order-stats.json similarity index 100% rename from Yosemite/YosemiteTests/Resources/order-stats.json rename to Networking/NetworkingTests/Responses/order-stats.json From 6853319b3611fb34a72b2fb2607d438c53ccad54 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Fri, 3 Aug 2018 17:01:22 -0500 Subject: [PATCH 071/112] Added MIContainer --- .../Networking.xcodeproj/project.pbxproj | 4 + Networking/Networking/Model/MIContainer.swift | 73 +++++++++++++++ .../Networking/Model/OrderStatsItem.swift | 91 +++++-------------- 3 files changed, 99 insertions(+), 69 deletions(-) create mode 100644 Networking/Networking/Model/MIContainer.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 9e90d3de78c..557041ca6fa 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 74C8F06C20EEBD5D00B6EDC9 /* broken-order.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */; }; 74C8F06E20EEC1E800B6EDC9 /* OrderNotesMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */; }; 74C8F07020EEC3A800B6EDC9 /* broken-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */; }; + 74D3BD142114FE6900A6E85E /* MIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D3BD132114FE6900A6E85E /* MIContainer.swift */; }; 74D522B62113607F00042831 /* OrderStatGranularity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D522B52113607F00042831 /* OrderStatGranularity.swift */; }; B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */; }; B505F6CF20BEE38B00BB1B69 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6CE20BEE38B00BB1B69 /* Account.swift */; }; @@ -114,6 +115,7 @@ 74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "broken-order.json"; sourceTree = ""; }; 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapperTests.swift; sourceTree = ""; }; 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "broken-notes.json"; sourceTree = ""; }; + 74D3BD132114FE6900A6E85E /* MIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MIContainer.swift; sourceTree = ""; }; 74D522B52113607F00042831 /* OrderStatGranularity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatGranularity.swift; sourceTree = ""; }; 753D6504FF01F09F6A33B73E /* Pods-Networking.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Networking.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Networking/Pods-Networking.debug.xcconfig"; sourceTree = ""; }; B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountMapper.swift; sourceTree = ""; }; @@ -340,6 +342,7 @@ B557DA1B20979E6D005962F4 /* Model */ = { isa = PBXGroup; children = ( + 74D3BD132114FE6900A6E85E /* MIContainer.swift */, B505F6CE20BEE38B00BB1B69 /* Account.swift */, B5BB1D0F20A237FB00112D92 /* Address.swift */, B557DA1C20979E7D005962F4 /* Order.swift */, @@ -647,6 +650,7 @@ B5C6FCCF20A3592900A4F8E4 /* OrderItem.swift in Sources */, 748D424A210F92EA00CF7D1B /* OrderStatsItem.swift in Sources */, B505F6EC20BEFDC200BB1B69 /* Loader.swift in Sources */, + 74D3BD142114FE6900A6E85E /* MIContainer.swift in Sources */, B5BB1D1220A255EC00112D92 /* OrderStatus.swift in Sources */, 74C8F06820EEB7BD00B6EDC9 /* OrderNotesMapper.swift in Sources */, B5BB1D1020A237FB00112D92 /* Address.swift in Sources */, diff --git a/Networking/Networking/Model/MIContainer.swift b/Networking/Networking/Model/MIContainer.swift new file mode 100644 index 00000000000..a6b89125ab0 --- /dev/null +++ b/Networking/Networking/Model/MIContainer.swift @@ -0,0 +1,73 @@ +import Foundation + +/** + This is a generic container data container used to hold an (unkeyed) data array + of which its elements can be multiple tupes. Additionally, the field names + are stored in a separate array where the specific index of a field name element + corresponds to its matching element in the `data` array. + + Why do we have this insanity? To deal with JSON payloads that can look like this: + ```` + { + "fields": [ + "period", + "orders", + "total_sales", + "total_tax", + "total_shipping", + "currency", + "gross_sales" + ], + "data": [ + [ "2018-06-01", 2, 14.24, 9.98, 0.28, "USD", 14.120000000000001 ], + [ 2018, 2, 123123, 9.98, 0.0, "USD", 0] + ] + ... + } + ```` + + A few accessor methods are also provided that will ensure the correct type is returned for a given field. This container + will be especially useful when dealing with data returned from the stats endpoints. 😃 +*/ +public struct MIContainer { + let data: [Any] + let fieldNames: [String] + + func fetchStringValue(for field: T) -> String where T.RawValue == String { + guard let index = fieldNames.index(of: field.rawValue) else { + return "" + } + + // 😢 As crazy as it sounds, sometimes the server occasionally returns + // String values as Ints — we need to account for this. + if self.data[index] is Int { + if let intValue = self.data[index] as? Int { + return String(intValue) + } + return "" + } else { + return self.data[index] as? String ?? "" + } + } + + func fetchIntValue(for field: T) -> Int where T.RawValue == String { + guard let index = fieldNames.index(of: field.rawValue), + let returnValue = self.data[index] as? Int else { + return 0 + } + return returnValue + } + + func fetchDoubleValue(for field: T) -> Double where T.RawValue == String { + guard let index = fieldNames.index(of: field.rawValue) else { + return 0 + } + + if self.data[index] is Int { + let intValue = self.data[index] as? Int ?? 0 + return Double(intValue) + } else { + return self.data[index] as? Double ?? 0 + } + } +} diff --git a/Networking/Networking/Model/OrderStatsItem.swift b/Networking/Networking/Model/OrderStatsItem.swift index 57539873402..a14cdb0a11f 100644 --- a/Networking/Networking/Model/OrderStatsItem.swift +++ b/Networking/Networking/Model/OrderStatsItem.swift @@ -4,89 +4,87 @@ import Foundation /// Represents an single order stat for a specific period. /// public struct OrderStatsItem { - public let data: [Any] - public let fieldNames: [String] - + public let payload: MIContainer /// OrderStatsItem struct initializer. /// public init(fieldNames: [String], rawData: [AnyCodable]) { - self.fieldNames = fieldNames - self.data = rawData.map({ $0.value }) + self.payload = MIContainer(data: rawData.map({ $0.value }), + fieldNames: fieldNames) } // MARK: Computed Properties public var period: String { - return fetchStringValue(for: .period) + return payload.fetchStringValue(for: FieldNames.period) } public var orders: Int { - return fetchIntValue(for: .orders) + return payload.fetchIntValue(for: FieldNames.orders) } public var products: Int { - return fetchIntValue(for: .products) + return payload.fetchIntValue(for: FieldNames.products) } public var coupons: Int { - return fetchIntValue(for: .coupons) + return payload.fetchIntValue(for: FieldNames.coupons) } public var couponDiscount: Double { - return fetchDoubleValue(for: .couponDiscount) + return payload.fetchDoubleValue(for: FieldNames.couponDiscount) } public var totalSales: Double { - return fetchDoubleValue(for: .totalSales) + return payload.fetchDoubleValue(for: FieldNames.totalSales) } public var totalTax: Double { - return fetchDoubleValue(for: .totalTax) + return payload.fetchDoubleValue(for: FieldNames.totalTax) } public var totalShipping: Double { - return fetchDoubleValue(for: .totalShipping) + return payload.fetchDoubleValue(for: FieldNames.totalShipping) } public var totalShippingTax: Double { - return fetchDoubleValue(for: .totalShippingTax) + return payload.fetchDoubleValue(for: FieldNames.totalShippingTax) } public var totalRefund: Double { - return fetchDoubleValue(for: .totalRefund) + return payload.fetchDoubleValue(for: FieldNames.totalRefund) } public var totalTaxRefund: Double { - return fetchDoubleValue(for: .totalTaxRefund) + return payload.fetchDoubleValue(for: FieldNames.totalTaxRefund) } public var totalShippingRefund: Double { - return fetchDoubleValue(for: .totalShippingRefund) + return payload.fetchDoubleValue(for: FieldNames.totalShippingRefund) } public var totalShippingTaxRefund: Double { - return fetchDoubleValue(for: .totalShippingTaxRefund) + return payload.fetchDoubleValue(for: FieldNames.totalShippingTaxRefund) } public var currency: String { - return fetchStringValue(for: .currency) + return payload.fetchStringValue(for: FieldNames.currency) } public var grossSales: Double { - return fetchDoubleValue(for: .grossSales) + return payload.fetchDoubleValue(for: FieldNames.grossSales) } public var netSales: Double { - return fetchDoubleValue(for: .netSales) + return payload.fetchDoubleValue(for: FieldNames.netSales) } public var avgOrderValue: Double { - return fetchDoubleValue(for: .avgOrderValue) + return payload.fetchDoubleValue(for: FieldNames.avgOrderValue) } public var avgProductsPerOrder: Double { - return fetchDoubleValue(for: .avgProductsPerOrder) + return payload.fetchDoubleValue(for: FieldNames.avgProductsPerOrder) } } @@ -95,8 +93,7 @@ public struct OrderStatsItem { // extension OrderStatsItem: Comparable { public static func == (lhs: OrderStatsItem, rhs: OrderStatsItem) -> Bool { - return lhs.fieldNames == rhs.fieldNames && - lhs.period == rhs.period && + return lhs.period == rhs.period && lhs.orders == rhs.orders && lhs.products == rhs.products && lhs.coupons == rhs.coupons && @@ -123,50 +120,6 @@ extension OrderStatsItem: Comparable { } } -// MARK: - Private Helpers -// - -private extension OrderStatsItem { - func fetchStringValue(for field: FieldNames) -> String { - guard let index = fieldNames.index(of: field.rawValue) else { - return "" - } - - // 😢 As crazy as it sounds, sometimes the server occasionally returns - // String values as Ints — we need to account for this. - if self.data[index] is Int { - if let intValue = self.data[index] as? Int { - return String(intValue) - } - return "" - } else { - return self.data[index] as? String ?? "" - } - } - - func fetchIntValue(for field: FieldNames) -> Int { - guard let index = fieldNames.index(of: field.rawValue), - let returnValue = self.data[index] as? Int else { - return 0 - } - return returnValue - } - - func fetchDoubleValue(for field: FieldNames) -> Double { - guard let index = fieldNames.index(of: field.rawValue) else { - return 0 - } - - if self.data[index] is Int { - let intValue = self.data[index] as? Int ?? 0 - return Double(intValue) - } else { - return self.data[index] as? Double ?? 0 - } - } -} - - // MARK: - Constants! // private extension OrderStatsItem { From 190193b5a196731bd54096758f812698f5739e35 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Fri, 3 Aug 2018 17:12:37 -0500 Subject: [PATCH 072/112] Fixed typo --- Networking/Networking/Model/MIContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Networking/Networking/Model/MIContainer.swift b/Networking/Networking/Model/MIContainer.swift index a6b89125ab0..d061001141a 100644 --- a/Networking/Networking/Model/MIContainer.swift +++ b/Networking/Networking/Model/MIContainer.swift @@ -2,7 +2,7 @@ import Foundation /** This is a generic container data container used to hold an (unkeyed) data array - of which its elements can be multiple tupes. Additionally, the field names + of which its elements can be multiple types. Additionally, the field names are stored in a separate array where the specific index of a field name element corresponds to its matching element in the `data` array. From 7ea2a43daee3eae4b01240e72b7aef3baeb543a1 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 6 Aug 2018 10:31:06 -0500 Subject: [PATCH 073/112] Added SiteVisitStats remote + models --- .../Networking.xcodeproj/project.pbxproj | 50 ++++++- .../Mapper/SiteVisitStatsMapper.swift | 14 ++ .../Model/{ => Stats}/MIContainer.swift | 0 .../{ => Stats}/OrderStatGranularity.swift | 0 .../Model/{ => Stats}/OrderStats.swift | 0 .../Model/{ => Stats}/OrderStatsItem.swift | 0 .../Model/Stats/SiteVisitStats.swift | 66 ++++++++++ .../Model/Stats/SiteVisitStatsItem.swift | 83 ++++++++++++ .../Networking/Remote/OrderStatsRemote.swift | 2 +- .../Remote/SiteVisitStatsRemote.swift | 43 ++++++ .../Responses/site-visits-day.json | 123 ++++++++++++++++++ .../Responses/site-visits-month.json | 123 ++++++++++++++++++ .../Responses/site-visits-week.json | 123 ++++++++++++++++++ .../Responses/site-visits-year.json | 60 +++++++++ 14 files changed, 681 insertions(+), 6 deletions(-) create mode 100644 Networking/Networking/Mapper/SiteVisitStatsMapper.swift rename Networking/Networking/Model/{ => Stats}/MIContainer.swift (100%) rename Networking/Networking/Model/{ => Stats}/OrderStatGranularity.swift (100%) rename Networking/Networking/Model/{ => Stats}/OrderStats.swift (100%) rename Networking/Networking/Model/{ => Stats}/OrderStatsItem.swift (100%) create mode 100644 Networking/Networking/Model/Stats/SiteVisitStats.swift create mode 100644 Networking/Networking/Model/Stats/SiteVisitStatsItem.swift create mode 100644 Networking/Networking/Remote/SiteVisitStatsRemote.swift create mode 100644 Networking/NetworkingTests/Responses/site-visits-day.json create mode 100644 Networking/NetworkingTests/Responses/site-visits-month.json create mode 100644 Networking/NetworkingTests/Responses/site-visits-week.json create mode 100644 Networking/NetworkingTests/Responses/site-visits-year.json diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 557041ca6fa..46aed7bc0f2 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -22,6 +22,14 @@ 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */; }; 748D424E210FB1F500CF7D1B /* order-stats-day.json in Resources */ = {isa = PBXBuildFile; fileRef = 748D424D210FB1F500CF7D1B /* order-stats-day.json */; }; 74A1196C2110F4BB00E1E5F0 /* OrderStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */; }; + 74A1D263211898F000931DFA /* site-visits-day.json in Resources */ = {isa = PBXBuildFile; fileRef = 74A1D25F211898F000931DFA /* site-visits-day.json */; }; + 74A1D264211898F000931DFA /* site-visits-week.json in Resources */ = {isa = PBXBuildFile; fileRef = 74A1D260211898F000931DFA /* site-visits-week.json */; }; + 74A1D265211898F000931DFA /* site-visits-month.json in Resources */ = {isa = PBXBuildFile; fileRef = 74A1D261211898F000931DFA /* site-visits-month.json */; }; + 74A1D266211898F000931DFA /* site-visits-year.json in Resources */ = {isa = PBXBuildFile; fileRef = 74A1D262211898F000931DFA /* site-visits-year.json */; }; + 74A1D26821189A7100931DFA /* SiteVisitStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1D26721189A7000931DFA /* SiteVisitStats.swift */; }; + 74A1D26B21189B8100931DFA /* SiteVisitStatsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1D26A21189B8100931DFA /* SiteVisitStatsItem.swift */; }; + 74A1D26D21189DFF00931DFA /* SiteVisitStatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1D26C21189DFE00931DFA /* SiteVisitStatsMapper.swift */; }; + 74A1D26F21189EA100931DFA /* SiteVisitStatsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A1D26E21189EA000931DFA /* SiteVisitStatsRemote.swift */; }; 74AE244D2113704C00CA8C54 /* OrderStatsRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AE244C2113704C00CA8C54 /* OrderStatsRemoteTests.swift */; }; 74C8F06420EEB44800B6EDC9 /* OrderNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */; }; 74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06520EEB76400B6EDC9 /* order-notes.json */; }; @@ -107,6 +115,14 @@ 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsMapper.swift; sourceTree = ""; }; 748D424D210FB1F500CF7D1B /* order-stats-day.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-day.json"; sourceTree = ""; }; 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStats.swift; sourceTree = ""; }; + 74A1D25F211898F000931DFA /* site-visits-day.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-visits-day.json"; sourceTree = ""; }; + 74A1D260211898F000931DFA /* site-visits-week.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-visits-week.json"; sourceTree = ""; }; + 74A1D261211898F000931DFA /* site-visits-month.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-visits-month.json"; sourceTree = ""; }; + 74A1D262211898F000931DFA /* site-visits-year.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-visits-year.json"; sourceTree = ""; }; + 74A1D26721189A7000931DFA /* SiteVisitStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStats.swift; sourceTree = ""; }; + 74A1D26A21189B8100931DFA /* SiteVisitStatsItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsItem.swift; sourceTree = ""; }; + 74A1D26C21189DFE00931DFA /* SiteVisitStatsMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsMapper.swift; sourceTree = ""; }; + 74A1D26E21189EA000931DFA /* SiteVisitStatsRemote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsRemote.swift; sourceTree = ""; }; 74AE244C2113704C00CA8C54 /* OrderStatsRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsRemoteTests.swift; sourceTree = ""; }; 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNote.swift; sourceTree = ""; }; 74C8F06520EEB76400B6EDC9 /* order-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-notes.json"; sourceTree = ""; }; @@ -205,6 +221,19 @@ name = Frameworks; sourceTree = ""; }; + 74A1D26921189AC100931DFA /* Stats */ = { + isa = PBXGroup; + children = ( + 74D3BD132114FE6900A6E85E /* MIContainer.swift */, + 74D522B52113607F00042831 /* OrderStatGranularity.swift */, + 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */, + 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */, + 74A1D26721189A7000931DFA /* SiteVisitStats.swift */, + 74A1D26A21189B8100931DFA /* SiteVisitStatsItem.swift */, + ); + path = Stats; + sourceTree = ""; + }; B505F6EB20BEFD9100BB1B69 /* Tools */ = { isa = PBXGroup; children = ( @@ -314,6 +343,7 @@ B505F6D020BEE39600BB1B69 /* AccountRemote.swift */, B557DA0120975500005962F4 /* OrdersRemote.swift */, 748D4247210F89ED00CF7D1B /* OrderStatsRemote.swift */, + 74A1D26E21189EA000931DFA /* SiteVisitStatsRemote.swift */, ); path = Remote; sourceTree = ""; @@ -342,7 +372,7 @@ B557DA1B20979E6D005962F4 /* Model */ = { isa = PBXGroup; children = ( - 74D3BD132114FE6900A6E85E /* MIContainer.swift */, + 74A1D26921189AC100931DFA /* Stats */, B505F6CE20BEE38B00BB1B69 /* Account.swift */, B5BB1D0F20A237FB00112D92 /* Address.swift */, B557DA1C20979E7D005962F4 /* Order.swift */, @@ -350,9 +380,6 @@ B5C6FCCE20A3592900A4F8E4 /* OrderItem.swift */, 74C8F06320EEB44800B6EDC9 /* OrderNote.swift */, B5BB1D1120A255EC00112D92 /* OrderStatus.swift */, - 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */, - 74D522B52113607F00042831 /* OrderStatGranularity.swift */, - 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */, B56C1EB720EA76F500D749F9 /* Site.swift */, ); path = Model; @@ -369,10 +396,14 @@ B58D10C92114D22E00107ED4 /* new-order-note.json */, 74C8F06520EEB76400B6EDC9 /* order-notes.json */, 748D424D210FB1F500CF7D1B /* order-stats-day.json */, - 743FDB99210FB36900AC737F /* order-stats-month.json */, 743FDB9B210FB36900AC737F /* order-stats-week.json */, + 743FDB99210FB36900AC737F /* order-stats-month.json */, 743FDB9A210FB36900AC737F /* order-stats-year.json */, B58D10C52114D1F100107ED4 /* order-stats.json */, + 74A1D25F211898F000931DFA /* site-visits-day.json */, + 74A1D260211898F000931DFA /* site-visits-week.json */, + 74A1D261211898F000931DFA /* site-visits-month.json */, + 74A1D262211898F000931DFA /* site-visits-year.json */, B5C6FCD520A3768900A4F8E4 /* order.json */, B559EBA920A0B5CD00836CD4 /* orders-load-all.json */, B56C1EB920EA7D2C00D749F9 /* sites.json */, @@ -391,6 +422,7 @@ 74C8F06720EEB7BC00B6EDC9 /* OrderNotesMapper.swift */, CE583A0D2109154500D73C1C /* OrderNoteMapper.swift */, 748D424B210FA34400CF7D1B /* OrderStatsMapper.swift */, + 74A1D26C21189DFE00931DFA /* SiteVisitStatsMapper.swift */, ); path = Mapper; sourceTree = ""; @@ -543,13 +575,17 @@ B58D10C82114D21D00107ED4 /* generic_error.json in Resources */, B505F6D520BEE4E700BB1B69 /* me.json in Resources */, B5C6FCD620A3768900A4F8E4 /* order.json in Resources */, + 74A1D266211898F000931DFA /* site-visits-year.json in Resources */, 743FDB9C210FB36900AC737F /* order-stats-month.json in Resources */, 748D424E210FB1F500CF7D1B /* order-stats-day.json in Resources */, 743FDB9E210FB36900AC737F /* order-stats-week.json in Resources */, + 74A1D264211898F000931DFA /* site-visits-week.json in Resources */, 743FDB9D210FB36900AC737F /* order-stats-year.json in Resources */, B559EBAA20A0B5CD00836CD4 /* orders-load-all.json in Resources */, + 74A1D263211898F000931DFA /* site-visits-day.json in Resources */, B56C1EBA20EA7D2C00D749F9 /* sites.json in Resources */, CE20179320E3EFA7005B4C18 /* broken-orders.json in Resources */, + 74A1D265211898F000931DFA /* site-visits-month.json in Resources */, B58D10C62114D1F200107ED4 /* order-stats.json in Resources */, 74C8F07020EEC3A800B6EDC9 /* broken-notes.json in Resources */, ); @@ -637,11 +673,14 @@ B56C1EB820EA76F500D749F9 /* Site.swift in Sources */, B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */, B557DA0D20975DB1005962F4 /* WordPressAPIVersion.swift in Sources */, + 74A1D26F21189EA100931DFA /* SiteVisitStatsRemote.swift in Sources */, B557DA1D20979E7D005962F4 /* Order.swift in Sources */, + 74A1D26821189A7100931DFA /* SiteVisitStats.swift in Sources */, B557DA0320975500005962F4 /* Remote.swift in Sources */, 748D424C210FA34400CF7D1B /* OrderStatsMapper.swift in Sources */, 7452387321124B7700A973CD /* AnyCodable.swift in Sources */, B567AF2920A0FA1E00AB6C62 /* Mapper.swift in Sources */, + 74A1D26D21189DFF00931DFA /* SiteVisitStatsMapper.swift in Sources */, 74D522B62113607F00042831 /* OrderStatGranularity.swift in Sources */, B518662220A097C200037A38 /* Network.swift in Sources */, B518662420A099BF00037A38 /* AlamofireNetwork.swift in Sources */, @@ -649,6 +688,7 @@ CE583A0E2109154500D73C1C /* OrderNoteMapper.swift in Sources */, B5C6FCCF20A3592900A4F8E4 /* OrderItem.swift in Sources */, 748D424A210F92EA00CF7D1B /* OrderStatsItem.swift in Sources */, + 74A1D26B21189B8100931DFA /* SiteVisitStatsItem.swift in Sources */, B505F6EC20BEFDC200BB1B69 /* Loader.swift in Sources */, 74D3BD142114FE6900A6E85E /* MIContainer.swift in Sources */, B5BB1D1220A255EC00112D92 /* OrderStatus.swift in Sources */, diff --git a/Networking/Networking/Mapper/SiteVisitStatsMapper.swift b/Networking/Networking/Mapper/SiteVisitStatsMapper.swift new file mode 100644 index 00000000000..719264af5da --- /dev/null +++ b/Networking/Networking/Mapper/SiteVisitStatsMapper.swift @@ -0,0 +1,14 @@ +import Foundation + + +/// Mapper: SiteVisitStats +/// +class SiteVisitStatsMapper: Mapper { + + /// (Attempts) to convert a dictionary into an SiteVisitStats entity. + /// + func map(response: Data) throws -> SiteVisitStats { + let decoder = JSONDecoder() + return try decoder.decode(SiteVisitStats.self, from: response) + } +} diff --git a/Networking/Networking/Model/MIContainer.swift b/Networking/Networking/Model/Stats/MIContainer.swift similarity index 100% rename from Networking/Networking/Model/MIContainer.swift rename to Networking/Networking/Model/Stats/MIContainer.swift diff --git a/Networking/Networking/Model/OrderStatGranularity.swift b/Networking/Networking/Model/Stats/OrderStatGranularity.swift similarity index 100% rename from Networking/Networking/Model/OrderStatGranularity.swift rename to Networking/Networking/Model/Stats/OrderStatGranularity.swift diff --git a/Networking/Networking/Model/OrderStats.swift b/Networking/Networking/Model/Stats/OrderStats.swift similarity index 100% rename from Networking/Networking/Model/OrderStats.swift rename to Networking/Networking/Model/Stats/OrderStats.swift diff --git a/Networking/Networking/Model/OrderStatsItem.swift b/Networking/Networking/Model/Stats/OrderStatsItem.swift similarity index 100% rename from Networking/Networking/Model/OrderStatsItem.swift rename to Networking/Networking/Model/Stats/OrderStatsItem.swift diff --git a/Networking/Networking/Model/Stats/SiteVisitStats.swift b/Networking/Networking/Model/Stats/SiteVisitStats.swift new file mode 100644 index 00000000000..cbfa8178978 --- /dev/null +++ b/Networking/Networking/Model/Stats/SiteVisitStats.swift @@ -0,0 +1,66 @@ +import Foundation + + +/// Represents site visit stats over a specific period. +/// +public struct SiteVisitStats: Decodable { + public let date: String + public let granularity: OrderStatGranularity + public let fields: [String] + public let items: [SiteVisitStatsItem]? + + /// The public initializer for order stats. + /// + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let date = try container.decode(String.self, forKey: .date) + let granularity = try container.decode(OrderStatGranularity.self, forKey: .unit) + + let fields = try container.decode([String].self, forKey: .fields) + let rawData: [[AnyCodable]] = try container.decode([[AnyCodable]].self, forKey: .data) + + let items = rawData.map({ SiteVisitStatsItem(fieldNames: fields, rawData: $0) }) + + self.init(date: date, granularity: granularity, fields: fields, items: items) + } + + + /// OrderStats struct initializer. + /// + public init(date: String, granularity: OrderStatGranularity, fields: [String], items: [SiteVisitStatsItem]?) { + self.date = date + self.granularity = granularity + self.fields = fields + self.items = items + } +} + + +/// Defines all of the SiteVisitStats CodingKeys. +/// +private extension SiteVisitStats { + + enum CodingKeys: String, CodingKey { + case date = "date" + case unit = "unit" + case fields = "fields" + case data = "data" + } +} + + +// MARK: - Comparable Conformance +// +extension SiteVisitStats: Comparable { + public static func == (lhs: SiteVisitStats, rhs: SiteVisitStats) -> Bool { + return lhs.date == rhs.date && + lhs.granularity == rhs.granularity && + lhs.fields == rhs.fields && + lhs.items == rhs.items + } + + public static func < (lhs: SiteVisitStats, rhs: SiteVisitStats) -> Bool { + return lhs.date < rhs.date + } +} diff --git a/Networking/Networking/Model/Stats/SiteVisitStatsItem.swift b/Networking/Networking/Model/Stats/SiteVisitStatsItem.swift new file mode 100644 index 00000000000..34be36d7429 --- /dev/null +++ b/Networking/Networking/Model/Stats/SiteVisitStatsItem.swift @@ -0,0 +1,83 @@ +import Foundation + + +/// Represents an single site visit stat for a specific period. +/// +public struct SiteVisitStatsItem { + public let payload: MIContainer + + /// SiteVisitStatsItem struct initializer. + /// + public init(fieldNames: [String], rawData: [AnyCodable]) { + self.payload = MIContainer(data: rawData.map({ $0.value }), + fieldNames: fieldNames) + } + + // MARK: Computed Properties + + public var period: String { + return payload.fetchStringValue(for: FieldNames.period) + } + + public var views: Int { + return payload.fetchIntValue(for: FieldNames.views) + } + + public var visitors: Int { + return payload.fetchIntValue(for: FieldNames.visitors) + } + + public var likes: Int { + return payload.fetchIntValue(for: FieldNames.likes) + } + + public var reblogs: Int { + return payload.fetchIntValue(for: FieldNames.reblogs) + } + + public var comments: Int { + return payload.fetchIntValue(for: FieldNames.comments) + } + + public var posts: Int { + return payload.fetchIntValue(for: FieldNames.posts) + } +} + + +// MARK: - Comparable Conformance +// +extension SiteVisitStatsItem: Comparable { + public static func == (lhs: SiteVisitStatsItem, rhs: SiteVisitStatsItem) -> Bool { + return lhs.period == rhs.period && + lhs.views == rhs.views && + lhs.visitors == rhs.visitors && + lhs.likes == rhs.likes && + lhs.reblogs == rhs.reblogs && + lhs.comments == rhs.comments && + lhs.posts == rhs.posts + } + + public static func < (lhs: SiteVisitStatsItem, rhs: SiteVisitStatsItem) -> Bool { + return lhs.period < rhs.period || + (lhs.period == rhs.period && lhs.views < rhs.views) || + (lhs.period == rhs.period && lhs.views == rhs.views && lhs.likes < rhs.likes) + } +} + +// MARK: - Constants! +// +private extension SiteVisitStatsItem { + + /// Defines all of the possbile fields for a SiteVisitStatsItem. + /// + enum FieldNames: String { + case period = "period" + case views = "views" + case visitors = "visitors" + case likes = "likes" + case reblogs = "reblogs" + case comments = "comments" + case posts = "posts" + } +} diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift index 23afaf719ab..6fbd6e91ae1 100644 --- a/Networking/Networking/Remote/OrderStatsRemote.swift +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -6,7 +6,7 @@ import Alamofire /// public class OrderStatsRemote: Remote { - /// Fetch the order stats for a given site up to the current day, month, or year (depending on the given granularity of the `unit` parameter). + /// Fetch the order stats for a given site up to the current day, week, month, or year (depending on the given granularity of the `unit` parameter). /// /// - Parameters: /// - siteID: The site ID diff --git a/Networking/Networking/Remote/SiteVisitStatsRemote.swift b/Networking/Networking/Remote/SiteVisitStatsRemote.swift new file mode 100644 index 00000000000..ac316999b5a --- /dev/null +++ b/Networking/Networking/Remote/SiteVisitStatsRemote.swift @@ -0,0 +1,43 @@ +import Foundation +import Alamofire + + +/// SiteVisitStats: Remote Endpoints +/// +public class SiteVisitStatsRemote: Remote { + + /// Fetch the visitor stats for a given site up to the current day, week, month, or year (depending on the given granularity of the `unit` parameter). + /// + /// - Parameters: + /// - siteID: The site ID + /// - unit: Defines the granularity of the stats we are fetching (one of 'day', 'week', 'month', or 'year') + /// - latestDateToInclude: The latest date to include in the results. + /// - quantity: How many `unit`s to fetch + /// - completion: Closure to be executed upon completion. + /// + public func loadSiteVisitorStats(for siteID: Int, unit: OrderStatGranularity, latestDateToInclude: Date, quantity: Int, completion: @escaping (SiteVisitStats?, Error?) -> Void) { + let path = "\(Constants.sitesPath)/\(siteID)/\(Constants.siteVisitStatsPath)/" + let parameters = [ParameterKeys.unit: unit.rawValue, + ParameterKeys.date: DateFormatter.Stats.statsDayFormatter.string(from: latestDateToInclude), + ParameterKeys.quantity: String(quantity)] + let request = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .get, path: path, parameters: parameters) + let mapper = SiteVisitStatsMapper() + enqueue(request, mapper: mapper, completion: completion) + } +} + + +// MARK: - Constants! +// +private extension SiteVisitStatsRemote { + enum Constants { + static let sitesPath: String = "sites" + static let siteVisitStatsPath: String = "stats/visits" + } + + enum ParameterKeys { + static let unit: String = "unit" + static let date: String = "date" + static let quantity: String = "quantity" + } +} diff --git a/Networking/NetworkingTests/Responses/site-visits-day.json b/Networking/NetworkingTests/Responses/site-visits-day.json new file mode 100644 index 00000000000..d5d10ac5786 --- /dev/null +++ b/Networking/NetworkingTests/Responses/site-visits-day.json @@ -0,0 +1,123 @@ +{ + "date": "2018-08-06", + "unit": "day", + "fields": [ + "period", + "views", + "visitors", + "likes", + "reblogs", + "comments", + "posts" + ], + "data": [ + [ + "2018-07-26", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-07-27", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-07-28", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-07-29", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-07-30", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-07-31", + 1, + 1, + 0, + 0, + 0, + 0 + ], + [ + "2018-08-01", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-08-02", + 1, + 1, + 0, + 0, + 0, + 0 + ], + [ + "2018-08-03", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-08-04", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-08-05", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018-08-06", + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] +} diff --git a/Networking/NetworkingTests/Responses/site-visits-month.json b/Networking/NetworkingTests/Responses/site-visits-month.json new file mode 100644 index 00000000000..766e664409b --- /dev/null +++ b/Networking/NetworkingTests/Responses/site-visits-month.json @@ -0,0 +1,123 @@ +{ + "date": "2018-08-06", + "unit": "month", + "fields": [ + "period", + "views", + "visitors", + "likes", + "reblogs", + "comments", + "posts" + ], + "data": [ + [ + "2017-09-01", + 36, + 24, + 1, + 0, + 3, + 2 + ], + [ + "2017-10-01", + 28, + 7, + 0, + 0, + 0, + 1 + ], + [ + "2017-11-01", + 41, + 12, + 1, + 0, + 2, + 0 + ], + [ + "2017-12-01", + 5, + 3, + 0, + 0, + 0, + 0 + ], + [ + "2018-01-01", + 9, + 3, + 0, + 0, + 0, + 0 + ], + [ + "2018-02-01", + 11, + 5, + 0, + 0, + 0, + 0 + ], + [ + "2018-03-01", + 20, + 6, + 0, + 0, + 0, + 0 + ], + [ + "2018-04-01", + 24, + 13, + 0, + 0, + 0, + 0 + ], + [ + "2018-05-01", + 32, + 7, + 0, + 0, + 0, + 0 + ], + [ + "2018-06-01", + 5, + 5, + 0, + 0, + 0, + 0 + ], + [ + "2018-07-01", + 16, + 6, + 9, + 0, + 0, + 0 + ], + [ + "2018-08-01", + 1, + 1, + 0, + 0, + 0, + 0 + ] + ] +} diff --git a/Networking/NetworkingTests/Responses/site-visits-week.json b/Networking/NetworkingTests/Responses/site-visits-week.json new file mode 100644 index 00000000000..a0646f26aef --- /dev/null +++ b/Networking/NetworkingTests/Responses/site-visits-week.json @@ -0,0 +1,123 @@ +{ + "date": "2018-08-06", + "unit": "week", + "fields": [ + "period", + "views", + "visitors", + "likes", + "reblogs", + "comments", + "posts" + ], + "data": [ + [ + "2018W05W21", + 5, + 4, + 0, + 0, + 0, + 0 + ], + [ + "2018W05W28", + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + "2018W06W04", + 1, + 1, + 0, + 0, + 0, + 0 + ], + [ + "2018W06W11", + 1, + 1, + 0, + 0, + 0, + 0 + ], + [ + "2018W06W18", + 2, + 2, + 0, + 0, + 0, + 0 + ], + [ + "2018W06W25", + 1, + 1, + 0, + 0, + 0, + 0 + ], + [ + "2018W07W02", + 2, + 2, + 0, + 0, + 0, + 0 + ], + [ + "2018W07W09", + 3, + 2, + 0, + 0, + 0, + 0 + ], + [ + "2018W07W16", + 4, + 2, + 9, + 0, + 0, + 0 + ], + [ + "2018W07W23", + 6, + 3, + 0, + 0, + 0, + 0 + ], + [ + "2018W07W30", + 2, + 2, + 0, + 0, + 0, + 0 + ], + [ + "2018W08W06", + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] +} diff --git a/Networking/NetworkingTests/Responses/site-visits-year.json b/Networking/NetworkingTests/Responses/site-visits-year.json new file mode 100644 index 00000000000..063fa54885f --- /dev/null +++ b/Networking/NetworkingTests/Responses/site-visits-year.json @@ -0,0 +1,60 @@ +{ + "date": "2018-08-06", + "unit": "year", + "fields": [ + "period", + "views", + "visitors", + "likes", + "reblogs", + "comments", + "posts" + ], + "data": [ + [ + "2014-01-01", + 12821, + 1135, + 1094, + 0, + 1611, + 597 + ], + [ + "2015-01-01", + 14808, + 1629, + 1492, + 0, + 1268, + 571 + ], + [ + "2016-01-01", + 2189, + 372, + 96, + 0, + 78, + 36 + ], + [ + "2017-01-01", + 348, + 144, + 3, + 0, + 5, + 4 + ], + [ + "2018-01-01", + 118, + 46, + 9, + 0, + 0, + 0 + ] + ] +} From 403f2cf50d5535ad0287553d6d0a1c20c957822f Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 6 Aug 2018 11:43:06 -0500 Subject: [PATCH 074/112] Added SiteVisitStats to Yosemite model --- Yosemite/Yosemite/Model/Model.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Yosemite/Yosemite/Model/Model.swift b/Yosemite/Yosemite/Model/Model.swift index 7670231d2ca..d233dc46607 100644 --- a/Yosemite/Yosemite/Model/Model.swift +++ b/Yosemite/Yosemite/Model/Model.swift @@ -18,6 +18,8 @@ public typealias OrderStats = Networking.OrderStats public typealias OrderStatsItem = Networking.OrderStatsItem public typealias OrderStatGranularity = Networking.OrderStatGranularity public typealias Site = Networking.Site +public typealias SiteVisitStats = Networking.SiteVisitStats +public typealias SiteVisitStatsItem = Networking.SiteVisitStatsItem // MARK: - Exported Storage Symbols From 627b70ddf22ce7bb6eeff7c117516749ffeb21e9 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 6 Aug 2018 12:09:36 -0500 Subject: [PATCH 075/112] Added SiteVisitStatsMapperTests --- .../Networking.xcodeproj/project.pbxproj | 4 + .../Mapper/SiteVisitStatsMapperTests.swift | 178 ++++++++++++++++++ .../Responses/site-visits-day.json | 24 +-- .../Responses/site-visits-week.json | 12 +- 4 files changed, 200 insertions(+), 18 deletions(-) create mode 100644 Networking/NetworkingTests/Mapper/SiteVisitStatsMapperTests.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 46aed7bc0f2..0fc41ec2fb8 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 21DB5B99C4107CF69C0A57EC /* Pods_NetworkingTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */; }; 6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; }; + 74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */; }; 741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */; }; 743FDB9C210FB36900AC737F /* order-stats-month.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB99210FB36900AC737F /* order-stats-month.json */; }; 743FDB9D210FB36900AC737F /* order-stats-year.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9A210FB36900AC737F /* order-stats-year.json */; }; @@ -102,6 +103,7 @@ /* Begin PBXFileReference section */ 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NetworkingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsMapperTests.swift; sourceTree = ""; }; 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponLine.swift; sourceTree = ""; }; 743FDB99210FB36900AC737F /* order-stats-month.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-month.json"; sourceTree = ""; }; 743FDB9A210FB36900AC737F /* order-stats-year.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-year.json"; sourceTree = ""; }; @@ -452,6 +454,7 @@ 74C8F06920EEBC8C00B6EDC9 /* OrderMapperTests.swift */, 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */, 743FDB9F210FB3E500AC737F /* OrderStatsMapperTests.swift */, + 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */, ); path = Mapper; sourceTree = ""; @@ -720,6 +723,7 @@ B567AF2F20A0FB8F00AB6C62 /* AuthenticatedRequestTests.swift in Sources */, B5C6FCCD20A34B8300A4F8E4 /* OrderListMapperTests.swift in Sources */, B518663520A0A2E800037A38 /* Constants.swift in Sources */, + 74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */, B567AF3020A0FB8F00AB6C62 /* DotcomRequestTests.swift in Sources */, 743FDBA0210FB3E500AC737F /* OrderStatsMapperTests.swift in Sources */, ); diff --git a/Networking/NetworkingTests/Mapper/SiteVisitStatsMapperTests.swift b/Networking/NetworkingTests/Mapper/SiteVisitStatsMapperTests.swift new file mode 100644 index 00000000000..e9f381397ec --- /dev/null +++ b/Networking/NetworkingTests/Mapper/SiteVisitStatsMapperTests.swift @@ -0,0 +1,178 @@ +import XCTest +@testable import Networking + + +/// SiteVisitStatsMapper Unit Tests +/// +class SiteVisitStatsMapperTests: XCTestCase { + + /// Verifies that all of the day unit SiteVisitStats fields are parsed correctly. + /// + func testDayUnitStatFieldsAreProperlyParsed() { + guard let dayStats = mapSiteVisitStatsWithDayUnitResponse() else { + XCTFail() + return + } + + XCTAssertEqual(dayStats.granularity, .day) + XCTAssertEqual(dayStats.date, "2018-08-06") + XCTAssertEqual(dayStats.fields.count, 7) + XCTAssertEqual(dayStats.items!.count, 12) + + let sampleItem1 = dayStats.items![0] + XCTAssertEqual(sampleItem1.period, "2018-07-26") + XCTAssertEqual(sampleItem1.views, 206) + XCTAssertEqual(sampleItem1.visitors, 101) + XCTAssertEqual(sampleItem1.likes, 12) + XCTAssertEqual(sampleItem1.reblogs, 2) + XCTAssertEqual(sampleItem1.comments, 17) + XCTAssertEqual(sampleItem1.posts, 3) + + let sampleItem2 = dayStats.items![11] + XCTAssertEqual(sampleItem2.period, "2018-08-06") + XCTAssertEqual(sampleItem2.views, 1) + XCTAssertEqual(sampleItem2.visitors, 2) + XCTAssertEqual(sampleItem2.likes, 3) + XCTAssertEqual(sampleItem2.reblogs, 4) + XCTAssertEqual(sampleItem2.comments, 5) + XCTAssertEqual(sampleItem2.posts, 6) + } + + /// Verifies that all of the week unit SiteVisitStats fields are parsed correctly. + /// + func testWeekUnitStatFieldsAreProperlyParsed() { + guard let weekStats = mapSiteVisitStatsWithWeekUnitResponse() else { + XCTFail() + return + } + + XCTAssertEqual(weekStats.granularity, .week) + XCTAssertEqual(weekStats.date, "2018-08-06") + XCTAssertEqual(weekStats.fields.count, 7) + XCTAssertEqual(weekStats.items!.count, 12) + + let sampleItem1 = weekStats.items![0] + XCTAssertEqual(sampleItem1.period, "2018W05W21") + XCTAssertEqual(sampleItem1.views, 5) + XCTAssertEqual(sampleItem1.visitors, 4) + XCTAssertEqual(sampleItem1.likes, 0) + XCTAssertEqual(sampleItem1.reblogs, 0) + XCTAssertEqual(sampleItem1.comments, 0) + XCTAssertEqual(sampleItem1.posts, 0) + + let sampleItem2 = weekStats.items![11] + XCTAssertEqual(sampleItem2.period, "2018W08W06") + XCTAssertEqual(sampleItem2.views, 33) + XCTAssertEqual(sampleItem2.visitors, 123123123) + XCTAssertEqual(sampleItem2.likes, 1) + XCTAssertEqual(sampleItem2.reblogs, 9999999) + XCTAssertEqual(sampleItem2.comments, 123345) + XCTAssertEqual(sampleItem2.posts, 56) + + } + + /// Verifies that all of the month unit SiteVisitStats fields are parsed correctly. + /// + func testMonthUnitStatFieldsAreProperlyParsed() { + guard let monthStats = mapSiteVisitStatsWithMonthUnitResponse() else { + XCTFail() + return + } + + XCTAssertEqual(monthStats.granularity, .month) + XCTAssertEqual(monthStats.date, "2018-08-06") + XCTAssertEqual(monthStats.fields.count, 7) + XCTAssertEqual(monthStats.items!.count, 12) + + let sampleItem1 = monthStats.items![0] + XCTAssertEqual(sampleItem1.period, "2017-09-01") + XCTAssertEqual(sampleItem1.views, 36) + XCTAssertEqual(sampleItem1.visitors, 24) + XCTAssertEqual(sampleItem1.likes, 1) + XCTAssertEqual(sampleItem1.reblogs, 0) + XCTAssertEqual(sampleItem1.comments, 3) + XCTAssertEqual(sampleItem1.posts, 2) + + let sampleItem2 = monthStats.items![10] + XCTAssertEqual(sampleItem2.period, "2018-07-01") + XCTAssertEqual(sampleItem2.views, 16) + XCTAssertEqual(sampleItem2.visitors, 6) + XCTAssertEqual(sampleItem2.likes, 9) + XCTAssertEqual(sampleItem2.reblogs, 0) + XCTAssertEqual(sampleItem2.comments, 0) + XCTAssertEqual(sampleItem2.posts, 0) + } + + /// Verifies that all of the year unit SiteVisitStats fields are parsed correctly. + /// + func testYearUnitStatFieldsAreProperlyParsed() { + guard let yearStats = mapSiteVisitStatsWithYearUnitResponse() else { + XCTFail() + return + } + + XCTAssertEqual(yearStats.granularity, .year) + XCTAssertEqual(yearStats.date, "2018-08-06") + XCTAssertEqual(yearStats.fields.count, 7) + XCTAssertEqual(yearStats.items!.count, 5) + + let sampleItem1 = yearStats.items![0] + XCTAssertEqual(sampleItem1.period, "2014-01-01") + XCTAssertEqual(sampleItem1.views, 12821) + XCTAssertEqual(sampleItem1.visitors, 1135) + XCTAssertEqual(sampleItem1.likes, 1094) + XCTAssertEqual(sampleItem1.reblogs, 0) + XCTAssertEqual(sampleItem1.comments, 1611) + XCTAssertEqual(sampleItem1.posts, 597) + + + let sampleItem2 = yearStats.items![3] + XCTAssertEqual(sampleItem2.period, "2017-01-01") + XCTAssertEqual(sampleItem2.views, 348) + XCTAssertEqual(sampleItem2.visitors, 144) + XCTAssertEqual(sampleItem2.likes, 3) + XCTAssertEqual(sampleItem2.reblogs, 0) + XCTAssertEqual(sampleItem2.comments, 5) + XCTAssertEqual(sampleItem2.posts, 4) + } +} + + +/// Private Methods. +/// +private extension SiteVisitStatsMapperTests { + + /// Returns the SiteVisitStatsMapper output upon receiving `filename` (Data Encoded) + /// + func mapSiteVisitStatItems(from filename: String) -> SiteVisitStats? { + guard let response = Loader.contentsOf(filename) else { + return nil + } + + return try! SiteVisitStatsMapper().map(response: response) + } + + /// Returns the SiteVisitStatsMapper output upon receiving `site-visits-day` + /// + func mapSiteVisitStatsWithDayUnitResponse() -> SiteVisitStats? { + return mapSiteVisitStatItems(from: "site-visits-day") + } + + /// Returns the SiteVisitStatsMapper output upon receiving `site-visits-week` + /// + func mapSiteVisitStatsWithWeekUnitResponse() -> SiteVisitStats? { + return mapSiteVisitStatItems(from: "site-visits-week") + } + + /// Returns the SiteVisitStatsMapper output upon receiving `site-visits-month` + /// + func mapSiteVisitStatsWithMonthUnitResponse() -> SiteVisitStats? { + return mapSiteVisitStatItems(from: "site-visits-month") + } + + /// Returns the SiteVisitStatsMapper output upon receiving `site-visits-year` + /// + func mapSiteVisitStatsWithYearUnitResponse() -> SiteVisitStats? { + return mapSiteVisitStatItems(from: "site-visits-year") + } +} diff --git a/Networking/NetworkingTests/Responses/site-visits-day.json b/Networking/NetworkingTests/Responses/site-visits-day.json index d5d10ac5786..3b26a8b9fab 100644 --- a/Networking/NetworkingTests/Responses/site-visits-day.json +++ b/Networking/NetworkingTests/Responses/site-visits-day.json @@ -13,12 +13,12 @@ "data": [ [ "2018-07-26", - 0, - 0, - 0, - 0, - 0, - 0 + 206, + 101, + 12, + 2, + 17, + 3 ], [ "2018-07-27", @@ -112,12 +112,12 @@ ], [ "2018-08-06", - 0, - 0, - 0, - 0, - 0, - 0 + 1, + 2, + 3, + 4, + 5, + 6 ] ] } diff --git a/Networking/NetworkingTests/Responses/site-visits-week.json b/Networking/NetworkingTests/Responses/site-visits-week.json index a0646f26aef..69db25ab63f 100644 --- a/Networking/NetworkingTests/Responses/site-visits-week.json +++ b/Networking/NetworkingTests/Responses/site-visits-week.json @@ -112,12 +112,12 @@ ], [ "2018W08W06", - 0, - 0, - 0, - 0, - 0, - 0 + 33, + 123123123, + 1, + 9999999, + 123345, + 56 ] ] } From 0e815caee7b999ee2420cde27017cf7b92d380f6 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 6 Aug 2018 12:15:24 -0500 Subject: [PATCH 076/112] Added SiteVisitStatsRemoteTests --- .../Networking.xcodeproj/project.pbxproj | 4 ++ .../Remote/SiteVisitStatsRemoteTests.swift | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 Networking/NetworkingTests/Remote/SiteVisitStatsRemoteTests.swift diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 0fc41ec2fb8..dd4dbcebeb9 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 21DB5B99C4107CF69C0A57EC /* Pods_NetworkingTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */; }; 6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; }; 74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */; }; + 74002D6C2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74002D6B2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift */; }; 741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */; }; 743FDB9C210FB36900AC737F /* order-stats-month.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB99210FB36900AC737F /* order-stats-month.json */; }; 743FDB9D210FB36900AC737F /* order-stats-year.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9A210FB36900AC737F /* order-stats-year.json */; }; @@ -104,6 +105,7 @@ /* Begin PBXFileReference section */ 69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NetworkingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsMapperTests.swift; sourceTree = ""; }; + 74002D6B2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsRemoteTests.swift; sourceTree = ""; }; 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponLine.swift; sourceTree = ""; }; 743FDB99210FB36900AC737F /* order-stats-month.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-month.json"; sourceTree = ""; }; 743FDB9A210FB36900AC737F /* order-stats-year.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-year.json"; sourceTree = ""; }; @@ -265,6 +267,7 @@ B505F6D620BEE58800BB1B69 /* AccountRemoteTests.swift */, B518662920A09C6F00037A38 /* OrdersRemoteTests.swift */, 74AE244C2113704C00CA8C54 /* OrderStatsRemoteTests.swift */, + 74002D6B2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift */, ); path = Remote; sourceTree = ""; @@ -723,6 +726,7 @@ B567AF2F20A0FB8F00AB6C62 /* AuthenticatedRequestTests.swift in Sources */, B5C6FCCD20A34B8300A4F8E4 /* OrderListMapperTests.swift in Sources */, B518663520A0A2E800037A38 /* Constants.swift in Sources */, + 74002D6C2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift in Sources */, 74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */, B567AF3020A0FB8F00AB6C62 /* DotcomRequestTests.swift in Sources */, 743FDBA0210FB3E500AC737F /* OrderStatsMapperTests.swift in Sources */, diff --git a/Networking/NetworkingTests/Remote/SiteVisitStatsRemoteTests.swift b/Networking/NetworkingTests/Remote/SiteVisitStatsRemoteTests.swift new file mode 100644 index 00000000000..afdc87fca3f --- /dev/null +++ b/Networking/NetworkingTests/Remote/SiteVisitStatsRemoteTests.swift @@ -0,0 +1,55 @@ +import XCTest +@testable import Networking + + +/// SiteVisitStatsRemote Unit Tests +/// +class SiteVisitStatsRemoteTests: XCTestCase { + + /// Dummy Network Wrapper + /// + let network = MockupNetwork() + + /// Dummy Site ID + /// + let sampleSiteID = 1234 + + /// Repeat always! + /// + override func setUp() { + network.removeAllSimulatedResponses() + } + + + /// Verifies that loadSiteVisitorStats properly parses the `SiteVisitStats` sample response. + /// + func testLoadSiteVisitStatsProperlyReturnsParsedStats() { + let remote = SiteVisitStatsRemote(network: network) + let expectation = self.expectation(description: "Load order stats") + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/visits/", filename: "site-visits-day") + remote.loadSiteVisitorStats(for: sampleSiteID, unit: .day, latestDateToInclude: Date(), quantity: 12) { (siteVisitStats, error) in + XCTAssertNil(error) + XCTAssertNotNil(siteVisitStats) + XCTAssertEqual(siteVisitStats?.items?.count, 12) + expectation.fulfill() + } + + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that loadSiteVisitorStats properly relays Networking Layer errors. + /// + func testLoadSiteVisitStatsProperlyRelaysNetwokingErrors() { + let remote = SiteVisitStatsRemote(network: network) + let expectation = self.expectation(description: "Load order stats contains errors") + + remote.loadSiteVisitorStats(for: sampleSiteID, unit: .day, latestDateToInclude: Date(), quantity: 12) { (siteVisitStats, error) in + XCTAssertNil(siteVisitStats) + XCTAssertNotNil(error) + expectation.fulfill() + } + + wait(for: [expectation], timeout: Constants.expectationTimeout) + } +} From a8857535e2e28c68b1a8e80297baf44f01c4789d Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 6 Aug 2018 12:36:24 -0500 Subject: [PATCH 077/112] Minor refactoring which yields StatsAction & StatsStore; added site visits action --- .../Networking.xcodeproj/project.pbxproj | 8 ++--- .../Networking/Model/Stats/OrderStats.swift | 6 ++-- .../Model/Stats/SiteVisitStats.swift | 6 ++-- ...ranularity.swift => StatGranularity.swift} | 8 ++--- .../Networking/Remote/OrderStatsRemote.swift | 2 +- .../Remote/SiteVisitStatsRemote.swift | 2 +- Yosemite/Yosemite.xcodeproj/project.pbxproj | 18 +++++------ .../Yosemite/Actions/OrderStatsAction.swift | 12 ------- Yosemite/Yosemite/Actions/StatsAction.swift | 16 ++++++++++ Yosemite/Yosemite/Model/Model.swift | 2 +- ...OrderStatsStore.swift => StatsStore.swift} | 32 +++++++++++++++---- .../Stores/OrderStatsStoreTests.swift | 12 +++---- 12 files changed, 72 insertions(+), 52 deletions(-) rename Networking/Networking/Model/Stats/{OrderStatGranularity.swift => StatGranularity.swift} (70%) delete mode 100644 Yosemite/Yosemite/Actions/OrderStatsAction.swift create mode 100644 Yosemite/Yosemite/Actions/StatsAction.swift rename Yosemite/Yosemite/Stores/{OrderStatsStore.swift => StatsStore.swift} (56%) diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index dd4dbcebeb9..8a3e62e8c43 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -41,7 +41,7 @@ 74C8F06E20EEC1E800B6EDC9 /* OrderNotesMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */; }; 74C8F07020EEC3A800B6EDC9 /* broken-notes.json in Resources */ = {isa = PBXBuildFile; fileRef = 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */; }; 74D3BD142114FE6900A6E85E /* MIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D3BD132114FE6900A6E85E /* MIContainer.swift */; }; - 74D522B62113607F00042831 /* OrderStatGranularity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D522B52113607F00042831 /* OrderStatGranularity.swift */; }; + 74D522B62113607F00042831 /* StatGranularity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D522B52113607F00042831 /* StatGranularity.swift */; }; B505F6CD20BEE37E00BB1B69 /* AccountMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */; }; B505F6CF20BEE38B00BB1B69 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6CE20BEE38B00BB1B69 /* Account.swift */; }; B505F6D120BEE39600BB1B69 /* AccountRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6D020BEE39600BB1B69 /* AccountRemote.swift */; }; @@ -136,7 +136,7 @@ 74C8F06D20EEC1E700B6EDC9 /* OrderNotesMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNotesMapperTests.swift; sourceTree = ""; }; 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "broken-notes.json"; sourceTree = ""; }; 74D3BD132114FE6900A6E85E /* MIContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MIContainer.swift; sourceTree = ""; }; - 74D522B52113607F00042831 /* OrderStatGranularity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatGranularity.swift; sourceTree = ""; }; + 74D522B52113607F00042831 /* StatGranularity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatGranularity.swift; sourceTree = ""; }; 753D6504FF01F09F6A33B73E /* Pods-Networking.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Networking.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Networking/Pods-Networking.debug.xcconfig"; sourceTree = ""; }; B505F6CC20BEE37E00BB1B69 /* AccountMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountMapper.swift; sourceTree = ""; }; B505F6CE20BEE38B00BB1B69 /* Account.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; @@ -229,7 +229,7 @@ isa = PBXGroup; children = ( 74D3BD132114FE6900A6E85E /* MIContainer.swift */, - 74D522B52113607F00042831 /* OrderStatGranularity.swift */, + 74D522B52113607F00042831 /* StatGranularity.swift */, 74A1196B2110F4BB00E1E5F0 /* OrderStats.swift */, 748D4249210F92EA00CF7D1B /* OrderStatsItem.swift */, 74A1D26721189A7000931DFA /* SiteVisitStats.swift */, @@ -687,7 +687,7 @@ 7452387321124B7700A973CD /* AnyCodable.swift in Sources */, B567AF2920A0FA1E00AB6C62 /* Mapper.swift in Sources */, 74A1D26D21189DFF00931DFA /* SiteVisitStatsMapper.swift in Sources */, - 74D522B62113607F00042831 /* OrderStatGranularity.swift in Sources */, + 74D522B62113607F00042831 /* StatGranularity.swift in Sources */, B518662220A097C200037A38 /* Network.swift in Sources */, B518662420A099BF00037A38 /* AlamofireNetwork.swift in Sources */, B557DA1820979D51005962F4 /* Credentials.swift in Sources */, diff --git a/Networking/Networking/Model/Stats/OrderStats.swift b/Networking/Networking/Model/Stats/OrderStats.swift index 1104367a350..22966df86d8 100644 --- a/Networking/Networking/Model/Stats/OrderStats.swift +++ b/Networking/Networking/Model/Stats/OrderStats.swift @@ -5,7 +5,7 @@ import Foundation /// public struct OrderStats: Decodable { public let date: String - public let granularity: OrderStatGranularity + public let granularity: StatGranularity public let quantity: String public let fields: [String] public let totalGrossSales: Float @@ -25,7 +25,7 @@ public struct OrderStats: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) let date = try container.decode(String.self, forKey: .date) - let granularity = try container.decode(OrderStatGranularity.self, forKey: .unit) + let granularity = try container.decode(StatGranularity.self, forKey: .unit) let quantity = try container.decode(String.self, forKey: .quantity) let fields = try container.decode([String].self, forKey: .fields) @@ -49,7 +49,7 @@ public struct OrderStats: Decodable { /// OrderStats struct initializer. /// - public init(date: String, granularity: OrderStatGranularity, quantity: String, fields: [String], items: [OrderStatsItem]?, totalGrossSales: Float, totalNetSales: Float, totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { + public init(date: String, granularity: StatGranularity, quantity: String, fields: [String], items: [OrderStatsItem]?, totalGrossSales: Float, totalNetSales: Float, totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { self.date = date self.granularity = granularity self.quantity = quantity diff --git a/Networking/Networking/Model/Stats/SiteVisitStats.swift b/Networking/Networking/Model/Stats/SiteVisitStats.swift index cbfa8178978..ea9b584426e 100644 --- a/Networking/Networking/Model/Stats/SiteVisitStats.swift +++ b/Networking/Networking/Model/Stats/SiteVisitStats.swift @@ -5,7 +5,7 @@ import Foundation /// public struct SiteVisitStats: Decodable { public let date: String - public let granularity: OrderStatGranularity + public let granularity: StatGranularity public let fields: [String] public let items: [SiteVisitStatsItem]? @@ -15,7 +15,7 @@ public struct SiteVisitStats: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) let date = try container.decode(String.self, forKey: .date) - let granularity = try container.decode(OrderStatGranularity.self, forKey: .unit) + let granularity = try container.decode(StatGranularity.self, forKey: .unit) let fields = try container.decode([String].self, forKey: .fields) let rawData: [[AnyCodable]] = try container.decode([[AnyCodable]].self, forKey: .data) @@ -28,7 +28,7 @@ public struct SiteVisitStats: Decodable { /// OrderStats struct initializer. /// - public init(date: String, granularity: OrderStatGranularity, fields: [String], items: [SiteVisitStatsItem]?) { + public init(date: String, granularity: StatGranularity, fields: [String], items: [SiteVisitStatsItem]?) { self.date = date self.granularity = granularity self.fields = fields diff --git a/Networking/Networking/Model/Stats/OrderStatGranularity.swift b/Networking/Networking/Model/Stats/StatGranularity.swift similarity index 70% rename from Networking/Networking/Model/Stats/OrderStatGranularity.swift rename to Networking/Networking/Model/Stats/StatGranularity.swift index 21e37a60b6b..f9132193584 100644 --- a/Networking/Networking/Model/Stats/OrderStatGranularity.swift +++ b/Networking/Networking/Model/Stats/StatGranularity.swift @@ -1,9 +1,9 @@ import Foundation -/// Represents the data granularity for a specific `OrderStats` instance (e.g. day, week, month, year) +/// Represents data granularity for stats (e.g. day, week, month, year) /// -public enum OrderStatGranularity: String, Decodable { +public enum StatGranularity: String, Decodable { case day case week case month @@ -12,9 +12,9 @@ public enum OrderStatGranularity: String, Decodable { // MARK: - StringConvertible Conformance // -extension OrderStatGranularity: CustomStringConvertible { +extension StatGranularity: CustomStringConvertible { - /// Returns a string describing the current OrderStatus Instance + /// Returns a user-freindly, localized string describing the stat granularity /// public var description: String { switch self { diff --git a/Networking/Networking/Remote/OrderStatsRemote.swift b/Networking/Networking/Remote/OrderStatsRemote.swift index 6fbd6e91ae1..6e458305b77 100644 --- a/Networking/Networking/Remote/OrderStatsRemote.swift +++ b/Networking/Networking/Remote/OrderStatsRemote.swift @@ -15,7 +15,7 @@ public class OrderStatsRemote: Remote { /// - quantity: How many `unit`s to fetch /// - completion: Closure to be executed upon completion. /// - public func loadOrderStats(for siteID: Int, unit: OrderStatGranularity, latestDateToInclude: String, quantity: Int, completion: @escaping (OrderStats?, Error?) -> Void) { + public func loadOrderStats(for siteID: Int, unit: StatGranularity, latestDateToInclude: String, quantity: Int, completion: @escaping (OrderStats?, Error?) -> Void) { let path = "\(Constants.sitesPath)/\(siteID)/\(Constants.orderStatsPath)/" let parameters = [ParameterKeys.unit: unit.rawValue, ParameterKeys.date: latestDateToInclude, diff --git a/Networking/Networking/Remote/SiteVisitStatsRemote.swift b/Networking/Networking/Remote/SiteVisitStatsRemote.swift index ac316999b5a..c920d9baab7 100644 --- a/Networking/Networking/Remote/SiteVisitStatsRemote.swift +++ b/Networking/Networking/Remote/SiteVisitStatsRemote.swift @@ -15,7 +15,7 @@ public class SiteVisitStatsRemote: Remote { /// - quantity: How many `unit`s to fetch /// - completion: Closure to be executed upon completion. /// - public func loadSiteVisitorStats(for siteID: Int, unit: OrderStatGranularity, latestDateToInclude: Date, quantity: Int, completion: @escaping (SiteVisitStats?, Error?) -> Void) { + public func loadSiteVisitorStats(for siteID: Int, unit: StatGranularity, latestDateToInclude: Date, quantity: Int, completion: @escaping (SiteVisitStats?, Error?) -> Void) { let path = "\(Constants.sitesPath)/\(siteID)/\(Constants.siteVisitStatsPath)/" let parameters = [ParameterKeys.unit: unit.rawValue, ParameterKeys.date: DateFormatter.Stats.statsDayFormatter.string(from: latestDateToInclude), diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 283c8506ff9..e21f9f13a38 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -11,13 +11,12 @@ 36941EA7B9242CAB1FF828BC /* Pods_YosemiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 991BBCE6E4A92F0A028885D8 /* Pods_YosemiteTests.framework */; }; 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */; }; 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */; }; - 7495C5262114977600CDD33B /* order-stats.json in Resources */ = {isa = PBXBuildFile; fileRef = 7495C5222114977600CDD33B /* order-stats.json */; }; 7495C5292114979D00CDD33B /* OrderStatsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */; }; 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */; }; 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */; }; 7499936820EFC0ED00CF01CD /* OrderNoteStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */; }; - 74A18C562113827E00DCF8A8 /* OrderStatsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A18C552113827E00DCF8A8 /* OrderStatsStore.swift */; }; - 74A18C58211382A000DCF8A8 /* OrderStatsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A18C57211382A000DCF8A8 /* OrderStatsAction.swift */; }; + 74A18C562113827E00DCF8A8 /* StatsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A18C552113827E00DCF8A8 /* StatsStore.swift */; }; + 74A18C58211382A000DCF8A8 /* StatsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A18C57211382A000DCF8A8 /* StatsAction.swift */; }; 74A7688C20D45EBA00F9D437 /* OrderStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688B20D45EBA00F9D437 /* OrderStore.swift */; }; 74A7688E20D45ED400F9D437 /* OrderStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */; }; 74A7689020D45F9300F9D437 /* OrderAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74A7688F20D45F9300F9D437 /* OrderAction.swift */; }; @@ -65,13 +64,12 @@ /* Begin PBXFileReference section */ 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+ReadOnlyConvertible.swift"; sourceTree = ""; }; - 7495C5222114977600CDD33B /* order-stats.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats.json"; sourceTree = ""; }; 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsStoreTests.swift; sourceTree = ""; }; 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteAction.swift; sourceTree = ""; }; 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteStore.swift; sourceTree = ""; }; 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteStoreTests.swift; sourceTree = ""; }; - 74A18C552113827E00DCF8A8 /* OrderStatsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsStore.swift; sourceTree = ""; }; - 74A18C57211382A000DCF8A8 /* OrderStatsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsAction.swift; sourceTree = ""; }; + 74A18C552113827E00DCF8A8 /* StatsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsStore.swift; sourceTree = ""; }; + 74A18C57211382A000DCF8A8 /* StatsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsAction.swift; sourceTree = ""; }; 74A7688B20D45EBA00F9D437 /* OrderStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStore.swift; sourceTree = ""; }; 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStoreTests.swift; sourceTree = ""; }; 74A7688F20D45F9300F9D437 /* OrderAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAction.swift; sourceTree = ""; }; @@ -168,7 +166,7 @@ B5BC736420D1A98500B5B6FA /* AccountStore.swift */, 74A7688B20D45EBA00F9D437 /* OrderStore.swift */, 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */, - 74A18C552113827E00DCF8A8 /* OrderStatsStore.swift */, + 74A18C552113827E00DCF8A8 /* StatsStore.swift */, ); path = Stores; sourceTree = ""; @@ -297,7 +295,7 @@ B5DC3CB020D1B8720063AC41 /* AccountAction.swift */, 74A7688F20D45F9300F9D437 /* OrderAction.swift */, 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */, - 74A18C57211382A000DCF8A8 /* OrderStatsAction.swift */, + 74A18C57211382A000DCF8A8 /* StatsAction.swift */, ); path = Actions; sourceTree = ""; @@ -498,7 +496,7 @@ B5DC3CB120D1B8720063AC41 /* AccountAction.swift in Sources */, B5BC736520D1A98500B5B6FA /* AccountStore.swift in Sources */, B56C1EC220EAE2E500D749F9 /* ReadOnlyConvertible.swift in Sources */, - 74A18C562113827E00DCF8A8 /* OrderStatsStore.swift in Sources */, + 74A18C562113827E00DCF8A8 /* StatsStore.swift in Sources */, B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */, B5B5C797208E49B600642956 /* Action+Internal.swift in Sources */, 74A7688C20D45EBA00F9D437 /* OrderStore.swift in Sources */, @@ -512,7 +510,7 @@ B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */, B5EED1A820F4F3CF00652449 /* Account+ReadOnlyConvertible.swift in Sources */, B5F2AE9720EBB54A00FEDC59 /* FetchedResultsControllerDelegateWrapper.swift in Sources */, - 74A18C58211382A000DCF8A8 /* OrderStatsAction.swift in Sources */, + 74A18C58211382A000DCF8A8 /* StatsAction.swift in Sources */, 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Yosemite/Yosemite/Actions/OrderStatsAction.swift b/Yosemite/Yosemite/Actions/OrderStatsAction.swift deleted file mode 100644 index 29b819fb234..00000000000 --- a/Yosemite/Yosemite/Actions/OrderStatsAction.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation -import Networking - - -// MARK: - OrderStatsAction: Defines all of the Actions supported by the OrderStatsStore. -// -public enum OrderStatsAction: Action { - - // FIXME: We are returning OrderStats in the completion handler...this needs to eventually return an error/nil much like - // OrderAction.retrieveOrders. Update this once OrderStats storage is in place. - case retrieveOrderStats(siteID: Int, granularity: OrderStatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: (OrderStats?, Error?) -> Void) -} diff --git a/Yosemite/Yosemite/Actions/StatsAction.swift b/Yosemite/Yosemite/Actions/StatsAction.swift new file mode 100644 index 00000000000..75747034982 --- /dev/null +++ b/Yosemite/Yosemite/Actions/StatsAction.swift @@ -0,0 +1,16 @@ +import Foundation +import Networking + + +// MARK: - StatsAction: Defines stats operations (supported by the StatsStore). +// +public enum StatsAction: Action { + + // FIXME: We are returning OrderStats in the completion handler...this needs to eventually return an error/nil much like + // OrderAction.retrieveOrders. Update this once OrderStats storage is in place. + case retrieveOrderStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: (OrderStats?, Error?) -> Void) + + // FIXME: We are returning SiteVisitStats in the completion handler...this needs to eventually return an error/nil much like + // OrderAction.retrieveOrders. Update this once SiteVisitStats storage is in place. + case retrieveSiteVisitStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: (SiteVisitStats?, Error?) -> Void) +} diff --git a/Yosemite/Yosemite/Model/Model.swift b/Yosemite/Yosemite/Model/Model.swift index d233dc46607..991571de4b1 100644 --- a/Yosemite/Yosemite/Model/Model.swift +++ b/Yosemite/Yosemite/Model/Model.swift @@ -16,7 +16,7 @@ public typealias OrderCouponLine = Networking.OrderCouponLine public typealias OrderNote = Networking.OrderNote public typealias OrderStats = Networking.OrderStats public typealias OrderStatsItem = Networking.OrderStatsItem -public typealias OrderStatGranularity = Networking.OrderStatGranularity +public typealias StatGranularity = Networking.StatGranularity public typealias Site = Networking.Site public typealias SiteVisitStats = Networking.SiteVisitStats public typealias SiteVisitStatsItem = Networking.SiteVisitStatsItem diff --git a/Yosemite/Yosemite/Stores/OrderStatsStore.swift b/Yosemite/Yosemite/Stores/StatsStore.swift similarity index 56% rename from Yosemite/Yosemite/Stores/OrderStatsStore.swift rename to Yosemite/Yosemite/Stores/StatsStore.swift index d7fa29c4511..bb2111323d8 100644 --- a/Yosemite/Yosemite/Stores/OrderStatsStore.swift +++ b/Yosemite/Yosemite/Stores/StatsStore.swift @@ -1,20 +1,20 @@ import Foundation import Networking -// MARK: - OrderStatsStore +// MARK: - StatsStore // -public class OrderStatsStore: Store { +public class StatsStore: Store { /// Registers for supported Actions. /// override public func registerSupportedActions(in dispatcher: Dispatcher) { - dispatcher.register(processor: self, for: OrderStatsAction.self) + dispatcher.register(processor: self, for: StatsAction.self) } /// Receives and executes Actions. /// override public func onAction(_ action: Action) { - guard let action = action as? OrderStatsAction else { + guard let action = action as? StatsAction else { assertionFailure("OrderStatsStore received an unsupported action") return } @@ -22,6 +22,8 @@ public class OrderStatsStore: Store { switch action { case .retrieveOrderStats(let siteID, let granularity, let latestDateToInclude, let quantity, let onCompletion): retrieveOrderStats(siteID: siteID, granularity: granularity, latestDateToInclude: latestDateToInclude, quantity: quantity, onCompletion: onCompletion) + case .retrieveSiteVisitStats(let siteID, let granularity, let latestDateToInclude, let quantity, let onCompletion): + retrieveSiteVisitStats(siteID: siteID, granularity: granularity, latestDateToInclude: latestDateToInclude, quantity: quantity, onCompletion: onCompletion) } } } @@ -29,11 +31,11 @@ public class OrderStatsStore: Store { // MARK: - Services! // -private extension OrderStatsStore { +private extension StatsStore { /// Retrieves the order stats associated with the provided Site ID (if any!). /// - func retrieveOrderStats(siteID: Int, granularity: OrderStatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: @escaping (OrderStats?, Error?) -> Void) { + func retrieveOrderStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: @escaping (OrderStats?, Error?) -> Void) { let remote = OrderStatsRemote(network: network) let formattedDateString = buildDateString(from: latestDateToInclude, with: granularity) @@ -48,9 +50,25 @@ private extension OrderStatsStore { } } + /// Retrieves the site visit stats associated with the provided Site ID (if any!). + /// + func retrieveSiteVisitStats(siteID: Int, granularity: StatGranularity, latestDateToInclude: Date, quantity: Int, onCompletion: @escaping (SiteVisitStats?, Error?) -> Void) { + + let remote = SiteVisitStatsRemote(network: network) + + remote.loadSiteVisitorStats(for: siteID, unit: granularity, latestDateToInclude: latestDateToInclude, quantity: quantity) { (siteVisitStats, error) in + guard let siteVisitStats = siteVisitStats else { + onCompletion(nil, error) + return + } + + onCompletion(siteVisitStats, nil) + } + } + /// Converts a Date into the appropriatly formatted string based on the `OrderStatGranularity` /// - func buildDateString(from date: Date, with granularity: OrderStatGranularity) -> String { + func buildDateString(from date: Date, with granularity: StatGranularity) -> String { switch granularity { case .day: return DateFormatter.Stats.statsDayFormatter.string(from: date) diff --git a/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift b/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift index 422b78d7e8a..bfd3e3b5c8c 100644 --- a/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift @@ -35,11 +35,11 @@ class OrderStatsStoreTests: XCTestCase { /// func testRetrieveOrderStatsReturnsExpectedFields() { let expectation = self.expectation(description: "Retrieve order stats") - let orderStatsStore = OrderStatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let orderStatsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) let remoteOrderStats = sampleOrderStats() network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/orders/", filename: "order-stats") - let action = OrderStatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, + let action = StatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (orderStats, error) in XCTAssertNil(error) guard let orderStats = orderStats, @@ -60,10 +60,10 @@ class OrderStatsStoreTests: XCTestCase { /// func testRetrieveOrderStatsReturnsErrorUponReponseError() { let expectation = self.expectation(description: "Retrieve order stats error response") - let orderStatsStore = OrderStatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let orderStatsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/orders/", filename: "generic_error") - let action = OrderStatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, + let action = StatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (orderStats, error) in XCTAssertNil(orderStats) XCTAssertNotNil(error) @@ -82,9 +82,9 @@ class OrderStatsStoreTests: XCTestCase { /// func testRetrieveOrderNotesReturnsErrorUponEmptyResponse() { let expectation = self.expectation(description: "Retrieve order stats empty response") - let orderStatsStore = OrderStatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let orderStatsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) - let action = OrderStatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, + let action = StatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (orderStats, error) in XCTAssertNotNil(error) XCTAssertNil(orderStats) From b6520f289dd83849de0597fcc022083bb0c2b92f Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 6 Aug 2018 12:50:06 -0500 Subject: [PATCH 078/112] Refactored StatsStoreTests; added StatsAction.retrieveSiteVisitStats error tests --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 8 +- ...StoreTests.swift => StatsStoreTests.swift} | 81 ++++++++++++++++--- 2 files changed, 72 insertions(+), 17 deletions(-) rename Yosemite/YosemiteTests/Stores/{OrderStatsStoreTests.swift => StatsStoreTests.swift} (62%) diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index e21f9f13a38..5455f67adf0 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ 36941EA7B9242CAB1FF828BC /* Pods_YosemiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 991BBCE6E4A92F0A028885D8 /* Pods_YosemiteTests.framework */; }; 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */; }; 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */; }; - 7495C5292114979D00CDD33B /* OrderStatsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */; }; + 7495C5292114979D00CDD33B /* StatsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7495C5282114979D00CDD33B /* StatsStoreTests.swift */; }; 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */; }; 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */; }; 7499936820EFC0ED00CF01CD /* OrderNoteStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */; }; @@ -64,7 +64,7 @@ /* Begin PBXFileReference section */ 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+ReadOnlyConvertible.swift"; sourceTree = ""; }; - 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsStoreTests.swift; sourceTree = ""; }; + 7495C5282114979D00CDD33B /* StatsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsStoreTests.swift; sourceTree = ""; }; 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteAction.swift; sourceTree = ""; }; 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteStore.swift; sourceTree = ""; }; 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderNoteStoreTests.swift; sourceTree = ""; }; @@ -177,7 +177,7 @@ B5BC736720D1AA8F00B5B6FA /* AccountStoreTests.swift */, 74A7688D20D45ED400F9D437 /* OrderStoreTests.swift */, 7499936720EFC0ED00CF01CD /* OrderNoteStoreTests.swift */, - 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */, + 7495C5282114979D00CDD33B /* StatsStoreTests.swift */, ); path = Stores; sourceTree = ""; @@ -519,7 +519,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7495C5292114979D00CDD33B /* OrderStatsStoreTests.swift in Sources */, + 7495C5292114979D00CDD33B /* StatsStoreTests.swift in Sources */, 74A7688E20D45ED400F9D437 /* OrderStoreTests.swift in Sources */, B5C9DE272087FF20006B910A /* MockupAcount.swift in Sources */, B5C9DE222087FF20006B910A /* DispatcherTests.swift in Sources */, diff --git a/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift similarity index 62% rename from Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift rename to Yosemite/YosemiteTests/Stores/StatsStoreTests.swift index bfd3e3b5c8c..0ad3377c199 100644 --- a/Yosemite/YosemiteTests/Stores/OrderStatsStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift @@ -3,9 +3,9 @@ import XCTest @testable import Networking -/// OrderStatsStore Unit Tests +/// StatsStoreTests Unit Tests /// -class OrderStatsStoreTests: XCTestCase { +class StatsStoreTests: XCTestCase { /// Mockup Dispatcher! /// @@ -31,11 +31,15 @@ class OrderStatsStoreTests: XCTestCase { network = MockupNetwork() } - /// Verifies that OrderStatsAction.retrieveOrderStats returns the expected stats. + + // MARK: - StatsAction.retrieveOrderStats + + + /// Verifies that StatsAction.retrieveOrderStats returns the expected stats. /// func testRetrieveOrderStatsReturnsExpectedFields() { let expectation = self.expectation(description: "Retrieve order stats") - let orderStatsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) let remoteOrderStats = sampleOrderStats() network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/orders/", filename: "order-stats") @@ -52,15 +56,15 @@ class OrderStatsStoreTests: XCTestCase { expectation.fulfill() } - orderStatsStore.onAction(action) + statsStore.onAction(action) wait(for: [expectation], timeout: Constants.expectationTimeout) } - /// Verifies that OrderStatsAction.retrieveOrderStats returns an error whenever there is an error response from the backend. + /// Verifies that StatsAction.retrieveOrderStats returns an error whenever there is an error response from the backend. /// func testRetrieveOrderStatsReturnsErrorUponReponseError() { let expectation = self.expectation(description: "Retrieve order stats error response") - let orderStatsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/orders/", filename: "generic_error") let action = StatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, @@ -74,15 +78,15 @@ class OrderStatsStoreTests: XCTestCase { expectation.fulfill() } - orderStatsStore.onAction(action) + statsStore.onAction(action) wait(for: [expectation], timeout: Constants.expectationTimeout) } - /// Verifies that OrderStatsAction.retrieveOrderStats returns an error whenever there is no backend response. + /// Verifies that StatsAction.retrieveOrderStats returns an error whenever there is no backend response. /// - func testRetrieveOrderNotesReturnsErrorUponEmptyResponse() { + func testRetrieveOrderStatsReturnsErrorUponEmptyResponse() { let expectation = self.expectation(description: "Retrieve order stats empty response") - let orderStatsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) let action = StatsAction.retrieveOrderStats(siteID: sampleSiteID, granularity: .day, latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (orderStats, error) in @@ -95,7 +99,54 @@ class OrderStatsStoreTests: XCTestCase { expectation.fulfill() } - orderStatsStore.onAction(action) + statsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + + // MARK: - StatsAction.retrieveSiteVisitStats + + + /// Verifies that StatsAction.retrieveSiteVisitStats returns an error whenever there is an error response from the backend. + /// + func testRetrieveSiteVisitStatsReturnsErrorUponReponseError() { + let expectation = self.expectation(description: "Retrieve site visit stats error response") + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/visits/", filename: "generic_error") + let action = StatsAction.retrieveSiteVisitStats(siteID: sampleSiteID, granularity: .day, + latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (siteVisitStats, error) in + XCTAssertNil(siteVisitStats) + XCTAssertNotNil(error) + guard let _ = error else { + XCTFail() + return + } + expectation.fulfill() + } + + statsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that StatsAction.retrieveSiteVisitStats returns an error whenever there is no backend response. + /// + func testRetrieveSiteVisitStatsReturnsErrorUponEmptyResponse() { + let expectation = self.expectation(description: "Retrieve site visit stats empty response") + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + let action = StatsAction.retrieveSiteVisitStats(siteID: sampleSiteID, granularity: .day, + latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (siteVisitStats, error) in + XCTAssertNotNil(error) + XCTAssertNil(siteVisitStats) + guard let _ = error else { + XCTFail() + return + } + expectation.fulfill() + } + + statsStore.onAction(action) wait(for: [expectation], timeout: Constants.expectationTimeout) } } @@ -103,7 +154,9 @@ class OrderStatsStoreTests: XCTestCase { // MARK: - Private Methods // -private extension OrderStatsStoreTests { +private extension StatsStoreTests { + + // MARK: - Order Stats Sample func sampleOrderStats() -> OrderStats { return OrderStats(date: "2018-06-02", @@ -137,6 +190,8 @@ private extension OrderStatsStoreTests { rawData: ["2018-06-02", 1, 1, 0, 0, 30.870000000000001, 0.87, 0, 0, 0, 0, 0, 0, "USD", 30.870000000000001, 30, 30.870000000000001, 1]) } + // MARK: - Misc + func date(with dateString: String) -> Date { guard let date = DateFormatter.Defaults.dateTimeFormatter.date(from: dateString) else { return Date() From 6bccfd331e26aa81300052f05258d1c9d35e80eb Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 14:58:42 -0300 Subject: [PATCH 079/112] Implements EntityListener --- Yosemite/Yosemite/Tools/EntityListener.swift | 151 +++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 Yosemite/Yosemite/Tools/EntityListener.swift diff --git a/Yosemite/Yosemite/Tools/EntityListener.swift b/Yosemite/Yosemite/Tools/EntityListener.swift new file mode 100644 index 00000000000..637a7645049 --- /dev/null +++ b/Yosemite/Yosemite/Tools/EntityListener.swift @@ -0,0 +1,151 @@ +import Foundation +import CoreData +import Storage + + +/// EntityListener: Observes changes performed over a specified ReadOnly Entity, and executes the callback Closures, as required. +/// *Note:* The type T is expected to be a ReadOnly one. +/// +public class EntityListener { + + /// NSManagedObjectContext associated to the Main Thread. + /// + private let viewContext: NSManagedObjectContext + + /// Last known state of the Observed ReadOnly Entity. + /// + private(set) public var readOnlyEntity: T + + /// NotificationCenter Observer Token. + /// + private var notificationsToken: Any! + + /// Closure to be executed whenever the associated Storage.Entity is: (Updated | Refreshed | Inserted). + /// + public var onUpsert: ((T) -> Void)? + + /// Closure to be executed whenever the associated Storage.Entity gets Nuked from the ViewContext. + /// + public var onDelete: (() -> Void)? + + + + /// Deinitializer + /// + deinit { + NotificationCenter.default.removeObserver(notificationsToken) + } + + /// Designated Initializer. + /// + public init(viewContext: NSManagedObjectContext, readOnlyEntity: T) { + /// This tool expects a *ReadOnly* entity. We'll make sure we haven't received a NSMO. + /// + assert(readOnlyEntity is NSManagedObject == false) + + self.viewContext = viewContext + self.readOnlyEntity = readOnlyEntity + self.notificationsToken = startObservingChangeNotifications(in: viewContext) + } + + /// Convenience Initializer. + /// + public convenience init(storageManager: CoreDataManager, readOnlyEntity: T) { + self.init(viewContext: storageManager.persistentContainer.viewContext, + readOnlyEntity: readOnlyEntity) + } +} + + +// MARK: - Private Methods +// +private extension EntityListener { + + /// Starts observing changes performed over the ViewContext. + /// + func startObservingChangeNotifications(in context: NSManagedObjectContext) -> Any { + let nc = NotificationCenter.default + return nc.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: viewContext, queue: nil) { [weak self] notification in + self?.viewContextDidChange(notification: notification) + } + } + + /// Handles the ContextObjectDidChange Notification triggered by the ViewContext. + /// + func viewContextDidChange(notification: Notification) { + guard let note = ManagedObjectsDidChangeNotification(notification: notification) else { + return + } + + /// Scenario: Upsert (Insert + Update + Refresh) + /// + if let storageEntity = readOnlyConvertible(from: note.upsertedObjects, representing: readOnlyEntity), + let updatedEntity = storageEntity.toTypeErasedReadOnly() as? T + { + readOnlyEntity = updatedEntity + onUpsert?(readOnlyEntity) + } + + /// Scenario: Nuked + /// + if let _ = readOnlyConvertible(from: note.deletedObjects, representing: readOnlyEntity) { + onDelete?() + } + } + + /// Returns the first NSManagedObject stored in a fiven collection, which represents the specified (ReadOnly) entity. + /// + func readOnlyConvertible(from entities: Set, representing readOnlyEntity: T) -> ReadOnlyTypeErasedConvertible? { + return entities + .compactMap { entity in + return entity as? ReadOnlyTypeErasedConvertible + } + .first { readOnlyConvertible in + return readOnlyConvertible.represents(readOnlyEntity: readOnlyEntity) + } + } +} + + +// MARK: - Represents a NSManagedObjectsDidChangeNotification +// +private struct ManagedObjectsDidChangeNotification { + + /// Returns the collection of Inserted Objects. + /// + let insertedObjects: Set + + /// Returns the collection of Updated Objects. + /// + let updatedObjects: Set + + /// Returns the collection of Refreshed Objects. + /// + let refreshedObjects: Set + + /// Returns the collection of Deleted Objects. + /// + let deletedObjects: Set + + /// Returns the Inserted + Updated + Refreshed Objects + /// + var upsertedObjects: Set { + return insertedObjects + .union(updatedObjects) + .union(refreshedObjects) + } + + + /// Designated Initializer + /// + init?(notification: Notification) { + guard notification.name == .NSManagedObjectContextObjectsDidChange else { + return nil + } + + insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set ?? Set() + updatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set ?? Set() + refreshedObjects = notification.userInfo?[NSRefreshedObjectsKey] as? Set ?? Set() + deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set ?? Set() + } +} From 0a49a7974c77a36839fbd9e726d8cf6b7d9c0863 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 14:58:52 -0300 Subject: [PATCH 080/112] Implements EntityListenerTests --- .../Tools/EntityListenerTests.swift | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 Yosemite/YosemiteTests/Tools/EntityListenerTests.swift diff --git a/Yosemite/YosemiteTests/Tools/EntityListenerTests.swift b/Yosemite/YosemiteTests/Tools/EntityListenerTests.swift new file mode 100644 index 00000000000..eaa792fa0c3 --- /dev/null +++ b/Yosemite/YosemiteTests/Tools/EntityListenerTests.swift @@ -0,0 +1,148 @@ +import XCTest +import CoreData +import Yosemite + + + +// MARK: - EntityListener Unit Tests +// +class EntityListenerTests: XCTestCase { + + /// InMemory Storage! + /// + private var storageManager: MockupStorageManager! + + /// Returns the NSMOC associated to the Main Thread + /// + private var viewContext: NSManagedObjectContext { + return storageManager.persistentContainer.viewContext + } + + + // MARK: - Overridden Methods + + override func setUp() { + super.setUp() + storageManager = MockupStorageManager() + } + + + + /// Verifies that onUpsert is called everytime the associated Storage.Entity is Updated. + /// + func testOnUpsertGetsCalledWheneverTargetEntityIsEffectivelyUpdated() { + /// Step 1: Insert + /// + let storageAccount = storageManager.insertSampleAccount() + viewContext.saveIfNeeded() + + /// Step 2: Setup the Listener + /// + let listener = EntityListener(viewContext: viewContext, readOnlyEntity: storageAccount.toReadOnly()) + let updatedDisplayName = "Updated Display Name" + let expectation = self.expectation(description: "onUpsert") + + listener.onUpsert = { updated in + XCTAssertEqual(updated.displayName, updatedDisplayName) + expectation.fulfill() + } + + listener.onDelete = { + XCTFail() + } + + /// Step 3: Update! + /// + storageAccount.displayName = updatedDisplayName + viewContext.saveIfNeeded() + + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that onDelete is called everytime the associated Storage Entity is nuked. + /// + func testOnDeleteGetsCalledWheneverTargetEntityIsEffectivelyNuked() { + /// Step 1: Insert + /// + let storageAccount = storageManager.insertSampleAccount() + viewContext.saveIfNeeded() + + /// Step 2: Setup the Listener + /// + let listener = EntityListener(viewContext: viewContext, readOnlyEntity: storageAccount.toReadOnly()) + let expectation = self.expectation(description: "onDelete") + + listener.onUpsert = { updated in + XCTFail() + } + + listener.onDelete = { + expectation.fulfill() + } + + /// Step 3: Nuke! + /// + viewContext.deleteObject(storageAccount) + viewContext.saveIfNeeded() + + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that onUpsert is called everytime the associated Storage.Entity is Refreshed. + /// + func testOnUpsertGetsCalledWheneverTheAssociatedContextRefreshesAllObjects() { + /// Step 1: Insert + /// + let storageAccount = storageManager.insertSampleAccount() + viewContext.saveIfNeeded() + + /// Step 2: Setup the Listener + /// + let listener = EntityListener(viewContext: viewContext, readOnlyEntity: storageAccount.toReadOnly()) + let expectation = self.expectation(description: "onUpsert") + + listener.onUpsert = { updated in + expectation.fulfill() + } + + listener.onDelete = { + XCTFail() + } + + /// Step 3: Refresh + /// + viewContext.refreshAllObjects() + + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + + /// Verifies that onUpsert is called whenever the ReadOnly Entity is actually *inserted* into the associated Context. + /// + /// Normally, this scenario wouldn't happen: EntityListener *USERS* would never have access to ReadOnly instances + /// before they're effectively persisted. *But* we're supporting this, as a safety measure. + /// + func testOnUpsertGetsCalledWheneverTheAssociatedEntityGetsInsertedInContext() { + /// Step 1: Insert + /// + let storageAccount = storageManager.insertSampleAccount() + + /// Step 2: Setup the Listener + /// + let listener = EntityListener(viewContext: viewContext, readOnlyEntity: storageAccount.toReadOnly()) + let expectation = self.expectation(description: "onUpsert") + + listener.onUpsert = { updated in + expectation.fulfill() + } + + listener.onDelete = { + XCTFail() + } + + /// Step 3: Save and effectively insert into the mainMOC + /// + viewContext.saveIfNeeded() + + wait(for: [expectation], timeout: Constants.expectationTimeout) + } +} From 5d11d61ccfb7cde7e4e1bc335a99c9ea52407a44 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 14:59:15 -0300 Subject: [PATCH 081/112] Updates Project --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 283c8506ff9..a6c4dbeeed3 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 36941EA7B9242CAB1FF828BC /* Pods_YosemiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 991BBCE6E4A92F0A028885D8 /* Pods_YosemiteTests.framework */; }; 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */; }; 74685D5020F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */; }; - 7495C5262114977600CDD33B /* order-stats.json in Resources */ = {isa = PBXBuildFile; fileRef = 7495C5222114977600CDD33B /* order-stats.json */; }; 7495C5292114979D00CDD33B /* OrderStatsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */; }; 7499936420EFBC1B00CF01CD /* OrderNoteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */; }; 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */; }; @@ -27,6 +26,8 @@ B53A56A0211245E0000776C9 /* MockupStorage+Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */; }; B53D89E520E6C22B00F90866 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53D89E420E6C22B00F90866 /* Model.swift */; }; B546CCF22093636A007CDA5F /* Yosemite.h in Headers */ = {isa = PBXBuildFile; fileRef = B546CCF12093636A007CDA5F /* Yosemite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B54EAF2121188C470029C35E /* EntityListenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54EAF2021188C470029C35E /* EntityListenerTests.swift */; }; + B5631ECD2114DF8C008D3535 /* EntityListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5631ECC2114DF8C008D3535 /* EntityListener.swift */; }; B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56C1EBD20EABD2B00D749F9 /* ResultsController.swift */; }; B56C1EC220EAE2E500D749F9 /* ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56C1EC120EAE2E500D749F9 /* ReadOnlyConvertible.swift */; }; B5A01CA120D19C4700E3207E /* MockupStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A01CA020D19C4700E3207E /* MockupStorage.swift */; }; @@ -65,7 +66,6 @@ /* Begin PBXFileReference section */ 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderItem+ReadOnlyConvertible.swift"; sourceTree = ""; }; 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderCoupon+ReadOnlyConvertible.swift"; sourceTree = ""; }; - 7495C5222114977600CDD33B /* order-stats.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats.json"; sourceTree = ""; }; 7495C5282114979D00CDD33B /* OrderStatsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatsStoreTests.swift; sourceTree = ""; }; 7499936320EFBC1A00CF01CD /* OrderNoteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteAction.swift; sourceTree = ""; }; 7499936520EFBC7200CF01CD /* OrderNoteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteStore.swift; sourceTree = ""; }; @@ -86,6 +86,8 @@ B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockupStorage+Sample.swift"; sourceTree = ""; }; B53D89E420E6C22B00F90866 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Model.swift; path = Yosemite/Model/Model.swift; sourceTree = SOURCE_ROOT; }; B546CCF12093636A007CDA5F /* Yosemite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Yosemite.h; sourceTree = ""; }; + B54EAF2021188C470029C35E /* EntityListenerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityListenerTests.swift; sourceTree = ""; }; + B5631ECC2114DF8C008D3535 /* EntityListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityListener.swift; sourceTree = ""; }; B56C1EBD20EABD2B00D749F9 /* ResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsController.swift; sourceTree = ""; }; B56C1EC120EAE2E500D749F9 /* ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnlyConvertible.swift; sourceTree = ""; }; B5A01CA020D19C4700E3207E /* MockupStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockupStorage.swift; sourceTree = ""; }; @@ -158,6 +160,7 @@ B56C1EBD20EABD2B00D749F9 /* ResultsController.swift */, B56C1EC120EAE2E500D749F9 /* ReadOnlyConvertible.swift */, B5F2AE9620EBB54A00FEDC59 /* FetchedResultsControllerDelegateWrapper.swift */, + B5631ECC2114DF8C008D3535 /* EntityListener.swift */, ); path = Tools; sourceTree = ""; @@ -305,6 +308,7 @@ B5F2AE9320EBAD5200FEDC59 /* Tools */ = { isa = PBXGroup; children = ( + B54EAF2021188C470029C35E /* EntityListenerTests.swift */, B5F2AE9420EBAD6000FEDC59 /* ResultsControllerTests.swift */, ); path = Tools; @@ -495,6 +499,7 @@ B5C9DE152087FF0E006B910A /* Dispatcher.swift in Sources */, 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */, 74B7D6B020F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift in Sources */, + B5631ECD2114DF8C008D3535 /* EntityListener.swift in Sources */, B5DC3CB120D1B8720063AC41 /* AccountAction.swift in Sources */, B5BC736520D1A98500B5B6FA /* AccountStore.swift in Sources */, B56C1EC220EAE2E500D749F9 /* ReadOnlyConvertible.swift in Sources */, @@ -529,6 +534,7 @@ B5A01CA120D19C4700E3207E /* MockupStorage.swift in Sources */, B5C9DE242087FF20006B910A /* StoreTests.swift in Sources */, B5C9DE252087FF20006B910A /* MockupProcessor.swift in Sources */, + B54EAF2121188C470029C35E /* EntityListenerTests.swift in Sources */, B5C9DE282087FF20006B910A /* MockupSite.swift in Sources */, B5BC736820D1AA8F00B5B6FA /* AccountStoreTests.swift in Sources */, B5F2AE9520EBAD6000FEDC59 /* ResultsControllerTests.swift in Sources */, From 2113bf2c3f2f1c269a382427a1cdf60e9fc8276c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 15:09:22 -0300 Subject: [PATCH 082/112] ReadOnlyConvertible: Type Erasure Workaround --- .../Yosemite/Tools/ReadOnlyConvertible.swift | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift b/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift index 989af8fc97e..124fab8d1fc 100644 --- a/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift @@ -4,7 +4,7 @@ import Storage // MARK: - Represents a Mutable Entity that can be converted into a ReadOnly Type. // -public protocol ReadOnlyConvertible: class { +public protocol ReadOnlyConvertible: ReadOnlyTypeErasedConvertible { /// Represents the ReadOnly Type (mirroring the receiver). /// @@ -18,3 +18,30 @@ public protocol ReadOnlyConvertible: class { /// func toReadOnly() -> ReadOnlyType } + + +// MARK: - ReadOnlyConvertible: ErasedConvertible Default Implementation +// +public extension ReadOnlyConvertible { + + /// Returns the (Type Erased) ReadOnly version of the receiver. + /// + public func toTypeErasedReadOnly() -> Any? { + return toReadOnly() + } +} + + +// MARK: - Type Erased ReadOnlyConvertible protocol, which allows us to work around several issues, triggered by +// the `associatedtype` requirements. +// +public protocol ReadOnlyTypeErasedConvertible: class { + + /// Indicates if the receiver represents the Storage version of a given ReadOnly Type. + /// + func represents(readOnlyEntity: Any) -> Bool + + /// Returns a ReadOnly version of the receiver, but with no Type Associated. + /// + func toTypeErasedReadOnly() -> Any? +} From 6bb61a04c9deb6d59d4534aacbadb25c5b730b42 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 15:09:40 -0300 Subject: [PATCH 083/112] Account+ReadOnlyConvertible: Type Erasure Conformance --- .../Yosemite/Model/Account+ReadOnlyConvertible.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift index 73a633f8a66..b9474ba1c93 100644 --- a/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift @@ -6,6 +6,16 @@ import Storage // extension Storage.Account: ReadOnlyConvertible { + /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. + /// + public func represents(readOnlyEntity: Any) -> Bool { + guard let readOnlyAccount = readOnlyEntity as? Yosemite.Account else { + return false + } + + return readOnlyAccount.userID == Int(userID) + } + /// Updates the Storage.Account with the a ReadOnly. /// public func update(with account: Yosemite.Account) { From cf620d6a58fdc8503f3772d4aeeafb10f47fec5e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 15:09:47 -0300 Subject: [PATCH 084/112] Site+ReadOnlyConvertible: Type Erasure Conformance --- Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift index 1d06e876644..427d3cffc2a 100644 --- a/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift @@ -6,6 +6,16 @@ import Storage // extension Storage.Site: ReadOnlyConvertible { + /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. + /// + public func represents(readOnlyEntity: Any) -> Bool { + guard let readOnlySite = readOnlyEntity as? Yosemite.Site else { + return false + } + + return readOnlySite.siteID == Int(siteID) + } + /// Updates the Storage.Site with the a ReadOnly. /// public func update(with site: Yosemite.Site) { From 3bf950494e482c15c5f7360d9b95cbb07f36162c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 15:15:23 -0300 Subject: [PATCH 085/112] Order+ReadOnlyConvertible: Type Erasure Conformance --- .../Yosemite/Model/Order+ReadOnlyConvertible.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift index 0defe0acc9f..f8452d88912 100644 --- a/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift @@ -6,6 +6,16 @@ import Storage // extension Storage.Order: ReadOnlyConvertible { + /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. + /// + public func represents(readOnlyEntity: Any) -> Bool { + guard let readOnlyOrder = readOnlyEntity as? Yosemite.Order else { + return false + } + + return readOnlyOrder.siteID == Int(siteID) && readOnlyOrder.orderID == Int(orderID) + } + /// Updates the Storage.Order with the ReadOnly. /// public func update(with order: Yosemite.Order) { From d7d307ce661c5e3b2dca8b0a8b91fce22adc1b8c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 15:21:42 -0300 Subject: [PATCH 086/112] OrderCoupon+ReadOnlyConvertible: Type Erasure Conformance --- .../Model/OrderCoupon+ReadOnlyConvertible.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift index 8e513589622..4f2d6bffc33 100644 --- a/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift @@ -6,6 +6,17 @@ import Storage // extension Storage.OrderCoupon: ReadOnlyConvertible { + /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. + /// + public func represents(readOnlyEntity: Any) -> Bool { + guard let readOnlyCoupon = readOnlyEntity as? Yosemite.OrderCouponLine else { + return false + } + +// TODO: Add order.orderID + order.siteID Check + return readOnlyCoupon.couponID == Int(couponID) + } + /// Updates the Storage.OrderCoupon with the ReadOnly. /// public func update(with orderCoupon: Yosemite.OrderCouponLine) { From 6a8c12fe0e8b512d6769b91b576323ccd953248e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 15:21:49 -0300 Subject: [PATCH 087/112] OrderItem+ReadOnlyConvertible: Type Erasure Conformance --- .../Model/OrderItem+ReadOnlyConvertible.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift index e25a65b2149..a7b14bc7594 100644 --- a/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift @@ -6,6 +6,17 @@ import Storage // extension Storage.OrderItem: ReadOnlyConvertible { + /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. + /// + public func represents(readOnlyEntity: Any) -> Bool { + guard let readOnlyItem = readOnlyEntity as? Yosemite.OrderItem else { + return false + } + +// TODO: Add order.orderID + order.siteID Check + return readOnlyItem.itemID == Int(itemID) + } + /// Updates the Storage.OrderItem with the ReadOnly. /// public func update(with orderItem: Yosemite.OrderItem) { From b124492b2b83fac1b49692c82ba04b317d19fded Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 6 Aug 2018 15:21:54 -0300 Subject: [PATCH 088/112] OrderNote+ReadOnlyConvertible: Type Erasure Conformance --- .../Model/OrderNote+ReadOnlyConvertible.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift index 3a881176439..53de811bcf0 100644 --- a/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift @@ -6,6 +6,17 @@ import Storage // extension Storage.OrderNote: ReadOnlyConvertible { + /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. + /// + public func represents(readOnlyEntity: Any) -> Bool { + guard let readOnlyNote = readOnlyEntity as? Yosemite.OrderNote else { + return false + } + +// TODO: Add order.orderID + order.siteID Check + return readOnlyNote.noteID == Int(noteID) + } + /// Updates the Storage.OrderCoupon with the ReadOnly. /// public func update(with orderNote: Yosemite.OrderNote) { From 7a7189ca0761ec95e94714b1d7b1a0aa6b66ae0d Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 6 Aug 2018 19:22:23 -0500 Subject: [PATCH 089/112] Added some StatsStoreTests --- .../Networking.xcodeproj/project.pbxproj | 4 ++ .../Responses/site-visits.json | 33 ++++++++++++ .../Classes/Yosemite/AuthenticatedState.swift | 3 +- .../Stores/StatsStoreTests.swift | 50 ++++++++++++++++++- 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 Networking/NetworkingTests/Responses/site-visits.json diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 8a3e62e8c43..5a6d1c977df 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */; }; 74002D6C2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74002D6B2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift */; }; 741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */; }; + 743BF8BE21191B63008A9D87 /* site-visits.json in Resources */ = {isa = PBXBuildFile; fileRef = 743BF8BD21191B63008A9D87 /* site-visits.json */; }; 743FDB9C210FB36900AC737F /* order-stats-month.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB99210FB36900AC737F /* order-stats-month.json */; }; 743FDB9D210FB36900AC737F /* order-stats-year.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9A210FB36900AC737F /* order-stats-year.json */; }; 743FDB9E210FB36900AC737F /* order-stats-week.json in Resources */ = {isa = PBXBuildFile; fileRef = 743FDB9B210FB36900AC737F /* order-stats-week.json */; }; @@ -107,6 +108,7 @@ 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsMapperTests.swift; sourceTree = ""; }; 74002D6B2118B88200A63C19 /* SiteVisitStatsRemoteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsRemoteTests.swift; sourceTree = ""; }; 741B950020EBC8A700DD6E2D /* OrderCouponLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponLine.swift; sourceTree = ""; }; + 743BF8BD21191B63008A9D87 /* site-visits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-visits.json"; sourceTree = ""; }; 743FDB99210FB36900AC737F /* order-stats-month.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-month.json"; sourceTree = ""; }; 743FDB9A210FB36900AC737F /* order-stats-year.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-year.json"; sourceTree = ""; }; 743FDB9B210FB36900AC737F /* order-stats-week.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-week.json"; sourceTree = ""; }; @@ -409,6 +411,7 @@ 74A1D260211898F000931DFA /* site-visits-week.json */, 74A1D261211898F000931DFA /* site-visits-month.json */, 74A1D262211898F000931DFA /* site-visits-year.json */, + 743BF8BD21191B63008A9D87 /* site-visits.json */, B5C6FCD520A3768900A4F8E4 /* order.json */, B559EBA920A0B5CD00836CD4 /* orders-load-all.json */, B56C1EB920EA7D2C00D749F9 /* sites.json */, @@ -579,6 +582,7 @@ B58D10CA2114D22E00107ED4 /* new-order-note.json in Resources */, 74C8F06C20EEBD5D00B6EDC9 /* broken-order.json in Resources */, B58D10C82114D21D00107ED4 /* generic_error.json in Resources */, + 743BF8BE21191B63008A9D87 /* site-visits.json in Resources */, B505F6D520BEE4E700BB1B69 /* me.json in Resources */, B5C6FCD620A3768900A4F8E4 /* order.json in Resources */, 74A1D266211898F000931DFA /* site-visits-year.json in Resources */, diff --git a/Networking/NetworkingTests/Responses/site-visits.json b/Networking/NetworkingTests/Responses/site-visits.json new file mode 100644 index 00000000000..1676ab80ade --- /dev/null +++ b/Networking/NetworkingTests/Responses/site-visits.json @@ -0,0 +1,33 @@ +{ + "date": "2015-08-06", + "unit": "year", + "fields": [ + "period", + "views", + "visitors", + "likes", + "reblogs", + "comments", + "posts" + ], + "data": [ + [ + "2014-01-01", + 12821, + 1135, + 1094, + 0, + 1611, + 597 + ], + [ + "2015-01-01", + 14808, + 1629, + 1492, + 0, + 1268, + 571 + ] + ] +} diff --git a/WooCommerce/Classes/Yosemite/AuthenticatedState.swift b/WooCommerce/Classes/Yosemite/AuthenticatedState.swift index cddfaf80b70..64f165e58b5 100644 --- a/WooCommerce/Classes/Yosemite/AuthenticatedState.swift +++ b/WooCommerce/Classes/Yosemite/AuthenticatedState.swift @@ -26,7 +26,8 @@ class AuthenticatedState: StoresManagerState { services = [ AccountStore(dispatcher: dispatcher, storageManager: storageManager, network: network), OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network), - OrderNoteStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + OrderNoteStore(dispatcher: dispatcher, storageManager: storageManager, network: network), + StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) ] } diff --git a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift index 0ad3377c199..4228ca97e72 100644 --- a/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/StatsStoreTests.swift @@ -107,6 +107,31 @@ class StatsStoreTests: XCTestCase { // MARK: - StatsAction.retrieveSiteVisitStats + /// Verifies that StatsAction.retrieveSiteVisitStats returns the expected stats. + /// + func testRetrieveSiteVisitStatsReturnsExpectedFields() { + let expectation = self.expectation(description: "Retrieve site visit stats") + let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + let remoteSiteVisitStats = sampleSiteVisitStats() + + network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/visits/", filename: "site-visits") + let action = StatsAction.retrieveSiteVisitStats(siteID: sampleSiteID, granularity: .day, + latestDateToInclude: date(with: "2018-08-06T17:06:55"), quantity: 2) { (siteVisitStats, error) in + XCTAssertNil(error) + guard let siteVisitStats = siteVisitStats, + let items = siteVisitStats.items else { + XCTFail() + return + } + XCTAssertEqual(items.count, 2) + XCTAssertEqual(siteVisitStats, remoteSiteVisitStats) + expectation.fulfill() + } + + statsStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + /// Verifies that StatsAction.retrieveSiteVisitStats returns an error whenever there is an error response from the backend. /// func testRetrieveSiteVisitStatsReturnsErrorUponReponseError() { @@ -114,8 +139,8 @@ class StatsStoreTests: XCTestCase { let statsStore = StatsStore(dispatcher: dispatcher, storageManager: storageManager, network: network) network.simulateResponse(requestUrlSuffix: "sites/\(sampleSiteID)/stats/visits/", filename: "generic_error") - let action = StatsAction.retrieveSiteVisitStats(siteID: sampleSiteID, granularity: .day, - latestDateToInclude: date(with: "2018-06-23T17:06:55"), quantity: 2) { (siteVisitStats, error) in + let action = StatsAction.retrieveSiteVisitStats(siteID: sampleSiteID, granularity: .year, + latestDateToInclude: date(with: "2015-06-23T17:06:55"), quantity: 2) { (siteVisitStats, error) in XCTAssertNil(siteVisitStats) XCTAssertNotNil(error) guard let _ = error else { @@ -190,6 +215,27 @@ private extension StatsStoreTests { rawData: ["2018-06-02", 1, 1, 0, 0, 30.870000000000001, 0.87, 0, 0, 0, 0, 0, 0, "USD", 30.870000000000001, 30, 30.870000000000001, 1]) } + // MARK: - Site Visit Stats Sample + + func sampleSiteVisitStats() -> SiteVisitStats { + return SiteVisitStats(date: "2015-08-06", + granularity: .year, + fields: ["period", "views", "visitors", "likes", "reblogs", "comments", "posts"], + items: [sampleSiteVisitStatsItem1(), sampleSiteVisitStatsItem2()]) + } + + + func sampleSiteVisitStatsItem1() -> SiteVisitStatsItem { + return SiteVisitStatsItem(fieldNames: ["period", "views", "visitors", "likes", "reblogs", "comments", "posts"], + rawData: ["2014-01-01", 12821, 1135, 1094, 0, 1611, 597]) + } + + func sampleSiteVisitStatsItem2() -> SiteVisitStatsItem { + return SiteVisitStatsItem(fieldNames: ["period", "views", "visitors", "likes", "reblogs", "comments", "posts"], + rawData: ["2015-01-01", 14808, 1629, 1492, 0, 1268, 571]) + } + + // MARK: - Misc func date(with dateString: String) -> Date { From 7a73324063712d6c139906e288fcf367cf154177 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 6 Aug 2018 19:30:00 -0500 Subject: [PATCH 090/112] Fixed API path issue --- Networking/Networking/Remote/SiteVisitStatsRemote.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Networking/Networking/Remote/SiteVisitStatsRemote.swift b/Networking/Networking/Remote/SiteVisitStatsRemote.swift index c920d9baab7..f59be975913 100644 --- a/Networking/Networking/Remote/SiteVisitStatsRemote.swift +++ b/Networking/Networking/Remote/SiteVisitStatsRemote.swift @@ -20,7 +20,7 @@ public class SiteVisitStatsRemote: Remote { let parameters = [ParameterKeys.unit: unit.rawValue, ParameterKeys.date: DateFormatter.Stats.statsDayFormatter.string(from: latestDateToInclude), ParameterKeys.quantity: String(quantity)] - let request = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .get, path: path, parameters: parameters) + let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .get, path: path, parameters: parameters) let mapper = SiteVisitStatsMapper() enqueue(request, mapper: mapper, completion: completion) } From f50b7bcfffc2b1fd137fa585cae7d2d7ff3a7b6a Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 11:41:54 -0300 Subject: [PATCH 091/112] Relocates FetchedResultsControllerDelegateWrapper --- .../{ => Internal}/FetchedResultsControllerDelegateWrapper.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Yosemite/Yosemite/Tools/{ => Internal}/FetchedResultsControllerDelegateWrapper.swift (100%) diff --git a/Yosemite/Yosemite/Tools/FetchedResultsControllerDelegateWrapper.swift b/Yosemite/Yosemite/Tools/Internal/FetchedResultsControllerDelegateWrapper.swift similarity index 100% rename from Yosemite/Yosemite/Tools/FetchedResultsControllerDelegateWrapper.swift rename to Yosemite/Yosemite/Tools/Internal/FetchedResultsControllerDelegateWrapper.swift From c5e505f11d0f654399deed5892a4a125cb2fd648 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 11:42:21 -0300 Subject: [PATCH 092/112] Relocates ManagedObjectsDidChangeNotification --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 16 +++++- Yosemite/Yosemite/Tools/EntityListener.swift | 56 +++---------------- .../ManagedObjectsDidChangeNotification.swift | 46 +++++++++++++++ 3 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 Yosemite/Yosemite/Tools/Internal/ManagedObjectsDidChangeNotification.swift diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index a6c4dbeeed3..634509f7a73 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 74B7D6B020F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B7D6AF20F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift */; }; 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */; }; B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */; }; + B52E002B2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */; }; B53A56A0211245E0000776C9 /* MockupStorage+Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */; }; B53D89E520E6C22B00F90866 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53D89E420E6C22B00F90866 /* Model.swift */; }; B546CCF22093636A007CDA5F /* Yosemite.h in Headers */ = {isa = PBXBuildFile; fileRef = B546CCF12093636A007CDA5F /* Yosemite.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -83,6 +84,7 @@ 991BBCE6E4A92F0A028885D8 /* Pods_YosemiteTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_YosemiteTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ABE0D84C5393D9EE2CCF2B7E /* Pods-YosemiteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YosemiteTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-YosemiteTests/Pods-YosemiteTests.release.xcconfig"; sourceTree = ""; }; B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Site+ReadOnlyConvertible.swift"; sourceTree = ""; }; + B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectsDidChangeNotification.swift; sourceTree = ""; }; B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockupStorage+Sample.swift"; sourceTree = ""; }; B53D89E420E6C22B00F90866 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Model.swift; path = Yosemite/Model/Model.swift; sourceTree = SOURCE_ROOT; }; B546CCF12093636A007CDA5F /* Yosemite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Yosemite.h; sourceTree = ""; }; @@ -140,6 +142,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + B52E002C2119E6C000700FDE /* Internal */ = { + isa = PBXGroup; + children = ( + B5F2AE9620EBB54A00FEDC59 /* FetchedResultsControllerDelegateWrapper.swift */, + B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */, + ); + path = Internal; + sourceTree = ""; + }; B53D89E720E6C7DC00F90866 /* Model */ = { isa = PBXGroup; children = ( @@ -157,10 +168,10 @@ B56C1EBC20EABD1C00D749F9 /* Tools */ = { isa = PBXGroup; children = ( + B52E002C2119E6C000700FDE /* Internal */, + B5631ECC2114DF8C008D3535 /* EntityListener.swift */, B56C1EBD20EABD2B00D749F9 /* ResultsController.swift */, B56C1EC120EAE2E500D749F9 /* ReadOnlyConvertible.swift */, - B5F2AE9620EBB54A00FEDC59 /* FetchedResultsControllerDelegateWrapper.swift */, - B5631ECC2114DF8C008D3535 /* EntityListener.swift */, ); path = Tools; sourceTree = ""; @@ -505,6 +516,7 @@ B56C1EC220EAE2E500D749F9 /* ReadOnlyConvertible.swift in Sources */, 74A18C562113827E00DCF8A8 /* OrderStatsStore.swift in Sources */, B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */, + B52E002B2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift in Sources */, B5B5C797208E49B600642956 /* Action+Internal.swift in Sources */, 74A7688C20D45EBA00F9D437 /* OrderStore.swift in Sources */, B53D89E520E6C22B00F90866 /* Model.swift in Sources */, diff --git a/Yosemite/Yosemite/Tools/EntityListener.swift b/Yosemite/Yosemite/Tools/EntityListener.swift index 637a7645049..df9631777d0 100644 --- a/Yosemite/Yosemite/Tools/EntityListener.swift +++ b/Yosemite/Yosemite/Tools/EntityListener.swift @@ -6,7 +6,7 @@ import Storage /// EntityListener: Observes changes performed over a specified ReadOnly Entity, and executes the callback Closures, as required. /// *Note:* The type T is expected to be a ReadOnly one. /// -public class EntityListener { +public class EntityListener { /// NSManagedObjectContext associated to the Main Thread. /// @@ -95,57 +95,15 @@ private extension EntityListener { /// Returns the first NSManagedObject stored in a fiven collection, which represents the specified (ReadOnly) entity. /// - func readOnlyConvertible(from entities: Set, representing readOnlyEntity: T) -> ReadOnlyTypeErasedConvertible? { - return entities - .compactMap { entity in - return entity as? ReadOnlyTypeErasedConvertible + func readOnlyConvertible(from storageEntities: Set, representing readOnlyEntity: T) -> ReadOnlyTypeErasedConvertible? { + for case let storageEntity as ReadOnlyTypeErasedConvertible in storageEntities { + guard storageEntity.represents(readOnlyEntity: readOnlyEntity) else { + continue } - .first { readOnlyConvertible in - return readOnlyConvertible.represents(readOnlyEntity: readOnlyEntity) - } - } -} - - -// MARK: - Represents a NSManagedObjectsDidChangeNotification -// -private struct ManagedObjectsDidChangeNotification { - - /// Returns the collection of Inserted Objects. - /// - let insertedObjects: Set - - /// Returns the collection of Updated Objects. - /// - let updatedObjects: Set - /// Returns the collection of Refreshed Objects. - /// - let refreshedObjects: Set - - /// Returns the collection of Deleted Objects. - /// - let deletedObjects: Set - - /// Returns the Inserted + Updated + Refreshed Objects - /// - var upsertedObjects: Set { - return insertedObjects - .union(updatedObjects) - .union(refreshedObjects) - } - - - /// Designated Initializer - /// - init?(notification: Notification) { - guard notification.name == .NSManagedObjectContextObjectsDidChange else { - return nil + return storageEntity } - insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set ?? Set() - updatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set ?? Set() - refreshedObjects = notification.userInfo?[NSRefreshedObjectsKey] as? Set ?? Set() - deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set ?? Set() + return nil } } diff --git a/Yosemite/Yosemite/Tools/Internal/ManagedObjectsDidChangeNotification.swift b/Yosemite/Yosemite/Tools/Internal/ManagedObjectsDidChangeNotification.swift new file mode 100644 index 00000000000..ee1a65030e9 --- /dev/null +++ b/Yosemite/Yosemite/Tools/Internal/ManagedObjectsDidChangeNotification.swift @@ -0,0 +1,46 @@ +import Foundation +import CoreData + + +// MARK: - Represents a NSManagedObjectsDidChangeNotification +// +struct ManagedObjectsDidChangeNotification { + + /// Returns the collection of Inserted Objects. + /// + let insertedObjects: Set + + /// Returns the collection of Updated Objects. + /// + let updatedObjects: Set + + /// Returns the collection of Refreshed Objects. + /// + let refreshedObjects: Set + + /// Returns the collection of Deleted Objects. + /// + let deletedObjects: Set + + /// Returns the Inserted + Updated + Refreshed Objects + /// + var upsertedObjects: Set { + return insertedObjects + .union(updatedObjects) + .union(refreshedObjects) + } + + + /// Designated Initializer + /// + init?(notification: Notification) { + guard notification.name == .NSManagedObjectContextObjectsDidChange else { + return nil + } + + insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set ?? Set() + updatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set ?? Set() + refreshedObjects = notification.userInfo?[NSRefreshedObjectsKey] as? Set ?? Set() + deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set ?? Set() + } +} From 36edfd0f0d934d1d5f46beb55fd7030f1f8735d2 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 11:42:41 -0300 Subject: [PATCH 093/112] ReadOnlyTypeErasedConvertible: Removing default implementation --- Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift b/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift index 124fab8d1fc..35b38c65351 100644 --- a/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift @@ -4,7 +4,7 @@ import Storage // MARK: - Represents a Mutable Entity that can be converted into a ReadOnly Type. // -public protocol ReadOnlyConvertible: ReadOnlyTypeErasedConvertible { +public protocol ReadOnlyConvertible { /// Represents the ReadOnly Type (mirroring the receiver). /// @@ -20,22 +20,11 @@ public protocol ReadOnlyConvertible: ReadOnlyTypeErasedConvertible { } -// MARK: - ReadOnlyConvertible: ErasedConvertible Default Implementation -// -public extension ReadOnlyConvertible { - - /// Returns the (Type Erased) ReadOnly version of the receiver. - /// - public func toTypeErasedReadOnly() -> Any? { - return toReadOnly() - } -} - // MARK: - Type Erased ReadOnlyConvertible protocol, which allows us to work around several issues, triggered by // the `associatedtype` requirements. // -public protocol ReadOnlyTypeErasedConvertible: class { +public protocol ReadOnlyTypeErasedConvertible { /// Indicates if the receiver represents the Storage version of a given ReadOnly Type. /// From 8abf078d7aa1f63446a74d976e3feacaa5f32693 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 13:35:04 -0300 Subject: [PATCH 094/112] Implements TypeErasedConvertible --- Yosemite/Yosemite/Tools/EntityListener.swift | 10 +++++----- Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Yosemite/Yosemite/Tools/EntityListener.swift b/Yosemite/Yosemite/Tools/EntityListener.swift index df9631777d0..ef4b1d1e647 100644 --- a/Yosemite/Yosemite/Tools/EntityListener.swift +++ b/Yosemite/Yosemite/Tools/EntityListener.swift @@ -6,7 +6,7 @@ import Storage /// EntityListener: Observes changes performed over a specified ReadOnly Entity, and executes the callback Closures, as required. /// *Note:* The type T is expected to be a ReadOnly one. /// -public class EntityListener { +public class EntityListener { /// NSManagedObjectContext associated to the Main Thread. /// @@ -80,7 +80,7 @@ private extension EntityListener { /// Scenario: Upsert (Insert + Update + Refresh) /// if let storageEntity = readOnlyConvertible(from: note.upsertedObjects, representing: readOnlyEntity), - let updatedEntity = storageEntity.toTypeErasedReadOnly() as? T + let updatedEntity = storageEntity.toTypeErased() as? T { readOnlyEntity = updatedEntity onUpsert?(readOnlyEntity) @@ -95,9 +95,9 @@ private extension EntityListener { /// Returns the first NSManagedObject stored in a fiven collection, which represents the specified (ReadOnly) entity. /// - func readOnlyConvertible(from storageEntities: Set, representing readOnlyEntity: T) -> ReadOnlyTypeErasedConvertible? { - for case let storageEntity as ReadOnlyTypeErasedConvertible in storageEntities { - guard storageEntity.represents(readOnlyEntity: readOnlyEntity) else { + func readOnlyConvertible(from storageEntities: Set, representing readOnlyEntity: T) -> TypeErasedConvertible? { + for case let storageEntity as TypeErasedConvertible in storageEntities { + guard storageEntity.represents(entity: readOnlyEntity) else { continue } diff --git a/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift b/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift index 35b38c65351..51794a546c3 100644 --- a/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift @@ -21,16 +21,16 @@ public protocol ReadOnlyConvertible { -// MARK: - Type Erased ReadOnlyConvertible protocol, which allows us to work around several issues, triggered by -// the `associatedtype` requirements. +// MARK: - TypeErasedConvertible: Allows us to work around several ReadOnlyConvertible issues caused +// by the `associatedtype` requirements. // -public protocol ReadOnlyTypeErasedConvertible { +public protocol TypeErasedConvertible { /// Indicates if the receiver represents the Storage version of a given ReadOnly Type. /// - func represents(readOnlyEntity: Any) -> Bool + func represents(entity: Any) -> Bool /// Returns a ReadOnly version of the receiver, but with no Type Associated. /// - func toTypeErasedReadOnly() -> Any? + func toTypeErased() -> Any? } From 752ed2f74ff51f96dda0840ce9e1054a83bc034a Mon Sep 17 00:00:00 2001 From: "Thuy.Copeland" Date: Tue, 7 Aug 2018 11:43:39 -0500 Subject: [PATCH 095/112] Point to new WordPressAuthenticator version 1.0.5, `pod install`. --- Podfile | 2 +- Podfile.lock | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Podfile b/Podfile index 52e357ab6ba..e3bcea809f4 100644 --- a/Podfile +++ b/Podfile @@ -18,7 +18,7 @@ target 'WooCommerce' do # pod 'Automattic-Tracks-iOS', :git => 'https://github.com/Automattic/Automattic-Tracks-iOS.git', :tag => '0.2.3' pod 'Gridicons', '0.15' - pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :branch => 'feature/more-configurations' + pod 'WordPressAuthenticator', '1.0.5' pod 'WordPressShared', '1.0.8' diff --git a/Podfile.lock b/Podfile.lock index b026d39253f..e996360ed7f 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -38,7 +38,7 @@ PODS: - Reachability (3.2) - SVProgressHUD (2.2.5) - UIDeviceIdentifier (0.5.0) - - WordPressAuthenticator (1.0.4): + - WordPressAuthenticator (1.0.5): - 1PasswordExtension (= 1.8.5) - Alamofire (= 4.7.2) - CocoaLumberjack (= 3.4.2) @@ -52,7 +52,7 @@ PODS: - WordPressShared (~> 1.0) - WordPressUI (~> 1.0) - wpxmlrpc (~> 0.8) - - WordPressKit (1.2): + - WordPressKit (1.2.1): - Alamofire (~> 4.7) - CocoaLumberjack (= 3.4.2) - NSObject-SafeExpectations (= 0.0.3) @@ -72,7 +72,7 @@ DEPENDENCIES: - Crashlytics (~> 3.10) - Gridicons (= 0.15) - KeychainAccess (~> 3.1) - - WordPressAuthenticator (from `https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git`, branch `feature/more-configurations`) + - WordPressAuthenticator (= 1.0.5) - WordPressShared (= 1.0.8) SPEC REPOS: @@ -93,6 +93,7 @@ SPEC REPOS: - Reachability - SVProgressHUD - UIDeviceIdentifier + - WordPressAuthenticator - WordPressKit - WordPressShared - WordPressUI @@ -102,17 +103,11 @@ EXTERNAL SOURCES: Automattic-Tracks-iOS: :git: https://github.com/Automattic/Automattic-Tracks-iOS.git :tag: 0.2.3 - WordPressAuthenticator: - :branch: feature/more-configurations - :git: https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git CHECKOUT OPTIONS: Automattic-Tracks-iOS: :git: https://github.com/Automattic/Automattic-Tracks-iOS.git :tag: 0.2.3 - WordPressAuthenticator: - :commit: 07d999ab6c731288eeef369f66d8d73ed1f187e6 - :git: https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git SPEC CHECKSUMS: 1PasswordExtension: 0e95bdea64ec8ff2f4f693be5467a09fac42a83d @@ -132,12 +127,12 @@ SPEC CHECKSUMS: Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 UIDeviceIdentifier: a959a6d4f51036b4180dd31fb26483a820f1cc46 - WordPressAuthenticator: 2825f0c56f83a17470564dbec427991fa5cac5af - WordPressKit: 68eaa8df5ceedeed03ba796afc4b825f0bed4fe2 + WordPressAuthenticator: e6e1c80aff95f1b2ad11e4477fcfe9f61aa8c49a + WordPressKit: a4a3849684f631a3abf579f6d3f15a32677cbb30 WordPressShared: 063e1e8b1a7aaf635abf17f091a2d235a068abdc WordPressUI: af141587ec444f9af753a00605bd0d3f14d8d8a3 wpxmlrpc: bfc572f62ce7ee897f6f38b098d2ba08732ecef4 -PODFILE CHECKSUM: 9227f0f9d64792cf9f874a21f7ce9f2943065b29 +PODFILE CHECKSUM: 207ef5c6f051a58ab26a9e514f2e800ee07cbcf2 COCOAPODS: 1.5.3 From 3b97541529b7d60bf6658587957efd3b09ccec1b Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 17:06:07 -0300 Subject: [PATCH 096/112] ReadOnlyConvertible: Cleanup --- .../Yosemite/Tools/ReadOnlyConvertible.swift | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift b/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift index 51794a546c3..ed088bea7e6 100644 --- a/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Tools/ReadOnlyConvertible.swift @@ -4,7 +4,7 @@ import Storage // MARK: - Represents a Mutable Entity that can be converted into a ReadOnly Type. // -public protocol ReadOnlyConvertible { +public protocol ReadOnlyConvertible: TypeErasedReadOnlyConvertible { /// Represents the ReadOnly Type (mirroring the receiver). /// @@ -20,17 +20,24 @@ public protocol ReadOnlyConvertible { } - -// MARK: - TypeErasedConvertible: Allows us to work around several ReadOnlyConvertible issues caused -// by the `associatedtype` requirements. +// MARK: - ReadOnlyConvertible TypeErasure Workaround. +// This allows us to cast an entity that conforms to ReadOnlyConvertible, without hitting any `associatedtype` issues. // -public protocol TypeErasedConvertible { +public protocol TypeErasedReadOnlyConvertible { - /// Indicates if the receiver represents the Storage version of a given ReadOnly Type. + /// Returns a ReadOnly version of the receiver, but with no Type associated. /// - func represents(entity: Any) -> Bool + func toTypeErasedReadOnly() -> Any +} + + +// MARK: - ReadOnlyConvertible TypeErased Conformance. +// +extension ReadOnlyConvertible { - /// Returns a ReadOnly version of the receiver, but with no Type Associated. + /// Returns a ReadOnly version of the receiver, but with no Type associated. /// - func toTypeErased() -> Any? + public func toTypeErasedReadOnly() -> Any { + return toReadOnly() + } } From dbed12dd17d935dc87ca8a79d0d9e890e389866a Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 17:06:32 -0300 Subject: [PATCH 097/112] EntityListener: Wiring TypeErased protocol --- Yosemite/Yosemite/Tools/EntityListener.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Yosemite/Yosemite/Tools/EntityListener.swift b/Yosemite/Yosemite/Tools/EntityListener.swift index ef4b1d1e647..f87a70fa7fe 100644 --- a/Yosemite/Yosemite/Tools/EntityListener.swift +++ b/Yosemite/Yosemite/Tools/EntityListener.swift @@ -80,7 +80,7 @@ private extension EntityListener { /// Scenario: Upsert (Insert + Update + Refresh) /// if let storageEntity = readOnlyConvertible(from: note.upsertedObjects, representing: readOnlyEntity), - let updatedEntity = storageEntity.toTypeErased() as? T + let updatedEntity = storageEntity.toTypeErasedReadOnly() as? T { readOnlyEntity = updatedEntity onUpsert?(readOnlyEntity) @@ -95,9 +95,9 @@ private extension EntityListener { /// Returns the first NSManagedObject stored in a fiven collection, which represents the specified (ReadOnly) entity. /// - func readOnlyConvertible(from storageEntities: Set, representing readOnlyEntity: T) -> TypeErasedConvertible? { - for case let storageEntity as TypeErasedConvertible in storageEntities { - guard storageEntity.represents(entity: readOnlyEntity) else { + func readOnlyConvertible(from storageEntities: Set, representing readOnlyEntity: T) -> TypeErasedReadOnlyConvertible? { + for case let storageEntity as TypeErasedReadOnlyConvertible in storageEntities { + guard storageEntity.represents(readOnlyEntity: readOnlyEntity) else { continue } From 8380ef8a97ec208b8f0e71d6b29cae39e0dcd87e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 18:00:46 -0300 Subject: [PATCH 098/112] Implements ReadOnlyRepresentation --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 4 ++++ Yosemite/Yosemite/Tools/EntityListener.swift | 4 ++-- Yosemite/Yosemite/Tools/ReadOnlyRepresentation.swift | 12 ++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 Yosemite/Yosemite/Tools/ReadOnlyRepresentation.swift diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 634509f7a73..8d9cefaf3b7 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */; }; B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */; }; B52E002B2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */; }; + B52E002E211A3F5500700FDE /* ReadOnlyRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002D211A3F5500700FDE /* ReadOnlyRepresentation.swift */; }; B53A56A0211245E0000776C9 /* MockupStorage+Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */; }; B53D89E520E6C22B00F90866 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53D89E420E6C22B00F90866 /* Model.swift */; }; B546CCF22093636A007CDA5F /* Yosemite.h in Headers */ = {isa = PBXBuildFile; fileRef = B546CCF12093636A007CDA5F /* Yosemite.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -85,6 +86,7 @@ ABE0D84C5393D9EE2CCF2B7E /* Pods-YosemiteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YosemiteTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-YosemiteTests/Pods-YosemiteTests.release.xcconfig"; sourceTree = ""; }; B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Site+ReadOnlyConvertible.swift"; sourceTree = ""; }; B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectsDidChangeNotification.swift; sourceTree = ""; }; + B52E002D211A3F5500700FDE /* ReadOnlyRepresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnlyRepresentation.swift; sourceTree = ""; }; B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockupStorage+Sample.swift"; sourceTree = ""; }; B53D89E420E6C22B00F90866 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Model.swift; path = Yosemite/Model/Model.swift; sourceTree = SOURCE_ROOT; }; B546CCF12093636A007CDA5F /* Yosemite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Yosemite.h; sourceTree = ""; }; @@ -172,6 +174,7 @@ B5631ECC2114DF8C008D3535 /* EntityListener.swift */, B56C1EBD20EABD2B00D749F9 /* ResultsController.swift */, B56C1EC120EAE2E500D749F9 /* ReadOnlyConvertible.swift */, + B52E002D211A3F5500700FDE /* ReadOnlyRepresentation.swift */, ); path = Tools; sourceTree = ""; @@ -525,6 +528,7 @@ 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */, B5C9DE182087FF0E006B910A /* Assert.swift in Sources */, 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */, + B52E002E211A3F5500700FDE /* ReadOnlyRepresentation.swift in Sources */, B5C9DE162087FF0E006B910A /* Store.swift in Sources */, B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */, B5EED1A820F4F3CF00652449 /* Account+ReadOnlyConvertible.swift in Sources */, diff --git a/Yosemite/Yosemite/Tools/EntityListener.swift b/Yosemite/Yosemite/Tools/EntityListener.swift index f87a70fa7fe..0d559195fa1 100644 --- a/Yosemite/Yosemite/Tools/EntityListener.swift +++ b/Yosemite/Yosemite/Tools/EntityListener.swift @@ -6,7 +6,7 @@ import Storage /// EntityListener: Observes changes performed over a specified ReadOnly Entity, and executes the callback Closures, as required. /// *Note:* The type T is expected to be a ReadOnly one. /// -public class EntityListener { +public class EntityListener { /// NSManagedObjectContext associated to the Main Thread. /// @@ -97,7 +97,7 @@ private extension EntityListener { /// func readOnlyConvertible(from storageEntities: Set, representing readOnlyEntity: T) -> TypeErasedReadOnlyConvertible? { for case let storageEntity as TypeErasedReadOnlyConvertible in storageEntities { - guard storageEntity.represents(readOnlyEntity: readOnlyEntity) else { + guard readOnlyEntity.isReadOnlyRepresentation(of: storageEntity) else { continue } diff --git a/Yosemite/Yosemite/Tools/ReadOnlyRepresentation.swift b/Yosemite/Yosemite/Tools/ReadOnlyRepresentation.swift new file mode 100644 index 00000000000..6889e5209ff --- /dev/null +++ b/Yosemite/Yosemite/Tools/ReadOnlyRepresentation.swift @@ -0,0 +1,12 @@ +import Foundation +import Storage + + +// +// +public protocol ReadOnlyRepresentation { + + /// + /// + func isReadOnlyRepresentation(of storageEntity: Any) -> Bool +} From f39b1751d0d1887127f1c4de520466a05b234aca Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 18:13:57 -0300 Subject: [PATCH 099/112] ReadOnlyRepresentation > ReadOnlyType --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 8 ++++---- Yosemite/Yosemite/Tools/EntityListener.swift | 2 +- .../{ReadOnlyRepresentation.swift => ReadOnlyType.swift} | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename Yosemite/Yosemite/Tools/{ReadOnlyRepresentation.swift => ReadOnlyType.swift} (75%) diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index 8d9cefaf3b7..cfcc11f676c 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -24,7 +24,7 @@ 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */; }; B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */; }; B52E002B2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */; }; - B52E002E211A3F5500700FDE /* ReadOnlyRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002D211A3F5500700FDE /* ReadOnlyRepresentation.swift */; }; + B52E002E211A3F5500700FDE /* ReadOnlyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002D211A3F5500700FDE /* ReadOnlyType.swift */; }; B53A56A0211245E0000776C9 /* MockupStorage+Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */; }; B53D89E520E6C22B00F90866 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53D89E420E6C22B00F90866 /* Model.swift */; }; B546CCF22093636A007CDA5F /* Yosemite.h in Headers */ = {isa = PBXBuildFile; fileRef = B546CCF12093636A007CDA5F /* Yosemite.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -86,7 +86,7 @@ ABE0D84C5393D9EE2CCF2B7E /* Pods-YosemiteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YosemiteTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-YosemiteTests/Pods-YosemiteTests.release.xcconfig"; sourceTree = ""; }; B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Site+ReadOnlyConvertible.swift"; sourceTree = ""; }; B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectsDidChangeNotification.swift; sourceTree = ""; }; - B52E002D211A3F5500700FDE /* ReadOnlyRepresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnlyRepresentation.swift; sourceTree = ""; }; + B52E002D211A3F5500700FDE /* ReadOnlyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnlyType.swift; sourceTree = ""; }; B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockupStorage+Sample.swift"; sourceTree = ""; }; B53D89E420E6C22B00F90866 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Model.swift; path = Yosemite/Model/Model.swift; sourceTree = SOURCE_ROOT; }; B546CCF12093636A007CDA5F /* Yosemite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Yosemite.h; sourceTree = ""; }; @@ -174,7 +174,7 @@ B5631ECC2114DF8C008D3535 /* EntityListener.swift */, B56C1EBD20EABD2B00D749F9 /* ResultsController.swift */, B56C1EC120EAE2E500D749F9 /* ReadOnlyConvertible.swift */, - B52E002D211A3F5500700FDE /* ReadOnlyRepresentation.swift */, + B52E002D211A3F5500700FDE /* ReadOnlyType.swift */, ); path = Tools; sourceTree = ""; @@ -528,7 +528,7 @@ 7499936620EFBC7200CF01CD /* OrderNoteStore.swift in Sources */, B5C9DE182087FF0E006B910A /* Assert.swift in Sources */, 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */, - B52E002E211A3F5500700FDE /* ReadOnlyRepresentation.swift in Sources */, + B52E002E211A3F5500700FDE /* ReadOnlyType.swift in Sources */, B5C9DE162087FF0E006B910A /* Store.swift in Sources */, B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */, B5EED1A820F4F3CF00652449 /* Account+ReadOnlyConvertible.swift in Sources */, diff --git a/Yosemite/Yosemite/Tools/EntityListener.swift b/Yosemite/Yosemite/Tools/EntityListener.swift index 0d559195fa1..a08ab02a5bc 100644 --- a/Yosemite/Yosemite/Tools/EntityListener.swift +++ b/Yosemite/Yosemite/Tools/EntityListener.swift @@ -6,7 +6,7 @@ import Storage /// EntityListener: Observes changes performed over a specified ReadOnly Entity, and executes the callback Closures, as required. /// *Note:* The type T is expected to be a ReadOnly one. /// -public class EntityListener { +public class EntityListener { /// NSManagedObjectContext associated to the Main Thread. /// diff --git a/Yosemite/Yosemite/Tools/ReadOnlyRepresentation.swift b/Yosemite/Yosemite/Tools/ReadOnlyType.swift similarity index 75% rename from Yosemite/Yosemite/Tools/ReadOnlyRepresentation.swift rename to Yosemite/Yosemite/Tools/ReadOnlyType.swift index 6889e5209ff..983943e4a39 100644 --- a/Yosemite/Yosemite/Tools/ReadOnlyRepresentation.swift +++ b/Yosemite/Yosemite/Tools/ReadOnlyType.swift @@ -4,7 +4,7 @@ import Storage // // -public protocol ReadOnlyRepresentation { +public protocol ReadOnlyType { /// /// From e120351280aee04ab5c302c4011f2a2ea15c736f Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 18:21:41 -0300 Subject: [PATCH 100/112] Implements ReadOnlyType Conformance --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 12 ++++++++++++ .../Model/Account+ReadOnlyConvertible.swift | 10 ---------- .../Yosemite/Model/Account+ReadOnlyType.swift | 18 ++++++++++++++++++ .../Model/Order+ReadOnlyConvertible.swift | 10 ---------- .../Yosemite/Model/Order+ReadOnlyType.swift | 18 ++++++++++++++++++ .../OrderCoupon+ReadOnlyConvertible.swift | 11 ----------- .../Model/OrderItem+ReadOnlyConvertible.swift | 11 ----------- .../Model/OrderNote+ReadOnlyConvertible.swift | 11 ----------- .../Model/Site+ReadOnlyConvertible.swift | 10 ---------- .../Yosemite/Model/Site+ReadOnlyType.swift | 18 ++++++++++++++++++ 10 files changed, 66 insertions(+), 63 deletions(-) create mode 100644 Yosemite/Yosemite/Model/Account+ReadOnlyType.swift create mode 100644 Yosemite/Yosemite/Model/Order+ReadOnlyType.swift create mode 100644 Yosemite/Yosemite/Model/Site+ReadOnlyType.swift diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index cfcc11f676c..e1254812c05 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -25,6 +25,9 @@ B505254C20EE6491008090F5 /* Site+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */; }; B52E002B2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */; }; B52E002E211A3F5500700FDE /* ReadOnlyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002D211A3F5500700FDE /* ReadOnlyType.swift */; }; + B52E0030211A439E00700FDE /* Account+ReadOnlyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E002F211A439E00700FDE /* Account+ReadOnlyType.swift */; }; + B52E0032211A440D00700FDE /* Order+ReadOnlyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E0031211A440D00700FDE /* Order+ReadOnlyType.swift */; }; + B52E0034211A449600700FDE /* Site+ReadOnlyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E0033211A449600700FDE /* Site+ReadOnlyType.swift */; }; B53A56A0211245E0000776C9 /* MockupStorage+Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */; }; B53D89E520E6C22B00F90866 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53D89E420E6C22B00F90866 /* Model.swift */; }; B546CCF22093636A007CDA5F /* Yosemite.h in Headers */ = {isa = PBXBuildFile; fileRef = B546CCF12093636A007CDA5F /* Yosemite.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -87,6 +90,9 @@ B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Site+ReadOnlyConvertible.swift"; sourceTree = ""; }; B52E002A2119E64800700FDE /* ManagedObjectsDidChangeNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectsDidChangeNotification.swift; sourceTree = ""; }; B52E002D211A3F5500700FDE /* ReadOnlyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnlyType.swift; sourceTree = ""; }; + B52E002F211A439E00700FDE /* Account+ReadOnlyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+ReadOnlyType.swift"; sourceTree = ""; }; + B52E0031211A440D00700FDE /* Order+ReadOnlyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Order+ReadOnlyType.swift"; sourceTree = ""; }; + B52E0033211A449600700FDE /* Site+ReadOnlyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Site+ReadOnlyType.swift"; sourceTree = ""; }; B53A569F211245E0000776C9 /* MockupStorage+Sample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockupStorage+Sample.swift"; sourceTree = ""; }; B53D89E420E6C22B00F90866 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Model.swift; path = Yosemite/Model/Model.swift; sourceTree = SOURCE_ROOT; }; B546CCF12093636A007CDA5F /* Yosemite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Yosemite.h; sourceTree = ""; }; @@ -158,11 +164,14 @@ children = ( B53D89E420E6C22B00F90866 /* Model.swift */, B5EED1A720F4F3CF00652449 /* Account+ReadOnlyConvertible.swift */, + B52E002F211A439E00700FDE /* Account+ReadOnlyType.swift */, 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */, + B52E0031211A440D00700FDE /* Order+ReadOnlyType.swift */, 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */, 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */, 74B7D6AF20F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift */, B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */, + B52E0033211A449600700FDE /* Site+ReadOnlyType.swift */, ); path = Model; sourceTree = ""; @@ -510,7 +519,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B52E0032211A440D00700FDE /* Order+ReadOnlyType.swift in Sources */, B5C9DE152087FF0E006B910A /* Dispatcher.swift in Sources */, + B52E0030211A439E00700FDE /* Account+ReadOnlyType.swift in Sources */, 74685D4E20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift in Sources */, 74B7D6B020F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift in Sources */, B5631ECD2114DF8C008D3535 /* EntityListener.swift in Sources */, @@ -530,6 +541,7 @@ 74D7F29B20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift in Sources */, B52E002E211A3F5500700FDE /* ReadOnlyType.swift in Sources */, B5C9DE162087FF0E006B910A /* Store.swift in Sources */, + B52E0034211A449600700FDE /* Site+ReadOnlyType.swift in Sources */, B56C1EBE20EABD2B00D749F9 /* ResultsController.swift in Sources */, B5EED1A820F4F3CF00652449 /* Account+ReadOnlyConvertible.swift in Sources */, B5F2AE9720EBB54A00FEDC59 /* FetchedResultsControllerDelegateWrapper.swift in Sources */, diff --git a/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift index b9474ba1c93..73a633f8a66 100644 --- a/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift @@ -6,16 +6,6 @@ import Storage // extension Storage.Account: ReadOnlyConvertible { - /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. - /// - public func represents(readOnlyEntity: Any) -> Bool { - guard let readOnlyAccount = readOnlyEntity as? Yosemite.Account else { - return false - } - - return readOnlyAccount.userID == Int(userID) - } - /// Updates the Storage.Account with the a ReadOnly. /// public func update(with account: Yosemite.Account) { diff --git a/Yosemite/Yosemite/Model/Account+ReadOnlyType.swift b/Yosemite/Yosemite/Model/Account+ReadOnlyType.swift new file mode 100644 index 00000000000..d999f777e9c --- /dev/null +++ b/Yosemite/Yosemite/Model/Account+ReadOnlyType.swift @@ -0,0 +1,18 @@ +import Foundation +import Storage + + +// MARK: - Yosemite.Account: ReadOnlyType +// +extension Yosemite.Account: ReadOnlyType { + + /// Indicates if the receiver is a representation of a specified Storage.Entity instance. + /// + public func isReadOnlyRepresentation(of storageEntity: Any) -> Bool { + guard let storageAccount = storageEntity as? Storage.Account else { + return false + } + + return Int(storageAccount.userID) == userID + } +} diff --git a/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift index f8452d88912..0defe0acc9f 100644 --- a/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift @@ -6,16 +6,6 @@ import Storage // extension Storage.Order: ReadOnlyConvertible { - /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. - /// - public func represents(readOnlyEntity: Any) -> Bool { - guard let readOnlyOrder = readOnlyEntity as? Yosemite.Order else { - return false - } - - return readOnlyOrder.siteID == Int(siteID) && readOnlyOrder.orderID == Int(orderID) - } - /// Updates the Storage.Order with the ReadOnly. /// public func update(with order: Yosemite.Order) { diff --git a/Yosemite/Yosemite/Model/Order+ReadOnlyType.swift b/Yosemite/Yosemite/Model/Order+ReadOnlyType.swift new file mode 100644 index 00000000000..1ea3e784e02 --- /dev/null +++ b/Yosemite/Yosemite/Model/Order+ReadOnlyType.swift @@ -0,0 +1,18 @@ +import Foundation +import Storage + + +// MARK: - Yosemite.Order: ReadOnlyType +// +extension Yosemite.Order: ReadOnlyType { + + /// Indicates if the receiver is a representation of a specified Storage.Entity instance. + /// + public func isReadOnlyRepresentation(of storageEntity: Any) -> Bool { + guard let storageOrder = storageEntity as? Storage.Order else { + return false + } + + return siteID == Int(storageOrder.siteID) && orderID == Int(storageOrder.orderID) + } +} diff --git a/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift index 4f2d6bffc33..8e513589622 100644 --- a/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift @@ -6,17 +6,6 @@ import Storage // extension Storage.OrderCoupon: ReadOnlyConvertible { - /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. - /// - public func represents(readOnlyEntity: Any) -> Bool { - guard let readOnlyCoupon = readOnlyEntity as? Yosemite.OrderCouponLine else { - return false - } - -// TODO: Add order.orderID + order.siteID Check - return readOnlyCoupon.couponID == Int(couponID) - } - /// Updates the Storage.OrderCoupon with the ReadOnly. /// public func update(with orderCoupon: Yosemite.OrderCouponLine) { diff --git a/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift index a7b14bc7594..e25a65b2149 100644 --- a/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift @@ -6,17 +6,6 @@ import Storage // extension Storage.OrderItem: ReadOnlyConvertible { - /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. - /// - public func represents(readOnlyEntity: Any) -> Bool { - guard let readOnlyItem = readOnlyEntity as? Yosemite.OrderItem else { - return false - } - -// TODO: Add order.orderID + order.siteID Check - return readOnlyItem.itemID == Int(itemID) - } - /// Updates the Storage.OrderItem with the ReadOnly. /// public func update(with orderItem: Yosemite.OrderItem) { diff --git a/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift index 53de811bcf0..3a881176439 100644 --- a/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift @@ -6,17 +6,6 @@ import Storage // extension Storage.OrderNote: ReadOnlyConvertible { - /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. - /// - public func represents(readOnlyEntity: Any) -> Bool { - guard let readOnlyNote = readOnlyEntity as? Yosemite.OrderNote else { - return false - } - -// TODO: Add order.orderID + order.siteID Check - return readOnlyNote.noteID == Int(noteID) - } - /// Updates the Storage.OrderCoupon with the ReadOnly. /// public func update(with orderNote: Yosemite.OrderNote) { diff --git a/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift index 427d3cffc2a..1d06e876644 100644 --- a/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift +++ b/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift @@ -6,16 +6,6 @@ import Storage // extension Storage.Site: ReadOnlyConvertible { - /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. - /// - public func represents(readOnlyEntity: Any) -> Bool { - guard let readOnlySite = readOnlyEntity as? Yosemite.Site else { - return false - } - - return readOnlySite.siteID == Int(siteID) - } - /// Updates the Storage.Site with the a ReadOnly. /// public func update(with site: Yosemite.Site) { diff --git a/Yosemite/Yosemite/Model/Site+ReadOnlyType.swift b/Yosemite/Yosemite/Model/Site+ReadOnlyType.swift new file mode 100644 index 00000000000..932049d8504 --- /dev/null +++ b/Yosemite/Yosemite/Model/Site+ReadOnlyType.swift @@ -0,0 +1,18 @@ +import Foundation +import Storage + + +// MARK: - Yosemite.Site: ReadOnlyType +// +extension Yosemite.Site: ReadOnlyType { + + /// Indicates if the receiver is the Storage.Entity, backing up the specified ReadOnly.Entity. + /// + public func isReadOnlyRepresentation(of storageEntity: Any) -> Bool { + guard let storageSite = storageEntity as? Storage.Site else { + return false + } + + return siteID == Int(storageSite.siteID) + } +} From 76a34e3587883f7305ef78b349c38dfdce0af9e9 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 18:22:32 -0300 Subject: [PATCH 101/112] Relocates Model Files --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 24 +++++++++++++++---- .../{ => ReadOnly}/Account+ReadOnlyType.swift | 0 .../{ => ReadOnly}/Order+ReadOnlyType.swift | 0 .../{ => ReadOnly}/Site+ReadOnlyType.swift | 0 .../Account+ReadOnlyConvertible.swift | 0 .../Order+ReadOnlyConvertible.swift | 0 .../OrderCoupon+ReadOnlyConvertible.swift | 0 .../OrderItem+ReadOnlyConvertible.swift | 0 .../OrderNote+ReadOnlyConvertible.swift | 0 .../Site+ReadOnlyConvertible.swift | 0 10 files changed, 20 insertions(+), 4 deletions(-) rename Yosemite/Yosemite/Model/{ => ReadOnly}/Account+ReadOnlyType.swift (100%) rename Yosemite/Yosemite/Model/{ => ReadOnly}/Order+ReadOnlyType.swift (100%) rename Yosemite/Yosemite/Model/{ => ReadOnly}/Site+ReadOnlyType.swift (100%) rename Yosemite/Yosemite/Model/{ => Storage}/Account+ReadOnlyConvertible.swift (100%) rename Yosemite/Yosemite/Model/{ => Storage}/Order+ReadOnlyConvertible.swift (100%) rename Yosemite/Yosemite/Model/{ => Storage}/OrderCoupon+ReadOnlyConvertible.swift (100%) rename Yosemite/Yosemite/Model/{ => Storage}/OrderItem+ReadOnlyConvertible.swift (100%) rename Yosemite/Yosemite/Model/{ => Storage}/OrderNote+ReadOnlyConvertible.swift (100%) rename Yosemite/Yosemite/Model/{ => Storage}/Site+ReadOnlyConvertible.swift (100%) diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index e1254812c05..477d6e11015 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -159,20 +159,36 @@ path = Internal; sourceTree = ""; }; - B53D89E720E6C7DC00F90866 /* Model */ = { + B52E0035211A44F800700FDE /* Storage */ = { isa = PBXGroup; children = ( - B53D89E420E6C22B00F90866 /* Model.swift */, B5EED1A720F4F3CF00652449 /* Account+ReadOnlyConvertible.swift */, - B52E002F211A439E00700FDE /* Account+ReadOnlyType.swift */, 74D7F29A20F6A7FB0058B2F0 /* Order+ReadOnlyConvertible.swift */, - B52E0031211A440D00700FDE /* Order+ReadOnlyType.swift */, 74685D4F20F7F3CE008958C1 /* OrderCoupon+ReadOnlyConvertible.swift */, 74685D4D20F7EFA7008958C1 /* OrderItem+ReadOnlyConvertible.swift */, 74B7D6AF20F910AF002667AC /* OrderNote+ReadOnlyConvertible.swift */, B505254B20EE6491008090F5 /* Site+ReadOnlyConvertible.swift */, + ); + path = Storage; + sourceTree = ""; + }; + B52E0036211A44FE00700FDE /* ReadOnly */ = { + isa = PBXGroup; + children = ( + B52E002F211A439E00700FDE /* Account+ReadOnlyType.swift */, + B52E0031211A440D00700FDE /* Order+ReadOnlyType.swift */, B52E0033211A449600700FDE /* Site+ReadOnlyType.swift */, ); + path = ReadOnly; + sourceTree = ""; + }; + B53D89E720E6C7DC00F90866 /* Model */ = { + isa = PBXGroup; + children = ( + B52E0036211A44FE00700FDE /* ReadOnly */, + B52E0035211A44F800700FDE /* Storage */, + B53D89E420E6C22B00F90866 /* Model.swift */, + ); path = Model; sourceTree = ""; }; diff --git a/Yosemite/Yosemite/Model/Account+ReadOnlyType.swift b/Yosemite/Yosemite/Model/ReadOnly/Account+ReadOnlyType.swift similarity index 100% rename from Yosemite/Yosemite/Model/Account+ReadOnlyType.swift rename to Yosemite/Yosemite/Model/ReadOnly/Account+ReadOnlyType.swift diff --git a/Yosemite/Yosemite/Model/Order+ReadOnlyType.swift b/Yosemite/Yosemite/Model/ReadOnly/Order+ReadOnlyType.swift similarity index 100% rename from Yosemite/Yosemite/Model/Order+ReadOnlyType.swift rename to Yosemite/Yosemite/Model/ReadOnly/Order+ReadOnlyType.swift diff --git a/Yosemite/Yosemite/Model/Site+ReadOnlyType.swift b/Yosemite/Yosemite/Model/ReadOnly/Site+ReadOnlyType.swift similarity index 100% rename from Yosemite/Yosemite/Model/Site+ReadOnlyType.swift rename to Yosemite/Yosemite/Model/ReadOnly/Site+ReadOnlyType.swift diff --git a/Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/Account+ReadOnlyConvertible.swift similarity index 100% rename from Yosemite/Yosemite/Model/Account+ReadOnlyConvertible.swift rename to Yosemite/Yosemite/Model/Storage/Account+ReadOnlyConvertible.swift diff --git a/Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/Order+ReadOnlyConvertible.swift similarity index 100% rename from Yosemite/Yosemite/Model/Order+ReadOnlyConvertible.swift rename to Yosemite/Yosemite/Model/Storage/Order+ReadOnlyConvertible.swift diff --git a/Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/OrderCoupon+ReadOnlyConvertible.swift similarity index 100% rename from Yosemite/Yosemite/Model/OrderCoupon+ReadOnlyConvertible.swift rename to Yosemite/Yosemite/Model/Storage/OrderCoupon+ReadOnlyConvertible.swift diff --git a/Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/OrderItem+ReadOnlyConvertible.swift similarity index 100% rename from Yosemite/Yosemite/Model/OrderItem+ReadOnlyConvertible.swift rename to Yosemite/Yosemite/Model/Storage/OrderItem+ReadOnlyConvertible.swift diff --git a/Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/OrderNote+ReadOnlyConvertible.swift similarity index 100% rename from Yosemite/Yosemite/Model/OrderNote+ReadOnlyConvertible.swift rename to Yosemite/Yosemite/Model/Storage/OrderNote+ReadOnlyConvertible.swift diff --git a/Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift b/Yosemite/Yosemite/Model/Storage/Site+ReadOnlyConvertible.swift similarity index 100% rename from Yosemite/Yosemite/Model/Site+ReadOnlyConvertible.swift rename to Yosemite/Yosemite/Model/Storage/Site+ReadOnlyConvertible.swift From 08a0aef2cf61d357a2c06ef2ec29c9db092b9d00 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Tue, 7 Aug 2018 18:28:35 -0300 Subject: [PATCH 102/112] ReadOnlyType: Adds missing documentation --- Yosemite/Yosemite/Tools/ReadOnlyType.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Yosemite/Yosemite/Tools/ReadOnlyType.swift b/Yosemite/Yosemite/Tools/ReadOnlyType.swift index 983943e4a39..e90344709c3 100644 --- a/Yosemite/Yosemite/Tools/ReadOnlyType.swift +++ b/Yosemite/Yosemite/Tools/ReadOnlyType.swift @@ -2,11 +2,11 @@ import Foundation import Storage -// +// MARK: - ReadOnlyType: Represents an Entity that cannot be modified. // public protocol ReadOnlyType { - /// + /// Indicates if the receiver is the "ReadOnly Version" of a given Storage.Entity instance. /// func isReadOnlyRepresentation(of storageEntity: Any) -> Bool } From 962559f27ec95bcb1886ace1420e261b74176c63 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 8 Aug 2018 11:51:54 -0300 Subject: [PATCH 103/112] WooAnalyticsTests: Fixing typo --- WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift b/WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift index 00b65c069dc..707c5f72a1f 100644 --- a/WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift +++ b/WooCommerce/WooCommerceTests/System/WooAnalyticsTests.swift @@ -74,7 +74,7 @@ class WooAnalyticsTests: XCTestCase { return } - /// Note: iOS 12 is shuffling several dictionaries (specially when it comes to serializing [:] > URL Parameters). + /// Note: iOS 12 is shuffling several dictionaries (especially when it comes to serializing [:] > URL Parameters). /// For that reason, we'll proceed with a bit of a more lengthy but robust check. /// for (key, value) in receivedProperty1 { From e615bdda35dfe957dc7b538c2d4ddb1b73537e39 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 8 Aug 2018 18:12:30 -0300 Subject: [PATCH 104/112] OrderListMapper: New Unit Test --- .../Networking.xcodeproj/project.pbxproj | 4 + .../Mapper/OrderListMapperTests.swift | 23 +- .../Responses/broken-orders-mark-2.json | 614 ++++++++++++++++++ 3 files changed, 636 insertions(+), 5 deletions(-) create mode 100644 Networking/NetworkingTests/Responses/broken-orders-mark-2.json diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index fe0496a3961..8a1418296d5 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ B505F6D720BEE58800BB1B69 /* AccountRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B505F6D620BEE58800BB1B69 /* AccountRemoteTests.swift */; }; B505F6EA20BEFC3700BB1B69 /* MockupNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518662620A09BCC00037A38 /* MockupNetwork.swift */; }; B505F6EC20BEFDC200BB1B69 /* Loader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518663420A0A2E800037A38 /* Loader.swift */; }; + B5147876211B9227007562E5 /* broken-orders-mark-2.json in Resources */ = {isa = PBXBuildFile; fileRef = B5147875211B9227007562E5 /* broken-orders-mark-2.json */; }; B518662220A097C200037A38 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518662120A097C200037A38 /* Network.swift */; }; B518662420A099BF00037A38 /* AlamofireNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518662320A099BF00037A38 /* AlamofireNetwork.swift */; }; B518662A20A09C6F00037A38 /* OrdersRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518662920A09C6F00037A38 /* OrdersRemoteTests.swift */; }; @@ -146,6 +147,7 @@ B505F6D220BEE3A500BB1B69 /* AccountMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountMapperTests.swift; sourceTree = ""; }; B505F6D420BEE4E600BB1B69 /* me.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = me.json; sourceTree = ""; }; B505F6D620BEE58800BB1B69 /* AccountRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRemoteTests.swift; sourceTree = ""; }; + B5147875211B9227007562E5 /* broken-orders-mark-2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "broken-orders-mark-2.json"; sourceTree = ""; }; B518662120A097C200037A38 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; B518662320A099BF00037A38 /* AlamofireNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlamofireNetwork.swift; sourceTree = ""; }; B518662620A09BCC00037A38 /* MockupNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockupNetwork.swift; sourceTree = ""; }; @@ -398,6 +400,7 @@ 74C8F06F20EEC3A800B6EDC9 /* broken-notes.json */, 74C8F06B20EEBD5D00B6EDC9 /* broken-order.json */, CE20179220E3EFA7005B4C18 /* broken-orders.json */, + B5147875211B9227007562E5 /* broken-orders-mark-2.json */, B58D10C72114D21C00107ED4 /* generic_error.json */, B505F6D420BEE4E600BB1B69 /* me.json */, B58D10C92114D22E00107ED4 /* new-order-note.json */, @@ -582,6 +585,7 @@ B58D10CA2114D22E00107ED4 /* new-order-note.json in Resources */, 74C8F06C20EEBD5D00B6EDC9 /* broken-order.json in Resources */, B58D10C82114D21D00107ED4 /* generic_error.json in Resources */, + B5147876211B9227007562E5 /* broken-orders-mark-2.json in Resources */, 743BF8BE21191B63008A9D87 /* site-visits.json in Resources */, B505F6D520BEE4E700BB1B69 /* me.json in Resources */, B5C6FCD620A3768900A4F8E4 /* order.json in Resources */, diff --git a/Networking/NetworkingTests/Mapper/OrderListMapperTests.swift b/Networking/NetworkingTests/Mapper/OrderListMapperTests.swift index d5629042f4c..bca1182b44d 100644 --- a/Networking/NetworkingTests/Mapper/OrderListMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/OrderListMapperTests.swift @@ -107,6 +107,13 @@ class OrderListMapperTests: XCTestCase { let orderModifiedString = format.string(from: brokenOrder.dateModified) XCTAssertEqual(orderModifiedString, todayCreatedString) } + + /// + /// + func testOrderListWithBreakingFormatIsProperlyParsed() { + let output = try? mapLoadBrokenOrdersResponseMarkII() + XCTAssertNil(output) + } } @@ -116,23 +123,29 @@ private extension OrderListMapperTests { /// Returns the OrderListMapper output upon receiving `filename` (Data Encoded) /// - func mapOrders(from filename: String) -> [Order] { + func mapOrders(from filename: String) throws -> [Order] { guard let response = Loader.contentsOf(filename) else { return [] } - return try! OrderListMapper(siteID: dummySiteID).map(response: response) + return try OrderListMapper(siteID: dummySiteID).map(response: response) } /// Returns the OrderListMapper output upon receiving `orders-load-all` /// func mapLoadAllOrdersResponse() -> [Order] { - return mapOrders(from: "orders-load-all") + return try! mapOrders(from: "orders-load-all") } - /// Returns the OrderlistMapper output upon receiving `broken-order` + /// Returns the OrderListMapper output upon receiving `broken-order` /// func mapLoadBrokenOrderResponse() -> [Order] { - return mapOrders(from: "broken-orders") + return try! mapOrders(from: "broken-orders") + } + + /// Returns the OrderListMapper output upon receiving `broken-orders-mark-2` + /// + func mapLoadBrokenOrdersResponseMarkII() throws -> [Order] { + return try mapOrders(from: "broken-orders-mark-2") } } diff --git a/Networking/NetworkingTests/Responses/broken-orders-mark-2.json b/Networking/NetworkingTests/Responses/broken-orders-mark-2.json new file mode 100644 index 00000000000..0911991a417 --- /dev/null +++ b/Networking/NetworkingTests/Responses/broken-orders-mark-2.json @@ -0,0 +1,614 @@ +{ + "data": [{ + "id": 263, + "parent_id": 0, + "number": "263", + "order_key": "wc_order_5b671708d1382", + "created_via": "checkout", + "version": "3.4.1", + "status": "processing", + "currency": "EUR", + "date_created": "2018-08-05T15:26:00", + "date_created_gmt": "2018-08-05T15:26:00", + "date_modified": "2018-08-05T15:26:04", + "date_modified_gmt": "2018-08-05T15:26:04", + "discount_total": "0.00", + "discount_tax": "0.00", + "shipping_total": "0.00", + "shipping_tax": "0.00", + "cart_tax": "0.00", + "total": "25.00", + "total_tax": "0.00", + "prices_include_tax": false, + "customer_id": 1, + "customer_ip_address": "192.0.0.0", + "customer_user_agent": "mozilla\/5.0 (macintosh; intel mac os x 10_13_6) applewebkit\/537.36 (khtml, like gecko) chrome\/67.0.3396.99 safari\/537.36", + "customer_note": "", + "billing": { + "first_name": "Woo", + "last_name": "Woo", + "company": "", + "address_1": "1223 Woo Street", + "address_2": "", + "city": "Vancouver", + "state": "CA", + "postcode": "90210", + "country": "US", + "email": "scrambled@scrambled.com", + "phone": "555 555 5555" + }, + "shipping": { + "first_name": "", + "last_name": "", + "company": "", + "address_1": "", + "address_2": "", + "city": "", + "state": "", + "postcode": "", + "country": "" + }, + "payment_method": "stripe", + "payment_method_title": "Credit Card (Stripe)", + "transaction_id": "ch_000000000000000", + "date_paid": "2018-08-05T15:26:04", + "date_paid_gmt": "2018-08-05T15:26:04", + "date_completed": null, + "date_completed_gmt": null, + "cart_hash": "ch_000000000000000", + "meta_data": [{ + "id": 7450, + "key": "_stripe_source_id", + "value": "ch_000000000000000" + }, { + "id": 7451, + "key": "_stripe_charge_captured", + "value": "yes" + }, { + "id": 7452, + "key": "_stripe_fee", + "value": "1.37" + }, { + "id": 7453, + "key": "_stripe_net", + "value": "35.54" + }, { + "id": 7454, + "key": "_stripe_currency", + "value": "CAD" + }], + "line_items": [{ + "id": 7, + "name": "Another tea", + "product_id": 224, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "25.00", + "subtotal_tax": "0.00", + "total": "25.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": "", + "price": 25 + }], + "tax_lines": [], + "shipping_lines": [], + "fee_lines": [], + "coupon_lines": [], + "refunds": [], + "_links": { + "self": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders\/263" + }], + "collection": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders" + }], + "customer": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/customers\/1" + }] + } + }, { + "id": 259, + "parent_id": 0, + "number": "259", + "order_key": "wc_order_5ace602461f2b", + "created_via": "checkout", + "version": "3.3.5", + "status": "processing", + "currency": "EUR", + "date_created": "2018-04-11T19:21:08", + "date_created_gmt": "2018-04-11T19:21:08", + "date_modified": "2018-04-11T19:21:11", + "date_modified_gmt": "2018-04-11T19:21:11", + "discount_total": "0.00", + "discount_tax": "0.00", + "shipping_total": "0.00", + "shipping_tax": "0.00", + "cart_tax": "0.00", + "total": "25.00", + "total_tax": "0.00", + "prices_include_tax": true, + "customer_id": 1, + "customer_ip_address": "192.168.1.1", + "customer_user_agent": "mozilla\/5.0 (macintosh; intel mac os x 10_13_4) applewebkit\/537.36 (khtml, like gecko) chrome\/65.0.3325.181 safari\/537.36", + "customer_note": "", + "billing": { + "first_name": "Scrambled", + "last_name": "Woo", + "company": "", + "address_1": "1223 Woo Street", + "address_2": "", + "city": "Vancouver", + "state": "CA", + "postcode": "90210", + "country": "US", + "email": "scrambled@scrambled", + "phone": "555 555 5555" + }, + "shipping": { + "first_name": "", + "last_name": "", + "company": "", + "address_1": "", + "address_2": "", + "city": "", + "state": "", + "postcode": "", + "country": "" + }, + "payment_method": "stripe", + "payment_method_title": "Credit Card (Stripe)", + "transaction_id": "ch_1CFoVGGNTAspWcqrNITccw58", + "date_paid": "2018-04-11T19:21:10", + "date_paid_gmt": "2018-04-11T19:21:10", + "date_completed": null, + "date_completed_gmt": null, + "cart_hash": "ff2c750d9bbeabb4a072aa1df515d227", + "meta_data": [{ + "id": 7388, + "key": "_stripe_source_id", + "value": "src_1CFoVDGNTAspWcqr8tCvgigU" + }, { + "id": 7389, + "key": "_stripe_charge_captured", + "value": "yes" + }, { + "id": 7390, + "key": "_stripe_fee", + "value": "1.41" + }, { + "id": 7391, + "key": "_stripe_net", + "value": "36.87" + }, { + "id": 7392, + "key": "_stripe_currency", + "value": "CAD" + }], + "line_items": [{ + "id": 6, + "name": "Another tea", + "product_id": 224, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "25.00", + "subtotal_tax": "0.00", + "total": "25.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": "", + "price": 25 + }], + "tax_lines": [], + "shipping_lines": [], + "fee_lines": [], + "coupon_lines": [], + "refunds": [], + "_links": { + "self": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders\/259" + }], + "collection": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders" + }], + "customer": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/customers\/1" + }] + } + }, { + "id": 161, + "parent_id": 0, + "number": "161", + "order_key": "wc_order_5a2c94d5252c0", + "created_via": "checkout", + "version": "3.1.2", + "status": "processing", + "currency": "GBP", + "date_created": "2017-12-10T01:58:45", + "date_created_gmt": "2017-12-10T01:58:45", + "date_modified": "2017-12-10T01:58:45", + "date_modified_gmt": "2017-12-10T01:58:45", + "discount_total": "0.00", + "discount_tax": "0.00", + "shipping_total": "0.00", + "shipping_tax": "0.00", + "cart_tax": "0.00", + "total": "12.00", + "total_tax": "0.00", + "prices_include_tax": false, + "customer_id": 1, + "customer_ip_address": "192.168.1.1", + "customer_user_agent": "mozilla\/5.0 (macintosh; intel mac os x 10_13_1) applewebkit\/537.36 (khtml, like gecko) chrome\/62.0.3202.94 safari\/537.36", + "customer_note": "Here are my order notes, do they show in the email?", + "billing": { + "first_name": "Scrambled", + "last_name": "Woo", + "company": "", + "address_1": "1223 Woo Street", + "address_2": "", + "city": "Vancouver", + "state": "CA", + "postcode": "90210", + "country": "US", + "email": "scrambled@scrambled", + "phone": "555 555 5555" + }, + "shipping": { + "first_name": "", + "last_name": "", + "company": "", + "address_1": "", + "address_2": "", + "city": "", + "state": "", + "postcode": "", + "country": "" + }, + "payment_method": "cod", + "payment_method_title": "Cash on delivery", + "transaction_id": "", + "date_paid": null, + "date_paid_gmt": null, + "date_completed": null, + "date_completed_gmt": null, + "cart_hash": "4c3db942a20dad7ad9ed8e218e46cb1c", + "meta_data": [{ + "id": 1406, + "key": "_shipping_method", + "value": "" + }], + "line_items": [{ + "id": 5, + "name": "Flying Ninja", + "product_id": 0, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "12.00", + "subtotal_tax": "0.00", + "total": "12.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 12 + }], + "tax_lines": [], + "shipping_lines": [], + "fee_lines": [], + "coupon_lines": [], + "refunds": [], + "_links": { + "self": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders\/161" + }], + "collection": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders" + }], + "customer": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/customers\/1" + }] + } + }, { + "id": 160, + "parent_id": 0, + "number": "160", + "order_key": "wc_order_5a2c94b07f79e", + "created_via": "checkout", + "version": "3.1.2", + "status": "processing", + "currency": "GBP", + "date_created": "2017-12-10T01:58:08", + "date_created_gmt": "2017-12-10T01:58:08", + "date_modified": "2017-12-10T01:58:08", + "date_modified_gmt": "2017-12-10T01:58:08", + "discount_total": "0.00", + "discount_tax": "0.00", + "shipping_total": "0.00", + "shipping_tax": "0.00", + "cart_tax": "0.00", + "total": "12.00", + "total_tax": "0.00", + "prices_include_tax": false, + "customer_id": 1, + "customer_ip_address": "192.168.1.1", + "customer_user_agent": "mozilla\/5.0 (macintosh; intel mac os x 10_13_1) applewebkit\/537.36 (khtml, like gecko) chrome\/62.0.3202.94 safari\/537.36", + "customer_note": "", + "billing": { + "first_name": "Scrambled", + "last_name": "Woo", + "company": "", + "address_1": "1223 Woo Street", + "address_2": "", + "city": "Vancouver", + "state": "CA", + "postcode": "90210", + "country": "US", + "email": "scrambled@scrambled", + "phone": "555 555 5555" + }, + "shipping": { + "first_name": "", + "last_name": "", + "company": "", + "address_1": "", + "address_2": "", + "city": "", + "state": "", + "postcode": "", + "country": "" + }, + "payment_method": "cod", + "payment_method_title": "Cash on delivery", + "transaction_id": "", + "date_paid": null, + "date_paid_gmt": null, + "date_completed": null, + "date_completed_gmt": null, + "cart_hash": "4c3db942a20dad7ad9ed8e218e46cb1c", + "meta_data": [{ + "id": 1357, + "key": "_shipping_method", + "value": "" + }], + "line_items": [{ + "id": 4, + "name": "Flying Ninja", + "product_id": 0, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "12.00", + "subtotal_tax": "0.00", + "total": "12.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 12 + }], + "tax_lines": [], + "shipping_lines": [], + "fee_lines": [], + "coupon_lines": [], + "refunds": [], + "_links": { + "self": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders\/160" + }], + "collection": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders" + }], + "customer": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/customers\/1" + }] + } + }, { + "id": 159, + "parent_id": 0, + "number": "159", + "order_key": "wc_order_5a2c93ef7eb03", + "created_via": "checkout", + "version": "3.1.2", + "status": "processing", + "currency": "GBP", + "date_created": "2017-12-10T01:54:55", + "date_created_gmt": "2017-12-10T01:54:55", + "date_modified": "2017-12-10T01:54:55", + "date_modified_gmt": "2017-12-10T01:54:55", + "discount_total": "0.00", + "discount_tax": "0.00", + "shipping_total": "0.00", + "shipping_tax": "0.00", + "cart_tax": "0.00", + "total": "15.00", + "total_tax": "0.00", + "prices_include_tax": false, + "customer_id": 1, + "customer_ip_address": "192.168.1.1", + "customer_user_agent": "mozilla\/5.0 (macintosh; intel mac os x 10_13_1) applewebkit\/537.36 (khtml, like gecko) chrome\/62.0.3202.94 safari\/537.36", + "customer_note": "Testing AT site Order Notes. Fingers crossed...", + "billing": { + "first_name": "Scrambled", + "last_name": "Woo", + "company": "", + "address_1": "1223 Woo Street", + "address_2": "", + "city": "Vancouver", + "state": "CA", + "postcode": "90210", + "country": "US", + "email": "scrambled@scrambled", + "phone": "555 555 5555" + }, + "shipping": { + "first_name": "", + "last_name": "", + "company": "", + "address_1": "", + "address_2": "", + "city": "", + "state": "", + "postcode": "", + "country": "" + }, + "payment_method": "cod", + "payment_method_title": "Cash on delivery", + "transaction_id": "", + "date_paid": null, + "date_paid_gmt": null, + "date_completed": null, + "date_completed_gmt": null, + "cart_hash": "59ef163e9f566ecee03d891868e28762", + "meta_data": [{ + "id": 1308, + "key": "_shipping_method", + "value": "" + }], + "line_items": [{ + "id": 3, + "name": "Woo Ninja", + "product_id": 0, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "15.00", + "subtotal_tax": "0.00", + "total": "15.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 15 + }], + "tax_lines": [], + "shipping_lines": [], + "fee_lines": [], + "coupon_lines": [], + "refunds": [], + "_links": { + "self": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders\/159" + }], + "collection": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders" + }], + "customer": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/customers\/1" + }] + } + }, { + "id": 158, + "parent_id": 0, + "number": "158", + "order_key": "wc_order_5a00fd94b3379", + "created_via": "checkout", + "version": "3.1.2", + "status": "processing", + "currency": "GBP", + "date_created": "2017-11-07T00:25:56", + "date_created_gmt": "2017-11-07T00:25:56", + "date_modified": "2017-11-07T00:25:57", + "date_modified_gmt": "2017-11-07T00:25:57", + "discount_total": "0.00", + "discount_tax": "0.00", + "shipping_total": "0.00", + "shipping_tax": "0.00", + "cart_tax": "0.00", + "total": "30.00", + "total_tax": "0.00", + "prices_include_tax": false, + "customer_id": 1, + "customer_ip_address": "172.103.149.103", + "customer_user_agent": "mozilla\/5.0 (macintosh; intel mac os x 10_12_6) applewebkit\/537.36 (khtml, like gecko) chrome\/62.0.3202.75 safari\/537.36", + "customer_note": "", + "billing": { + "first_name": "Scrambled", + "last_name": "Woo", + "company": "", + "address_1": "1223 Woo Street", + "address_2": "", + "city": "Vancouver", + "state": "CA", + "postcode": "90210", + "country": "US", + "email": "scrambled@scrambled", + "phone": "555 555 5555" + }, + "shipping": { + "first_name": "", + "last_name": "", + "company": "", + "address_1": "", + "address_2": "", + "city": "", + "state": "", + "postcode": "", + "country": "" + }, + "payment_method": "cod", + "payment_method_title": "Cash on delivery", + "transaction_id": "", + "date_paid": null, + "date_paid_gmt": null, + "date_completed": null, + "date_completed_gmt": null, + "cart_hash": "5c5c196dbcee14723a60291b94cbc92e", + "meta_data": [{ + "id": 1249, + "key": "_shipping_method", + "value": "" + }], + "line_items": [{ + "id": 1, + "name": "Flying Ninja", + "product_id": 0, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "12.00", + "subtotal_tax": "0.00", + "total": "12.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 12 + }, { + "id": 2, + "name": "Happy Ninja", + "product_id": 0, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "18.00", + "subtotal_tax": "0.00", + "total": "18.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 18 + }], + "tax_lines": [], + "shipping_lines": [], + "fee_lines": [], + "coupon_lines": [], + "refunds": [], + "_links": { + "self": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders\/158" + }], + "collection": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/orders" + }], + "customer": [{ + "href": "https:\/\/scrambled.scrambled.com\/wp-json\/wc\/v2\/customers\/1" + }] + } + }] +} \ No newline at end of file From 2cb0e1b8397d65a69c13156334115fafc60abe81 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 8 Aug 2018 18:37:46 -0300 Subject: [PATCH 105/112] Updates broken-orders-mark-2.json --- .../Responses/broken-orders-mark-2.json | 91 +++++++++++++++++-- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/Networking/NetworkingTests/Responses/broken-orders-mark-2.json b/Networking/NetworkingTests/Responses/broken-orders-mark-2.json index 0911991a417..f15cf37fb25 100644 --- a/Networking/NetworkingTests/Responses/broken-orders-mark-2.json +++ b/Networking/NetworkingTests/Responses/broken-orders-mark-2.json @@ -90,9 +90,24 @@ "total_tax": "0.00", "taxes": [], "meta_data": [], - "sku": "", + "sku": null, "price": 25 - }], + }, { + "id": 7, + "name": "Another tea", + "product_id": 224, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "25.00", + "subtotal_tax": "0.00", + "total": "25.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 25 + }], "tax_lines": [], "shipping_lines": [], "fee_lines": [], @@ -200,9 +215,24 @@ "total_tax": "0.00", "taxes": [], "meta_data": [], - "sku": "", + "sku": null, "price": 25 - }], + }, { + "id": 6, + "name": "Another tea", + "product_id": 224, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "25.00", + "subtotal_tax": "0.00", + "total": "25.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 25 + }], "tax_lines": [], "shipping_lines": [], "fee_lines": [], @@ -296,7 +326,22 @@ "meta_data": [], "sku": null, "price": 12 - }], + }, { + "id": 5, + "name": "Flying Ninja", + "product_id": 0, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "12.00", + "subtotal_tax": "0.00", + "total": "12.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 12 + }], "tax_lines": [], "shipping_lines": [], "fee_lines": [], @@ -390,7 +435,22 @@ "meta_data": [], "sku": null, "price": 12 - }], + }, { + "id": 4, + "name": "Flying Ninja", + "product_id": 0, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "12.00", + "subtotal_tax": "0.00", + "total": "12.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 12 + }], "tax_lines": [], "shipping_lines": [], "fee_lines": [], @@ -484,7 +544,22 @@ "meta_data": [], "sku": null, "price": 15 - }], + }, { + "id": 3, + "name": "Woo Ninja", + "product_id": 0, + "variation_id": 0, + "quantity": 1, + "tax_class": "", + "subtotal": "15.00", + "subtotal_tax": "0.00", + "total": "15.00", + "total_tax": "0.00", + "taxes": [], + "meta_data": [], + "sku": null, + "price": 15 + }], "tax_lines": [], "shipping_lines": [], "fee_lines": [], @@ -611,4 +686,4 @@ }] } }] -} \ No newline at end of file +} From a1600eb4794b6422992653060c0f6b1a75cfd21c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 8 Aug 2018 18:37:54 -0300 Subject: [PATCH 106/112] OrderItem: sku is now optional --- Networking/Networking/Model/OrderItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Networking/Networking/Model/OrderItem.swift b/Networking/Networking/Model/OrderItem.swift index a31ef414ea3..337189ba4df 100644 --- a/Networking/Networking/Model/OrderItem.swift +++ b/Networking/Networking/Model/OrderItem.swift @@ -8,7 +8,7 @@ public struct OrderItem: Decodable { public let name: String public let productID: Int public let quantity: Int - public let sku: String + public let sku: String? public let subtotal: String public let subtotalTax: String public let taxClass: String From 3d806e502feb5df3c8d8b0d51e38e5cb2fb4e073 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 8 Aug 2018 18:38:08 -0300 Subject: [PATCH 107/112] OrderListMapperTests: new Unit Test --- .../Mapper/OrderListMapperTests.swift | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Networking/NetworkingTests/Mapper/OrderListMapperTests.swift b/Networking/NetworkingTests/Mapper/OrderListMapperTests.swift index bca1182b44d..00336d414b6 100644 --- a/Networking/NetworkingTests/Mapper/OrderListMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/OrderListMapperTests.swift @@ -108,11 +108,22 @@ class OrderListMapperTests: XCTestCase { XCTAssertEqual(orderModifiedString, todayCreatedString) } + /// Verifies that `broken-orders-mark-2` gets properly parsed: 6 Orders with 2 items each, and the SKU property should + /// always be set to null. /// + /// Ref. Issue: https://github.com/woocommerce/woocommerce-ios/issues/221 /// func testOrderListWithBreakingFormatIsProperlyParsed() { - let output = try? mapLoadBrokenOrdersResponseMarkII() - XCTAssertNil(output) + let orders = mapLoadBrokenOrdersResponseMarkII() + XCTAssertEqual(orders.count, 6) + + for order in orders { + XCTAssertEqual(order.items.count, 2) + + for item in order.items { + XCTAssertNil(item.sku) + } + } } } @@ -123,29 +134,29 @@ private extension OrderListMapperTests { /// Returns the OrderListMapper output upon receiving `filename` (Data Encoded) /// - func mapOrders(from filename: String) throws -> [Order] { + func mapOrders(from filename: String) -> [Order] { guard let response = Loader.contentsOf(filename) else { return [] } - return try OrderListMapper(siteID: dummySiteID).map(response: response) + return try! OrderListMapper(siteID: dummySiteID).map(response: response) } /// Returns the OrderListMapper output upon receiving `orders-load-all` /// func mapLoadAllOrdersResponse() -> [Order] { - return try! mapOrders(from: "orders-load-all") + return mapOrders(from: "orders-load-all") } /// Returns the OrderListMapper output upon receiving `broken-order` /// func mapLoadBrokenOrderResponse() -> [Order] { - return try! mapOrders(from: "broken-orders") + return mapOrders(from: "broken-orders") } /// Returns the OrderListMapper output upon receiving `broken-orders-mark-2` /// - func mapLoadBrokenOrdersResponseMarkII() throws -> [Order] { - return try mapOrders(from: "broken-orders-mark-2") + func mapLoadBrokenOrdersResponseMarkII() -> [Order] { + return mapOrders(from: "broken-orders-mark-2") } } From 461c18377694e09801b4e9109d8b014f6a06b531 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 8 Aug 2018 18:38:19 -0300 Subject: [PATCH 108/112] OrderItemViewModel: Handling null SKU --- WooCommerce/Classes/ViewModels/OrderItemViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/ViewModels/OrderItemViewModel.swift b/WooCommerce/Classes/ViewModels/OrderItemViewModel.swift index de97daf5b6b..4b0b57ce485 100644 --- a/WooCommerce/Classes/ViewModels/OrderItemViewModel.swift +++ b/WooCommerce/Classes/ViewModels/OrderItemViewModel.swift @@ -51,11 +51,11 @@ struct OrderItemViewModel { /// Item's SKU /// var sku: String? { - guard item.sku.isEmpty == false else { + guard let sku = item.sku, sku.isEmpty == false else { return nil } let prefix = NSLocalizedString("SKU:", comment: "SKU label") - return prefix + " " + item.sku + return prefix + " " + sku } } From 300b1a53105174642991262ad388dfcd46fe8700 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 8 Aug 2018 18:38:27 -0300 Subject: [PATCH 109/112] ProductDetailsTableViewCell: Handling null SKU --- .../Orders/OrderDetails/ProductDetailsTableViewCell.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/ProductDetailsTableViewCell.swift b/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/ProductDetailsTableViewCell.swift index f7b036f616b..59d95e4d5a8 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/ProductDetailsTableViewCell.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/ProductDetailsTableViewCell.swift @@ -119,7 +119,8 @@ extension ProductDetailsTableViewCell { taxLabel.text = taxText let skuString = NSLocalizedString("SKU:", comment: "SKU label") - let skuText = item.sku.isEmpty ? nil : "\(skuString) \(item.sku)" + let skuRaw = item.sku ?? String() + let skuText = skuRaw.isEmpty ? nil : "\(skuString) \(skuRaw)" skuLabel.text = skuText } } From bea9d5b867626854a5029698f8e95ae5d687f86e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 8 Aug 2018 18:38:34 -0300 Subject: [PATCH 110/112] OrderStoreTests: New Unit Test --- .../Stores/OrderStoreTests.swift | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift b/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift index 425ebf7ac65..d05915d1ea5 100644 --- a/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift +++ b/Yosemite/YosemiteTests/Stores/OrderStoreTests.swift @@ -111,6 +111,28 @@ class OrderStoreTests: XCTestCase { wait(for: [expectation], timeout: Constants.expectationTimeout) } + /// Verifies that `OrderAction.retrieveOrders` can properly process the document `broken-orders-mark-2`. + /// + /// Ref. Issue: https://github.com/woocommerce/woocommerce-ios/issues/221 + /// + func testRetrieveOrdersWithBreakingDocumentIsProperlyParsedAndInsertedIntoStorage() { + let expectation = self.expectation(description: "Persist order list") + let orderStore = OrderStore(dispatcher: dispatcher, storageManager: storageManager, network: network) + + network.simulateResponse(requestUrlSuffix: "orders", filename: "broken-orders-mark-2") + XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Order.self), 0) + + let action = OrderAction.retrieveOrders(siteID: sampleSiteID) { error in + XCTAssertNil(error) + XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Order.self), 6) + + expectation.fulfill() + } + + orderStore.onAction(action) + wait(for: [expectation], timeout: Constants.expectationTimeout) + } + /// Verifies that OrderAction.retrieveOrders returns an error whenever there is an error response from the backend. /// func testRetrieveOrdersReturnsErrorUponReponseError() { From e46689dedb87d8863bad0275f13af8eba3b09519 Mon Sep 17 00:00:00 2001 From: Matt Bumgardner Date: Mon, 13 Aug 2018 11:24:58 -0500 Subject: [PATCH 111/112] Updating the bg color of the order screens --- .../Orders/OrderDetails/OrderDetailsViewController.swift | 2 ++ .../Classes/ViewRelated/Orders/OrdersViewController.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/OrderDetailsViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/OrderDetailsViewController.swift index 92d632b1e4e..300df779796 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/OrderDetailsViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrderDetails/OrderDetailsViewController.swift @@ -79,6 +79,8 @@ private extension OrderDetailsViewController { /// Setup: TableView /// func configureTableView() { + view.backgroundColor = StyleManager.tableViewBackgroundColor + tableView.backgroundColor = StyleManager.tableViewBackgroundColor tableView.estimatedSectionHeaderHeight = Constants.sectionHeight tableView.estimatedSectionFooterHeight = Constants.rowHeight tableView.estimatedRowHeight = Constants.rowHeight diff --git a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift index a534e7af89d..28c8e7b90a1 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift @@ -90,6 +90,8 @@ private extension OrdersViewController { } func configureTableView() { + view.backgroundColor = StyleManager.tableViewBackgroundColor + tableView.backgroundColor = StyleManager.tableViewBackgroundColor tableView.estimatedRowHeight = Constants.estimatedRowHeight tableView.rowHeight = UITableViewAutomaticDimension tableView.refreshControl = refreshControl From 8e30b2b6c4d077a5bf14800c7f338f0b1a18f052 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Mon, 13 Aug 2018 14:24:35 -0300 Subject: [PATCH 112/112] Increasing Version to Mark 0.5 --- WooCommerce/Resources/Info.plist | 4 ++-- WooCommerce/WooCommerce.xcodeproj/project.pbxproj | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WooCommerce/Resources/Info.plist b/WooCommerce/Resources/Info.plist index dd08d4544d1..fcf409fbbf8 100644 --- a/WooCommerce/Resources/Info.plist +++ b/WooCommerce/Resources/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.4.3 + 0.5 CFBundleURLTypes @@ -43,7 +43,7 @@ CFBundleVersion - 20180731 + 20180813 Fabric APIKey diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index b5cf4c619a9..65302950971 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -96,8 +96,8 @@ B5DBF3C320E1484400B53AED /* StoresManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3C220E1484400B53AED /* StoresManagerTests.swift */; }; B5DBF3C520E148E000B53AED /* DeauthenticatedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3C420E148E000B53AED /* DeauthenticatedState.swift */; }; B5DBF3CB20E149CC00B53AED /* AuthenticatedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBF3CA20E149CC00B53AED /* AuthenticatedState.swift */; }; - B873E8F8E103966D2182EE67 /* Pods_WooCommerceTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DC4526F9A7357761197EBF0 /* Pods_WooCommerceTests.framework */; }; B5E96B3821137AA100DF68D0 /* OrderStatus+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E96B3721137AA100DF68D0 /* OrderStatus+Woo.swift */; }; + B873E8F8E103966D2182EE67 /* Pods_WooCommerceTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DC4526F9A7357761197EBF0 /* Pods_WooCommerceTests.framework */; }; CE17C2E220ACA06800AFBD20 /* BillingDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE17C2E020ACA06800AFBD20 /* BillingDetailsTableViewCell.swift */; }; CE17C2E320ACA06800AFBD20 /* BillingDetailsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE17C2E120ACA06800AFBD20 /* BillingDetailsTableViewCell.xib */; }; CE1CCB402056F21C000EE3AC /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1CCB3F2056F21C000EE3AC /* Style.swift */; }; @@ -1218,7 +1218,7 @@ CODE_SIGN_STYLE = Automatic; INFOPLIST_PREFIX_HEADER = InfoPlist.h; PRODUCT_NAME = "$(TARGET_NAME)"; - SECRETS_PATH = "$HOME/.woo_app_credentials.json"; + SECRETS_PATH = $HOME/.woo_app_credentials.json; VALID_ARCHS = "$(inherited)"; }; name = Debug; @@ -1229,7 +1229,7 @@ CODE_SIGN_STYLE = Automatic; INFOPLIST_PREFIX_HEADER = InfoPlist.h; PRODUCT_NAME = "$(TARGET_NAME)"; - SECRETS_PATH = "$HOME/.woo_app_credentials.json"; + SECRETS_PATH = $HOME/.woo_app_credentials.json; VALID_ARCHS = "$(inherited)"; }; name = Release; @@ -1367,7 +1367,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = "WooCommerce Development"; - SECRETS_PATH = "$HOME/.woo_app_credentials.json"; + SECRETS_PATH = $HOME/.woo_app_credentials.json; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = ""; @@ -1393,7 +1393,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.automattic.woocommerce; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "WooCommerce App Store"; - SECRETS_PATH = "$HOME/.woo_app_credentials.json"; + SECRETS_PATH = $HOME/.woo_app_credentials.json; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; USER_HEADER_SEARCH_PATHS = "";