diff --git a/.env b/.env index 7fc3a09ee..8163b074b 100644 --- a/.env +++ b/.env @@ -43,6 +43,8 @@ FACEBOOK_APP_SECRET=facebook_app_secret GOOGLE_APP_ID=id.apps.googleusercontent.com +GA4_PROPERTY_ID=your_ga4_property_id + APPLE_PRIVATE_KEY=private_key APPLE_CLIENT_ID=org.cru.godtools APPLE_KEY_ID=apple_key_id diff --git a/Gemfile b/Gemfile index 0fc7ece6c..7d61d95b7 100644 --- a/Gemfile +++ b/Gemfile @@ -74,7 +74,7 @@ gem "datadog" gem "dogstatsd-ruby", "~> 5.3" gem "file_validators" gem "googleauth" -gem "google-apis-analyticsreporting_v4" +gem "google-analytics-data" gem "httparty" gem "jwt" gem "lograge" diff --git a/Gemfile.lock b/Gemfile.lock index 88807cb58..cc7bceb90 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -186,7 +186,6 @@ GEM debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) - declarative (0.0.20) diff-lcs (1.6.2) docile (1.4.1) dogstatsd-ruby (5.7.1) @@ -213,6 +212,9 @@ GEM faraday (>= 1, < 3) faraday-net_http (3.4.2) net-http (~> 0.5) + faraday-retry (2.4.0) + faraday (~> 2.0) + ffi (1.17.3) ffi (1.17.3-arm64-darwin) ffi (1.17.3-x86_64-linux-gnu) ffi (1.17.3-x86_64-linux-musl) @@ -220,22 +222,50 @@ GEM activemodel (>= 3.2) mime-types (>= 1.0) formatador (1.1.0) + gapic-common (1.2.0) + faraday (>= 1.9, < 3.a) + faraday-retry (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + google-protobuf (~> 4.26) + googleapis-common-protos (~> 1.6) + googleapis-common-protos-types (~> 1.15) + googleauth (~> 1.12) + grpc (~> 1.66) globalid (1.3.0) activesupport (>= 6.1) - google-apis-analyticsreporting_v4 (0.17.0) - google-apis-core (>= 0.15.0, < 2.a) - google-apis-core (0.15.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (~> 1.9) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml + google-analytics-data (0.7.2) + google-analytics-data-v1beta (>= 0.11, < 2.a) + google-cloud-core (~> 1.6) + google-analytics-data-v1beta (0.19.1) + gapic-common (~> 1.2) + google-cloud-errors (~> 1.0) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) google-cloud-env (2.3.1) base64 (~> 0.2) faraday (>= 1.0, < 3.a) + google-cloud-errors (1.5.0) google-logging-utils (0.2.0) + google-protobuf (4.33.4) + bigdecimal + rake (>= 13) + google-protobuf (4.33.4-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.33.4-x86_64-linux-gnu) + bigdecimal + rake (>= 13) + google-protobuf (4.33.4-x86_64-linux-musl) + bigdecimal + rake (>= 13) + googleapis-common-protos (1.9.0) + google-protobuf (~> 4.26) + googleapis-common-protos-types (~> 1.21) + grpc (~> 1.41) + googleapis-common-protos-types (1.22.0) + google-protobuf (~> 4.26) googleauth (1.16.1) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.2) @@ -244,6 +274,18 @@ GEM multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) + grpc (1.76.0) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + grpc (1.76.0-arm64-darwin) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + grpc (1.76.0-x86_64-linux-gnu) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + grpc (1.76.0-x86_64-linux-musl) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) guard (2.18.1) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) @@ -273,8 +315,6 @@ GEM csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - httpclient (2.9.0) - mutex_m i18n (1.14.8) concurrent-ruby (~> 1.0) io-console (0.8.2) @@ -333,6 +373,7 @@ GEM mime-types-data (~> 3.2025, >= 3.2025.0507) mime-types-data (3.2025.0924) mini_mime (1.1.5) + mini_portile2 (2.8.9) minitest (6.0.1) prism (~> 1.5) msgpack (1.8.0) @@ -342,7 +383,6 @@ GEM mustache (1.1.1) mustermann (3.0.4) ruby2_keywords (~> 0.0.1) - mutex_m (0.3.0) nenv (0.3.0) net-http (0.9.1) uri (>= 0.11.1) @@ -357,6 +397,9 @@ GEM net-protocol netrc (0.11.0) nio4r (2.7.5) + nokogiri (1.19.0) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) nokogiri (1.19.0-arm64-darwin) racc (~> 1.4) nokogiri (1.19.0-x86_64-linux-gnu) @@ -390,6 +433,7 @@ GEM parser (3.3.10.1) ast (~> 2.4.1) racc + pg (1.6.3) pg (1.6.3-arm64-darwin) pg (1.6.3-x86_64-linux) pg (1.6.3-x86_64-linux-musl) @@ -480,10 +524,6 @@ GEM regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) request_store (1.7.0) rack (>= 1.4) rest-client (2.1.0) @@ -491,7 +531,6 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - retriable (3.1.2) rexml (3.4.4) rollbar (3.7.0) rspec (3.13.2) @@ -601,11 +640,9 @@ GEM thor (1.5.0) tilt (2.6.1) timeout (0.4.4) - trailblazer-option (0.1.2) tsort (0.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - uber (0.1.0) unf (0.1.4) unf_ext unf_ext (0.0.9.1) @@ -644,6 +681,7 @@ GEM PLATFORMS arm64-darwin + ruby x86_64-linux-gnu x86_64-linux-musl @@ -666,7 +704,7 @@ DEPENDENCIES equivalent-xml (~> 0.6.0) factory_bot_rails file_validators - google-apis-analyticsreporting_v4 + google-analytics-data googleauth guard-rspec guard-rubocop @@ -754,7 +792,6 @@ CHECKSUMS datadog-ruby_core_source (3.5.2) sha256=c379c012eca11d6c58b112d716a986d051fb566a436d58f46cde7cfec43cb299 date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6 - declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e dogstatsd-ruby (5.7.1) sha256=d2e211f9635d6bbe773e152e00fd4cb9f165d26e10356dbef8f917f7a9e5d58c @@ -770,17 +807,32 @@ CHECKSUMS faraday (2.14.0) sha256=8699cfe5d97e55268f2596f9a9d5a43736808a943714e3d9a53e6110593941cd faraday-follow_redirects (0.3.0) sha256=d92d975635e2c7fe525dd494fcd4b9bb7f0a4a0ec0d5f4c15c729530fdb807f9 faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c + faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe + ffi (1.17.3) sha256=0e9f39f7bb3934f77ad6feab49662be77e87eedcdeb2a3f5c0234c2938563d4c ffi (1.17.3-arm64-darwin) sha256=0c690555d4cee17a7f07c04d59df39b2fba74ec440b19da1f685c6579bb0717f ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f ffi (1.17.3-x86_64-linux-musl) sha256=086b221c3a68320b7564066f46fed23449a44f7a1935f1fe5a245bd89d9aea56 file_validators (3.0.0) sha256=43700f0c1fe9b235bf7f4701375e1d2f9391352ab26723f123b934724db8067b formatador (1.1.0) sha256=54e23e2af4d60bb9327c7fac62b29968e4cf28cee0111f726d0bdeadc85e06d0 + gapic-common (1.2.0) sha256=b477ec1eebbed7eed80efc04267369ce623e18b14e573c806e8920f76dc60dde globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11 - google-apis-analyticsreporting_v4 (0.17.0) sha256=e902b6cae7139f4fd8ab32950c2ae7ff8034131a7257e4064dbe332581ee0221 - google-apis-core (0.15.0) sha256=9c95ee9068c6b1fb3f8698b2856f53a031c3a68e886d074cc2e9a482e070a6e4 + google-analytics-data (0.7.2) sha256=5e4bf6dc575c7725c2b32541725749890a88e0a64ff0ad7e8a73f75ad5a1bbc9 + google-analytics-data-v1beta (0.19.1) sha256=7b63a551819ee9cfdb9364f6d995239dc69465c38bb3a9344cc0d12c8a8e4031 + google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf google-cloud-env (2.3.1) sha256=0faac01eb27be78c2591d64433663b1a114f8f7af55a4f819755426cac9178e7 + google-cloud-errors (1.5.0) sha256=b56be28b8c10628125214dde571b925cfcebdbc58619e598250c37a2114f7b4b google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b + google-protobuf (4.33.4) sha256=86921935b023ed0d872d6e84382e79016c91689be0520d614c74426778f13c16 + google-protobuf (4.33.4-arm64-darwin) sha256=63bb57e3d7108986f961546a536adaa38bd9ab5b1ebc39a1741a16c6eba3e869 + google-protobuf (4.33.4-x86_64-linux-gnu) sha256=a8cff953d742bc804ece78bd96e82d88cf65a7d8c1910e045359e2f53074e55d + google-protobuf (4.33.4-x86_64-linux-musl) sha256=265e6e5bbcc0ba1367268739e31c4cc84ff310b56e8d738d2a3245b5cccfc2e7 + googleapis-common-protos (1.9.0) sha256=207be372d8d25e3876e1e1155d057e14f3c92f8f5428772864a8ce04a0b756e4 + googleapis-common-protos-types (1.22.0) sha256=f97492b77bd6da0018c860d5004f512fe7cd165554d7019a8f4df6a56fbfc4c7 googleauth (1.16.1) sha256=36776bce9d55d8c1a0c6638c939b000dcee5954ca5b728f06ec4c2df4a46709c + grpc (1.76.0) sha256=112416fa42153aee440fd1b975de4b2bf746656df071bace97c197a6b5575598 + grpc (1.76.0-arm64-darwin) sha256=c23f30c2e6a51bb75d8f617b028453e66e09665d8bf571d99c7be1b1f9dd2b59 + grpc (1.76.0-x86_64-linux-gnu) sha256=1561c852903617acae0a9ef9011bcf313054ba65c5528519b002a9f6b4d5c024 + grpc (1.76.0-x86_64-linux-musl) sha256=b942a6ae0828e0e696ce26d8b797167314ae7633810c41dd09b45c2176f966cd guard (2.18.1) sha256=a3893d3b23d538a9b2d6f05a27713e0ed53ea881df0f0550b21a3393c6d87c09 guard-compat (1.2.1) sha256=3ad21ab0070107f92edfd82610b5cdc2fb8e368851e72362ada9703443d646fe guard-rspec (4.7.3) sha256=a47ba03cbd1e3c71e6ae8645cea97e203098a248aede507461a43e906e2f75ca @@ -790,7 +842,6 @@ CHECKSUMS http-accept (1.7.0) sha256=c626860682bfbb3b46462f8c39cd470fd7b0584f61b3cc9df5b2e9eb9972a126 http-cookie (1.1.0) sha256=38a5e60d1527eebc396831b8c4b9455440509881219273a6c99943d29eadbb19 httparty (0.24.2) sha256=8fca6a54aa0c4aa4303a0fd33e5e2156175d6a5334f714263b458abd7fda9c38 - httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8 i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806 @@ -818,13 +869,13 @@ CHECKSUMS mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56 mime-types-data (3.2025.0924) sha256=f276bca15e59f35767cbcf2bc10e023e9200b30bd6a572c1daf7f4cc24994728 mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289 minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb msgpack (1.8.0) sha256=e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732 multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7 multi_xml (0.8.1) sha256=addba0290bac34e9088bfe73dc4878530297a82a7bbd66cb44dcd0a4b86edf5a mustache (1.1.1) sha256=90891fdd50b53919ca334c8c1031eada1215e78d226d5795e523d6123a2717d0 mustermann (3.0.4) sha256=85fadcb6b3c6493a8b511b42426f904b7f27b282835502233dd154daab13aa22 - mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 nenv (0.3.0) sha256=d9de6d8fb7072228463bf61843159419c969edb34b3cef51832b516ae7972765 net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996 net-imap (0.5.12) sha256=cb8cd05bd353fcc19b6cbc530a9cb06b577a969ea10b7ddb0f37787f74be4444 @@ -833,6 +884,7 @@ CHECKSUMS net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736 netrc (0.11.0) sha256=de1ce33da8c99ab1d97871726cba75151113f117146becbe45aa85cb3dabee3f nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1 + nokogiri (1.19.0) sha256=e304d21865f62518e04f2bf59f93bd3a97ca7b07e7f03952946d8e1c05f45695 nokogiri (1.19.0-arm64-darwin) sha256=0811dfd936d5f6dd3f6d32ef790568bf29b2b7bead9ba68866847b33c9cf5810 nokogiri (1.19.0-x86_64-linux-gnu) sha256=f482b95c713d60031d48c44ce14562f8d2ce31e3a9e8dd0ccb131e9e5a68b58c nokogiri (1.19.0-x86_64-linux-musl) sha256=1c4ca6b381622420073ce6043443af1d321e8ed93cc18b08e2666e5bd02ffae4 @@ -845,6 +897,7 @@ CHECKSUMS ougai (2.0.0) sha256=2e8d522b901cb6a818ebf6b5492c237538fb4a3f5cd2e333d391663aaa7eb67e parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.1) sha256=06f6a725d2cd91e5e7f2b7c32ba143631e1f7c8ae2fb918fc4cebec187e6a688 + pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99 pg (1.6.3-arm64-darwin) sha256=7240330b572e6355d7c75a7de535edb5dfcbd6295d9c7777df4d9dddfb8c0e5f pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009 @@ -878,10 +931,8 @@ CHECKSUMS redis-client (0.26.4) sha256=3ad70beff5da2653e02dfeae996e7d8d7147a558da12b16b2282ad345e4c7120 regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 - representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb rest-client (2.1.0) sha256=35a6400bdb14fae28596618e312776c158f7ebbb0ccad752ff4fa142bf2747e3 - retriable (3.1.2) sha256=0a5a5d0ca4ba61a76fb31a17ab8f7f80281beb040c329d34dfc137a1398688e0 rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 rollbar (3.7.0) sha256=915853168dd61e41621b482d5f3c38e32a0b3ce57e72252a4bf9eae906571d86 rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 @@ -922,10 +973,8 @@ CHECKSUMS thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 timeout (0.4.4) sha256=f0f6f970104b82427cd990680f539b6bbb8b1e55efa913a55c6492935e4e0edb - trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b - uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc unf (0.1.4) sha256=4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e unf_ext (0.0.9.1) sha256=926114a858934126c6bbfd3254347dadb5dae354711869368c0f75e3765fc6e9 unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 diff --git a/app/lib/update_global_activity_analytics.rb b/app/lib/update_global_activity_analytics.rb index 4b78cf96d..e23a2938e 100644 --- a/app/lib/update_global_activity_analytics.rb +++ b/app/lib/update_global_activity_analytics.rb @@ -1,126 +1,112 @@ # frozen_string_literal: true -# Updates the global activity analytics by fetching data from the endpoint +# Updates the global activity analytics by fetching data from the GA4 Data API # and saving it in the `GlobalActivityAnalytics` model. It supposes to do that # every 24h. # The threshold is configurable by `GlobalActivityAnalytics::TTL` constant. class UpdateGlobalActivityAnalytics SERVICE_ACCOUNT_CREDENTIALS_FILE_PATH = "config/secure/service_account_cred.json" + GOSPEL_PRESENTATION_EVENT_SUFFIX = "_gospel_presented" def initialize - init_google_instance @analytics = GlobalActivityAnalytics.instance + init_ga4_client end def perform return if @analytics.actual? - data = google_analytics_report - counters = fetch_counters(data) + counters = fetch_all_counters @analytics.update!(counters) end private - def fetch_counters(data) - results = {} - data.reports.each do |report| - headers = report.column_header.metric_header.metric_header_entries.map(&:name) - first_row = report.data.rows&.first - headers.each_with_index do |header_name, index| - if header_name == "countries" - results[header_name] = report.data.rows.count - else - results[header_name.sub("ga:", "")] = first_row ? first_row.metrics.first.values[index] : 0 - end - end - end - results + def fetch_all_counters + users_and_sessions = fetch_users_and_sessions + gospel_presentations = fetch_gospel_presentations + countries = fetch_countries_count + + { + users: users_and_sessions[:users], + launches: users_and_sessions[:sessions], + gospel_presentations: gospel_presentations, + countries: countries + } end - def init_google_instance - service = Google::Apis::AnalyticsreportingV4::AnalyticsReportingService.new - - # Create service account credentials - credentials = Google::Auth::ServiceAccountCredentials.make_creds( - # this file is downloaded from s3 in production via config/initializers/sync_secure_google_creds.rb - json_key_io: File.open(SERVICE_ACCOUNT_CREDENTIALS_FILE_PATH), - scope: "https://www.googleapis.com/auth/analytics.readonly" + def fetch_users_and_sessions + request = Google::Analytics::Data::V1beta::RunReportRequest.new( + property: "properties/#{property_id}", + date_ranges: [date_range], + metrics: [ + Google::Analytics::Data::V1beta::Metric.new(name: "totalUsers"), + Google::Analytics::Data::V1beta::Metric.new(name: "sessions") + ] ) - # Authorize with our readonly credentials - service.authorization = credentials + response = @client.run_report(request) + row = response.rows&.first - @google_client = service + { + users: row ? row.metric_values[0].value.to_i : 0, + sessions: row ? row.metric_values[1].value.to_i : 0 + } end - def date_range - Google::Apis::AnalyticsreportingV4::DateRange.new( - start_date: Date.today.beginning_of_year.to_s, - end_date: (Date.today.beginning_of_year + 1.year).to_s - ) - end - - def sessions_and_users_request - metrics = [Google::Apis::AnalyticsreportingV4::Metric.new( - expression: "ga:users" - ), - - Google::Apis::AnalyticsreportingV4::Metric.new( - expression: "ga:sessions", - alias: "launches" - )] - - Google::Apis::AnalyticsreportingV4::ReportRequest.new( - view_id: ENV.fetch("GOOGLE_ANALYTICS_VIEW_ID"), - sampling_level: "DEFAULT", + def fetch_gospel_presentations + request = Google::Analytics::Data::V1beta::RunReportRequest.new( + property: "properties/#{property_id}", date_ranges: [date_range], - metrics: metrics, - dimensions: [] + metrics: [ + Google::Analytics::Data::V1beta::Metric.new(name: "eventCount") + ], + dimension_filter: Google::Analytics::Data::V1beta::FilterExpression.new( + filter: Google::Analytics::Data::V1beta::Filter.new( + field_name: "eventName", + string_filter: Google::Analytics::Data::V1beta::Filter::StringFilter.new( + value: GOSPEL_PRESENTATION_EVENT_SUFFIX, + match_type: Google::Analytics::Data::V1beta::Filter::StringFilter::MatchType::ENDS_WITH + ) + ) + ) ) - end - def gospel_presentations_request - metrics = [Google::Apis::AnalyticsreportingV4::Metric.new( - expression: "ga:totalEvents", - alias: "gospel_presentations" - )] + response = @client.run_report(request) + row = response.rows&.first + row ? row.metric_values[0].value.to_i : 0 + end - Google::Apis::AnalyticsreportingV4::ReportRequest.new( - view_id: ENV.fetch("GOOGLE_ANALYTICS_VIEW_ID"), - sampling_level: "DEFAULT", - filters_expression: "ga:eventLabel==presenting the gospel", + def fetch_countries_count + request = Google::Analytics::Data::V1beta::RunReportRequest.new( + property: "properties/#{property_id}", date_ranges: [date_range], - metrics: metrics, - dimensions: [] + metrics: [ + Google::Analytics::Data::V1beta::Metric.new(name: "sessions") + ], + dimensions: [ + Google::Analytics::Data::V1beta::Dimension.new(name: "country") + ] ) - end - def countries_request - metrics = [Google::Apis::AnalyticsreportingV4::Metric.new( - expression: "ga:sessions", - alias: "countries" - )] + response = @client.run_report(request) + response.row_count.to_i + end - countries_dimesion = Google::Apis::AnalyticsreportingV4::Dimension.new( - name: "ga:country" - ) + def init_ga4_client + @client = Google::Analytics::Data::V1beta::AnalyticsData::Client.new do |config| + config.credentials = SERVICE_ACCOUNT_CREDENTIALS_FILE_PATH + end + end - Google::Apis::AnalyticsreportingV4::ReportRequest.new( - view_id: ENV.fetch("GOOGLE_ANALYTICS_VIEW_ID"), - sampling_level: "DEFAULT", - date_ranges: [date_range], - metrics: metrics, - dimensions: [countries_dimesion] + def date_range + Google::Analytics::Data::V1beta::DateRange.new( + start_date: Date.today.beginning_of_year.to_s, + end_date: Date.today.to_s ) end - def google_analytics_report - # Create a new report request - request = Google::Apis::AnalyticsreportingV4::GetReportsRequest.new( - report_requests: [sessions_and_users_request, gospel_presentations_request, countries_request] - ) - # Make API call. - @google_client.batch_get_reports(request) + def property_id + ENV.fetch("GA4_PROPERTY_ID") end end diff --git a/spec/fixtures/ga4_analytics_stub.json b/spec/fixtures/ga4_analytics_stub.json new file mode 100644 index 000000000..fa61dff35 --- /dev/null +++ b/spec/fixtures/ga4_analytics_stub.json @@ -0,0 +1,25 @@ +{ + "metricHeaders": [ + { + "name": "totalUsers", + "type": "TYPE_INTEGER" + }, + { + "name": "sessions", + "type": "TYPE_INTEGER" + } + ], + "rows": [ + { + "metricValues": [ + { + "value": "238326" + }, + { + "value": "966442" + } + ] + } + ], + "rowCount": 1 +} \ No newline at end of file diff --git a/spec/fixtures/ga4_countries_stub.json b/spec/fixtures/ga4_countries_stub.json new file mode 100644 index 000000000..e1db5f655 --- /dev/null +++ b/spec/fixtures/ga4_countries_stub.json @@ -0,0 +1,88 @@ +{ + "dimensionHeaders": [ + { + "name": "country" + } + ], + "metricHeaders": [ + { + "name": "sessions", + "type": "TYPE_INTEGER" + } + ], + "rows": [ + { + "dimensionValues": [ + { + "value": "(not set)" + } + ], + "metricValues": [ + { + "value": "52" + } + ] + }, + { + "dimensionValues": [ + { + "value": "Afghanistan" + } + ], + "metricValues": [ + { + "value": "1" + } + ] + }, + { + "dimensionValues": [ + { + "value": "Albania" + } + ], + "metricValues": [ + { + "value": "12" + } + ] + }, + { + "dimensionValues": [ + { + "value": "Algeria" + } + ], + "metricValues": [ + { + "value": "6" + } + ] + }, + { + "dimensionValues": [ + { + "value": "Angola" + } + ], + "metricValues": [ + { + "value": "5" + } + ] + }, + { + "dimensionValues": [ + { + "value": "Zimbabwe" + } + ], + "metricValues": [ + { + "value": "24" + } + ] + } + ], + "rowCount": 6 +} \ No newline at end of file diff --git a/spec/fixtures/ga4_gospel_presentations_stub.json b/spec/fixtures/ga4_gospel_presentations_stub.json new file mode 100644 index 000000000..33a46b149 --- /dev/null +++ b/spec/fixtures/ga4_gospel_presentations_stub.json @@ -0,0 +1,18 @@ +{ + "metricHeaders": [ + { + "name": "eventCount", + "type": "TYPE_INTEGER" + } + ], + "rows": [ + { + "metricValues": [ + { + "value": "43834" + } + ] + } + ], + "rowCount": 1 +} \ No newline at end of file diff --git a/spec/lib/update_global_activity_analytics_spec.rb b/spec/lib/update_global_activity_analytics_spec.rb index f83efb41a..049f552ed 100644 --- a/spec/lib/update_global_activity_analytics_spec.rb +++ b/spec/lib/update_global_activity_analytics_spec.rb @@ -65,4 +65,16 @@ "`cp spec/fixtures/service_account_cred.json.actions #{described_class::SERVICE_ACCOUNT_CREDENTIALS_FILE_PATH}`" end end + + describe "GA4 client initialization" do + it "configures client with service account credentials" do + mock_config = double("config") + expect(mock_config).to receive(:credentials=).with(described_class::SERVICE_ACCOUNT_CREDENTIALS_FILE_PATH) + + mock_client = instance_double(Google::Analytics::Data::V1beta::AnalyticsData::Client) + allow(Google::Analytics::Data::V1beta::AnalyticsData::Client).to receive(:new).and_yield(mock_config).and_return(mock_client) + + described_class.new + end + end end diff --git a/spec/support/test_helpers.rb b/spec/support/test_helpers.rb index 9b97a38e0..abf70f1f0 100644 --- a/spec/support/test_helpers.rb +++ b/spec/support/test_helpers.rb @@ -1,17 +1,54 @@ +require "google/analytics/data/v1beta" + module TestHelpers module_function def stub_request_to_analytics(rspec_context, analytics_status: 200, access_token_status: 200) rspec_context.instance_eval do - analytics_url = "https://analyticsreporting.googleapis.com/v4/reports:batchGet" - access_token_url = "https://www.googleapis.com/oauth2/v4/token" + ENV["GA4_PROPERTY_ID"] = "234841169" + + # Load GA4 fixture data + analytics_data = JSON.parse(File.read(Rails.root.join("spec", "fixtures", "ga4_analytics_stub.json"))) + countries_data = JSON.parse(File.read(Rails.root.join("spec", "fixtures", "ga4_countries_stub.json"))) + gospel_data = JSON.parse(File.read(Rails.root.join("spec", "fixtures", "ga4_gospel_presentations_stub.json"))) - ENV["GOOGLE_ANALYTICS_VIEW_ID"] = "234841169" - ENV["GOOGLE_API_USE_RAILS_LOGGER"] = "false" + # Build mock responses + analytics_response = TestHelpers.build_ga4_response(analytics_data) + countries_response = TestHelpers.build_ga4_response(countries_data) + gospel_response = TestHelpers.build_ga4_response(gospel_data) - body = File.read(Rails.root.join("spec", "fixtures", "google_access_token.json")) - stub_request(:post, access_token_url).to_return(status: access_token_status, body: body, headers: {"Content-Type" => "application/json; charset=UTF-8"}) + # Mock the GA4 client + mock_client = instance_double(Google::Analytics::Data::V1beta::AnalyticsData::Client) - body = File.read(Rails.root.join("spec", "fixtures", "google_analytics_stub.json")) - stub_request(:post, analytics_url).to_return(status: analytics_status, body: body, headers: {"Content-Type" => "application/json; charset=UTF-8"}) + if analytics_status == 200 && access_token_status == 200 + allow(mock_client).to receive(:run_report).and_return( + analytics_response, + gospel_response, + countries_response + ) + else + allow(mock_client).to receive(:run_report).and_raise(Google::Cloud::InvalidArgumentError.new("Invalid request")) + end + + allow(Google::Analytics::Data::V1beta::AnalyticsData::Client).to receive(:new).and_return(mock_client) end end + + module_function def build_ga4_response(data) + rows = (data["rows"] || []).map do |row| + metric_values = (row["metricValues"] || []).map do |mv| + Google::Analytics::Data::V1beta::MetricValue.new(value: mv["value"]) + end + dimension_values = (row["dimensionValues"] || []).map do |dv| + Google::Analytics::Data::V1beta::DimensionValue.new(value: dv["value"]) + end + Google::Analytics::Data::V1beta::Row.new( + metric_values: metric_values, + dimension_values: dimension_values + ) + end + + Google::Analytics::Data::V1beta::RunReportResponse.new( + rows: rows, + row_count: data["rowCount"] || rows.size + ) + end end