From 3b478624f2b37818b926b54187283f8bcc5ec783 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Mon, 23 Dec 2019 13:52:59 -0800 Subject: [PATCH 1/5] add gem 'google-api-client' --- Gemfile | 3 + Gemfile.lock | 692 --------------------------------------------------- 2 files changed, 3 insertions(+), 692 deletions(-) delete mode 100644 Gemfile.lock diff --git a/Gemfile b/Gemfile index 088746e2b..10f11cf4e 100644 --- a/Gemfile +++ b/Gemfile @@ -78,6 +78,9 @@ gem 'counter_culture', '~> 2.0' gem 'cookies_eu' +gem 'google-api-client' # Google Analytics REST API + + group :development, :test do gem 'rspec-rails' gem 'shoulda-matchers' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 523cae388..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,692 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - aasm (4.11.1) - actioncable (5.2.3) - actionpack (= 5.2.3) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailer (5.2.3) - actionpack (= 5.2.3) - actionview (= 5.2.3) - activejob (= 5.2.3) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.2.3) - actionview (= 5.2.3) - activesupport (= 5.2.3) - rack (~> 2.0) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.3) - activesupport (= 5.2.3) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.3) - activesupport (= 5.2.3) - globalid (>= 0.3.6) - activemodel (5.2.3) - activesupport (= 5.2.3) - activerecord (5.2.3) - activemodel (= 5.2.3) - activesupport (= 5.2.3) - arel (>= 9.0) - activestorage (5.2.3) - actionpack (= 5.2.3) - activerecord (= 5.2.3) - marcel (~> 0.3.1) - activesupport (5.2.3) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) - after_commit_action (1.1.0) - activerecord (>= 3.0.0) - activesupport (>= 3.0.0) - airbrussh (1.3.1) - sshkit (>= 1.6.1, != 1.7.0) - arel (9.0.0) - ast (2.4.0) - autoprefixer-rails (9.5.0) - execjs - aws-eventstream (1.0.2) - aws-partitions (1.149.0) - aws-sdk-core (3.48.3) - aws-eventstream (~> 1.0, >= 1.0.2) - aws-partitions (~> 1.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.16.0) - aws-sdk-core (~> 3, >= 3.48.2) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.36.0) - aws-sdk-core (~> 3, >= 3.48.2) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.0) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) - axiom-types (0.1.1) - descendants_tracker (~> 0.0.4) - ice_nine (~> 0.11.0) - thread_safe (~> 0.3, >= 0.3.1) - backports (3.13.0) - bcrypt (3.1.12) - better_errors (2.5.1) - coderay (>= 1.0.0) - erubi (>= 1.0.0) - rack (>= 0.9.0) - bindex (0.6.0) - binding_of_caller (0.8.0) - debug_inspector (>= 0.0.1) - bootsnap (1.4.2) - msgpack (~> 1.0) - bootstrap (4.1.3) - autoprefixer-rails (>= 6.0.3) - popper_js (>= 1.12.9, < 2) - sass (>= 3.5.2) - bootstrap-toggle-rails (2.2.1.0) - bootstrap-will_paginate (1.0.0) - will_paginate - builder (3.2.3) - bullet (5.9.0) - activesupport (>= 3.0.0) - uniform_notifier (~> 1.11) - byebug (11.0.1) - capistrano (3.11.0) - airbrussh (>= 1.0.0) - i18n - rake (>= 10.0.0) - sshkit (>= 1.9.0) - capistrano-bundler (1.1.4) - capistrano (~> 3.1) - sshkit (~> 1.2) - capistrano-env-config (0.3.0) - capistrano (~> 3.0) - dotenv (~> 2.0) - capistrano-rails (1.4.0) - capistrano (~> 3.1) - capistrano-bundler (~> 1.1) - capistrano-rbenv (2.1.4) - capistrano (~> 3.1) - sshkit (~> 1.3) - capistrano-ssh-doctor (1.0.0) - capistrano (>= 3.1) - capybara (3.16.1) - addressable - mini_mime (>= 0.1.3) - nokogiri (~> 1.8) - rack (>= 1.6.0) - rack-test (>= 0.6.3) - regexp_parser (~> 1.2) - xpath (~> 3.2) - chartkick (3.0.2) - childprocess (0.9.0) - ffi (~> 1.0, >= 1.0.11) - chronic (0.10.2) - city-state (0.0.13) - rubyzip (~> 1.1) - ckeditor (4.3.0) - orm_adapter (~> 0.5.0) - terrapin - climate_control (0.2.0) - cliver (0.3.2) - codeclimate-engine-rb (0.4.1) - virtus (~> 1.0) - codeclimate-test-reporter (1.0.7) - simplecov - coderay (1.1.2) - coercible (1.0.0) - descendants_tracker (~> 0.0.1) - concurrent-ruby (1.1.5) - cookies_eu (1.7.5) - js_cookie_rails (~> 2.2.0) - counter_culture (2.1.4) - activerecord (>= 3.0.0) - activesupport (>= 3.0.0) - after_commit_action (~> 1.0) - coveralls (0.8.22) - json (>= 1.8, < 3) - simplecov (~> 0.16.1) - term-ansicolor (~> 1.3) - thor (~> 0.19.4) - tins (~> 1.6) - crack (0.4.3) - safe_yaml (~> 1.0.0) - crass (1.0.5) - css_parser (1.7.0) - addressable - cucumber (3.1.2) - builder (>= 2.1.2) - cucumber-core (~> 3.2.0) - cucumber-expressions (~> 6.0.1) - cucumber-wire (~> 0.0.1) - diff-lcs (~> 1.3) - gherkin (~> 5.1.0) - multi_json (>= 1.7.5, < 2.0) - multi_test (>= 0.1.2) - cucumber-core (3.2.1) - backports (>= 3.8.0) - cucumber-tag_expressions (~> 1.1.0) - gherkin (~> 5.0) - cucumber-expressions (6.0.1) - cucumber-rails (1.6.0) - capybara (>= 1.1.2, < 4) - cucumber (>= 3.0.2, < 4) - mime-types (>= 1.17, < 4) - nokogiri (~> 1.8) - railties (>= 4, < 6) - cucumber-tag_expressions (1.1.1) - cucumber-timecop (0.0.6) - chronic - cucumber - timecop - cucumber-wire (0.0.1) - database_cleaner (1.7.0) - debug_inspector (0.0.3) - descendants_tracker (0.0.4) - thread_safe (~> 0.3, >= 0.3.1) - devise (4.6.2) - bcrypt (~> 3.0) - orm_adapter (~> 0.1) - railties (>= 4.1.0, < 6.0) - responders - warden (~> 1.2.3) - diff-lcs (1.3) - docile (1.3.1) - domain_name (0.5.20180417) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.2) - dotenv-rails (2.7.2) - dotenv (= 2.7.2) - railties (>= 3.2, < 6.1) - email_spec (2.2.0) - htmlentities (~> 4.3.3) - launchy (~> 2.1) - mail (~> 2.7) - equalizer (0.0.11) - erb2haml (0.1.5) - html2haml - erubi (1.8.0) - erubis (2.7.0) - exception_notification (4.2.2) - actionmailer (>= 4.0, < 6) - activesupport (>= 4.0, < 6) - exception_notification-rake (0.3.0) - exception_notification (~> 4.2.0) - rake (>= 0.9.0) - execjs (2.7.0) - factory_bot (5.0.2) - activesupport (>= 4.2.0) - factory_bot_rails (5.0.1) - factory_bot (~> 5.0.0) - railties (>= 4.2.0) - ffaker (2.11.0) - ffi (1.10.0) - flay (2.12.0) - erubis (~> 2.7.0) - path_expander (~> 1.0) - ruby_parser (~> 3.0) - sexp_processor (~> 4.0) - flog (4.6.2) - path_expander (~> 1.0) - ruby_parser (~> 3.1, > 3.1.0) - sexp_processor (~> 4.8) - font-awesome-sass (5.5.0.1) - sassc (>= 1.11) - geocoder (1.5.1) - gherkin (5.1.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - groupdate (4.1.1) - activesupport (>= 4.2) - haml (5.0.4) - temple (>= 0.8.0) - tilt - haml-rails (1.0.0) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) - haml (>= 4.0.6, < 6.0) - html2haml (>= 1.0.1) - railties (>= 4.0.1) - hashdiff (0.3.8) - hashie (3.6.0) - high_voltage (3.0.0) - highline (2.0.1) - html2haml (2.2.0) - erubis (~> 2.7.0) - haml (>= 4.0, < 6) - nokogiri (>= 1.6.0) - ruby_parser (~> 3.5) - htmlentities (4.3.4) - http-cookie (1.0.3) - domain_name (~> 0.5) - httparty (0.16.4) - mime-types (~> 3.0) - multi_xml (>= 0.5.2) - i18n (1.6.0) - concurrent-ruby (~> 1.0) - i18n-js (3.2.1) - i18n (>= 0.6.6) - i18n-tasks (0.9.29) - activesupport (>= 4.0.2) - ast (>= 2.1.0) - erubi - highline (>= 2.0.0) - i18n - parser (>= 2.2.3.0) - rails-i18n - rainbow (>= 2.2.2, < 4.0) - terminal-table (>= 1.5.1) - ice_nine (0.11.2) - imgkit (1.6.2) - jaro_winkler (1.5.2) - jbuilder (2.8.0) - activesupport (>= 4.2.0) - multi_json (>= 1.2) - jmespath (1.4.0) - jquery-rails (4.3.3) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - js_cookie_rails (2.2.0) - railties (>= 3.1) - json (2.2.0) - jwt (2.1.0) - kwalify (0.7.2) - launchy (2.4.3) - addressable (~> 2.3) - libv8 (6.7.288.46.1) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.3.1) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - mailgun-ruby (1.1.11) - rest-client (~> 2.0.2) - marcel (0.3.3) - mimemagic (~> 0.3.2) - meta-tags (2.11.1) - actionpack (>= 3.2.0, < 6.1) - method_source (0.9.2) - mime-types (3.2.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2019.0331) - mimemagic (0.3.3) - mini_magick (4.9.3) - mini_mime (1.0.1) - mini_portile2 (2.4.0) - mini_racer (0.2.4) - libv8 (>= 6.3) - minitest (5.11.3) - msgpack (1.2.9) - multi_json (1.13.1) - multi_test (0.1.2) - multi_xml (0.6.0) - net-scp (2.0.0) - net-ssh (>= 2.6.5, < 6.0.0) - net-ssh (5.2.0) - netrc (0.11.0) - nio4r (2.3.1) - nokogiri (1.10.5) - mini_portile2 (~> 2.4.0) - nokogumbo (2.0.1) - nokogiri (~> 1.8, >= 1.8.4) - orgnummer (0.2.0) - orm_adapter (0.5.0) - paperclip (6.0.0) - activemodel (>= 4.2.0) - activesupport (>= 4.2.0) - mime-types - mimemagic (~> 0.3.0) - terrapin (~> 0.6.0) - parallel (1.17.0) - parser (2.6.2.0) - ast (~> 2.4.0) - path_expander (1.0.3) - pg (0.21.0) - poltergeist (1.18.1) - capybara (>= 2.1, < 4) - cliver (~> 0.3.1) - websocket-driver (>= 0.2.0) - popper_js (1.14.5) - premailer (1.11.1) - addressable - css_parser (>= 1.6.0) - htmlentities (>= 4.0.0) - premailer-rails (1.10.2) - actionmailer (>= 3, < 6) - premailer (~> 1.7, >= 1.7.9) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - pry-byebug (3.7.0) - byebug (~> 11.0) - pry (~> 0.10) - psych (3.1.0) - public_suffix (3.0.3) - puma (3.12.1) - pundit (2.0.1) - activesupport (>= 3.0.0) - pundit-matchers (1.6.0) - rspec-rails (>= 3.0.0) - rack (2.0.7) - rack-mini-profiler (1.0.2) - rack (>= 1.2.0) - rack-test (1.1.0) - rack (>= 1.0, < 3) - railroady (1.5.3) - rails (5.2.3) - actioncable (= 5.2.3) - actionmailer (= 5.2.3) - actionpack (= 5.2.3) - actionview (= 5.2.3) - activejob (= 5.2.3) - activemodel (= 5.2.3) - activerecord (= 5.2.3) - activestorage (= 5.2.3) - activesupport (= 5.2.3) - bundler (>= 1.3.0) - railties (= 5.2.3) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - rails-i18n (5.1.3) - i18n (>= 0.7, < 2) - railties (>= 5.0, < 6) - railties (5.2.3) - actionpack (= 5.2.3) - activesupport (= 5.2.3) - method_source - rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) - rake (12.3.2) - ransack (2.1.1) - actionpack (>= 5.0) - activerecord (>= 5.0) - activesupport (>= 5.0) - i18n - rb-fsevent (0.10.3) - rb-inotify (0.10.0) - ffi (~> 1.0) - rb-readline (0.5.5) - reek (5.3.2) - codeclimate-engine-rb (~> 0.4.0) - kwalify (~> 0.7.0) - parser (>= 2.5.0.0, < 2.7, != 2.5.1.1) - psych (~> 3.1.0) - rainbow (>= 2.0, < 4.0) - regexp_parser (1.4.0) - responders (2.4.1) - actionpack (>= 4.2.0, < 6.0) - railties (>= 4.2.0, < 6.0) - rest-client (2.0.2) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) - routing-filter (0.6.2) - actionpack (>= 4.2, < 6) - activesupport (>= 4.2, < 6) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-rails (3.8.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - rubocop (0.67.2) - jaro_winkler (~> 1.5.1) - parallel (~> 1.10) - parser (>= 2.5, != 2.5.1.1) - psych (>= 3.1.0) - rainbow (>= 2.2.2, < 4.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.6) - rubocop-rspec (1.32.0) - rubocop (>= 0.60.0) - ruby-progressbar (1.10.0) - ruby_dep (1.5.0) - ruby_parser (3.13.1) - sexp_processor (~> 4.9) - rubycritic (4.0.2) - flay (~> 2.8) - flog (~> 4.4) - launchy (= 2.4.3) - parser (~> 2.6.0) - rainbow (~> 3.0) - reek (~> 5.3.0) - ruby_parser (~> 3.8) - tty-which (~> 0.4.0) - virtus (~> 1.0) - rubyzip (1.2.2) - safe_yaml (1.0.5) - sanitize (5.0.0) - crass (~> 1.0.2) - nokogiri (>= 1.8.0) - nokogumbo (~> 2.0) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - sassc (2.0.1) - ffi (~> 1.9) - rake - selenium-webdriver (3.141.0) - childprocess (~> 0.5) - rubyzip (~> 1.2, >= 1.2.2) - sexp_processor (4.12.0) - shoulda-matchers (4.0.1) - activesupport (>= 4.2.0) - show_me_the_cookies (5.0.0) - capybara (>= 2, < 4) - simplecov (0.16.1) - docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) - slack-notifier (2.3.2) - smarter_csv (1.2.6) - spring (2.0.2) - activesupport (>= 4.2) - spring-watcher-listen (2.0.1) - listen (>= 2.7, < 4.0) - spring (>= 1.2, < 3.0) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sshkit (1.18.2) - net-scp (>= 1.1.2) - net-ssh (>= 2.8.0) - temple (0.8.1) - term-ansicolor (1.7.1) - tins (~> 1.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - terrapin (0.6.0) - climate_control (>= 0.0.3, < 1.0) - thor (0.19.4) - thread_safe (0.3.6) - tilt (2.0.9) - timecop (0.9.1) - tins (1.20.2) - tty-which (0.4.0) - turbolinks (5.2.0) - turbolinks-source (~> 5.2) - turbolinks-source (5.2.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - uglifier (4.1.20) - execjs (>= 0.3.0, < 3) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.5.0) - uniform_notifier (1.12.1) - vcr (4.0.0) - virtus (1.0.5) - axiom-types (~> 0.1) - coercible (~> 1.0) - descendants_tracker (~> 0.0, >= 0.0.3) - equalizer (~> 0.0, >= 0.0.9) - warden (1.2.8) - rack (>= 2.0.6) - web-console (3.7.0) - actionview (>= 5.0) - activemodel (>= 5.0) - bindex (>= 0.4.0) - railties (>= 5.0) - webdrivers (3.9.2) - nokogiri (~> 1.6) - rubyzip (~> 1.0) - selenium-webdriver (~> 3.0) - webmock (3.5.1) - addressable (>= 2.3.6) - crack (>= 0.3.2) - hashdiff - websocket-driver (0.7.0) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) - will_paginate (3.1.7) - wkhtmltoimage-binary (0.12.5) - xpath (3.2.0) - nokogiri (~> 1.8) - -PLATFORMS - ruby - -DEPENDENCIES - aasm (~> 4.11.1) - aws-sdk-s3 - bcrypt (~> 3.1.7) - better_errors - binding_of_caller - bootsnap - bootstrap (~> 4.1.3) - bootstrap-toggle-rails - bootstrap-will_paginate - bullet - capistrano (~> 3.11) - capistrano-bundler (~> 1.1.2) - capistrano-env-config - capistrano-rails (~> 1.4) - capistrano-rbenv (~> 2.0) - capistrano-ssh-doctor (~> 1.0) - chartkick - city-state - ckeditor (~> 4.2, >= 4.2.4) - codeclimate-test-reporter (~> 1.0.0) - cookies_eu - counter_culture (~> 2.0) - coveralls (>= 0.8.21) - cucumber-rails - cucumber-timecop - database_cleaner - devise - dotenv - dotenv-rails - email_spec - erb2haml - exception_notification - exception_notification-rake (~> 0.3.0) - factory_bot_rails - ffaker - font-awesome-sass (~> 5.5.0) - geocoder - groupdate - haml-rails - hashie - high_voltage (~> 3.0.0) - httparty - i18n-js (>= 3.0.0.rc11) - i18n-tasks (~> 0.9.21) - imgkit - jbuilder (~> 2.5) - jquery-rails - jwt - launchy - mailgun-ruby - meta-tags - mini_magick - mini_racer - nokogiri - orgnummer - paperclip (~> 6.0.0) - pg (~> 0.18) - poltergeist - popper_js (~> 1.14.3) - premailer-rails - pry - pry-byebug - puma (~> 3.0) - pundit - pundit-matchers - rack-mini-profiler - railroady - rails (= 5.2.3) - rake - ransack - rb-readline - routing-filter - rspec-rails - rubocop - rubocop-rspec - rubycritic - rubyzip (>= 1.2.1) - sanitize - sass-rails (~> 5.0) - selenium-webdriver - shoulda-matchers - show_me_the_cookies - simplecov (>= 0.13.0) - slack-notifier - smarter_csv - spring - spring-watcher-listen (~> 2.0.0) - timecop - turbolinks (~> 5) - uglifier (>= 1.3.0) - vcr - web-console - webdrivers (~> 3.0) - webmock - will_paginate - wkhtmltoimage-binary - -RUBY VERSION - ruby 2.5.1p57 - -BUNDLED WITH - 1.17.3 From 3e6c99bff1b222114edd04e85d10c1a74f270239 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Mon, 23 Dec 2019 13:53:32 -0800 Subject: [PATCH 2/5] example source files for GoogleAPI credentials; don't check real ones in to git --- .env.example | 17 ++++++++++++++++- .gitignore | 3 +++ config/google-api-client-secrets.json.example | 12 ++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 config/google-api-client-secrets.json.example diff --git a/.env.example b/.env.example index e59f5a85b..8b370c80b 100644 --- a/.env.example +++ b/.env.example @@ -37,7 +37,7 @@ MAILGUN_DOMAIN=app-email-domain MAILGUN_SANDBOX_DOMAIN=sandboxlongkey.mailgun.org SHF_SENDER_EMAIL=hello@app-email-domain -SHF_EMAIL_DISPLAY_NAME='Sveriges Hundföretagare' +SHF_EMAIL_DISPLAY_NAME='Sveriges Hundföretagare' SHF_FROM_EMAIL='info@sverigeshundforetagare.se' SHF_REPLY_TO_EMAIL='medlem@sverigeshundforetagare.se' @@ -125,3 +125,18 @@ BRANCH=vcs_branch # # (Contact the project admin for the app id for this project.) #SHF_FB_APPID='1234567890' + + +# -------------------------------------------- +# GOOGLE API Service Account Credentials +# -------------------------------------------- +# (Contact the project admin for the values.) +SHF_GOOGLE_APPLICATION_CREDENTIALS_FILE="config/google-api-client-secrets.json" +SHF_GOOGLE_API_KEY='some-api-key' +SHF_GOOGLE_PROJECT_ID='key-for-this-project' +SHF_GOOGLE_ACCOUNT_TYPE='service' +SHF_GOOGLE_ANALYTICS_VIEW_ID='view-id' +SHF_GOOGLE_CLIENT_EMAIL='email-for-the-service-account' +SHF_GOOGLE_UNIQUE_ID='google-id-for-this-service-account' +SHF_GOOGLE_PRIVATE_KEY='private-api-key' +# -------------------------------------------- diff --git a/.gitignore b/.gitignore index d75bab85d..ff5b17971 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,9 @@ public/ckeditor_assets # do not put config/secrets.yml into repository config/secrets.yml +# do not put Google API client secrents into the repository +config/google-api-client-secrets.json + # ignore all directories named rubycritic no matter where they are # (rubycritic gem reports: do not put individual rubycritic reports into the repo) /**/rubycritic/ diff --git a/config/google-api-client-secrets.json.example b/config/google-api-client-secrets.json.example new file mode 100644 index 000000000..408760c63 --- /dev/null +++ b/config/google-api-client-secrets.json.example @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "project-id", + "private_key_id": "private-key-id", + "private_key": "very-long-private-key", + "client_email": "service-account-email", + "client_id": "google-unique-id", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/email-here" +} From 32a1984dab3a08afcd089c7bce87923ad1a7c146 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Mon, 23 Dec 2019 13:53:52 -0800 Subject: [PATCH 3/5] formatter for arrays of arrays --- .../google-analytics/array_arrays_as_table.rb | 135 +++++++++++ .../array_arrays_as_table_spec.rb | 209 ++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 app/services/google-analytics/array_arrays_as_table.rb create mode 100644 spec/services/google_analytics/array_arrays_as_table_spec.rb diff --git a/app/services/google-analytics/array_arrays_as_table.rb b/app/services/google-analytics/array_arrays_as_table.rb new file mode 100644 index 000000000..0c3f217bc --- /dev/null +++ b/app/services/google-analytics/array_arrays_as_table.rb @@ -0,0 +1,135 @@ +#-------------------------- +# +# @class ArrayArraysAsTable +# +# @desc Responsibility: Creates a string with an Array of Arrays of Strings and/or Numbers +# formatted as a Table. +# BASED ON print_table FROM THOR: bundler/vendor/thor/lib/thor/shell/basic.rb +# +# I needed a way to easily view an array of arrays as a decently formatted table. +# The code in Thor had the basic methods. I refactored and renamed some things, +# added the :row_end and :column_separator options, +# and made the output a String instead of writing to $stdout. +# +# +# @author Ashley Engelund (ashley.engelund@gmail.com weedySeaDragon @ github) +# @date 12/14/19 +# +#-------------------------- +# +class ArrayArraysAsTable + + DEFAULT_COL_SEPARATOR = ' ' + DEFAULT_ROW_ENDER = $/ + + + # Prints an Array of Arrays of Strings and/or Numbers as table, returns a String + # array[0] = column headers + # array[1] = first row + # array[2] = second row + # ... + # array[n] = nth row + # + # + # @param [Array] array - array of arrays = the table to print out. Every entry should be a String or Number + # @param [Hash] options - options + # indent:: Indent the first columnof the entire table by indent value. + # colwidth:: Force the first column to colwidth spaces wide. Ex: if the first column is a label, set this to be plenty wide so all of the data in the following columns is aligned nicely + # row_end:: String to use at the end of each row. Default = "\n" + # column_separator:: String to use at the end of each column. Default = " " (2 spaces) + # + # @return [String] - a String with the formatted table + def self.print_table(array, options = {}) # rubocop:disable MethodLength + return if array.empty? + + set_options(options) + + result = '' + maxs_and_formats = column_max_lengths_formats(array, @first_colwidth, @indent) + result << puts_rows(array, maxs_and_formats[:col_maximas], maxs_and_formats[:formats]) + result + end + + + def self.set_options(options) + @indent = options[:indent].to_i + @first_colwidth = options[:colwidth] + @row_ender = options.fetch(:row_end, DEFAULT_ROW_ENDER) + @col_separator = options.fetch(:column_separator, DEFAULT_COL_SEPARATOR) + end + + + def self.column_max_lengths_formats(array, first_colwidth, indent) + col_maximas = [] + formats = [] + formats << "%-#{first_colwidth}s#{@col_separator}".dup if first_colwidth + + start = first_colwidth ? 1 : 0 + colcount = array.max { |arr_a, arr_b| arr_a.size <=> arr_b.size }.size - 1 + + # Create maxima (max. col. length) and format for each column + start.upto(colcount) do |column_index| + col_max_size = col_max_size(array, column_index) + col_maximas << col_max_size + + # Don't append the @col_separator when printing the last column + formats << ((column_index == colcount) ? col_str_format : col_padded_str_format(col_max_size)) + end + formats << "%s" # format for last column + formats[0] = formats[0].insert(0, ' ' * indent) # indent the first column by 'indent' spaces + + return { col_maximas: col_maximas, formats: formats } + end + + + # get the size of the widest string in the column + def self.col_max_size(array, column_index) + array.map { |row| row[column_index] ? row[column_index].to_s.size : 0 }.max # rubocop:disable DuplicateMethodCall + end + + + def self.puts_rows(array, maximas, formats) + array.map { |row| row_sentence(row, maximas, formats) }.join('') + end + + + def self.row_sentence(row, maximas, formats) + row_sentence = ''.dup + row.each_with_index do |column, index| + format_str = column.is_a?(Numeric) ? + formatted_numeric_col(index, row.size, maximas[index]) : + formats[index] + + # apply the format string (format_str) to the column value (as a String) + row_sentence << format_str % column.to_s + end + row_sentence << @row_ender.dup + end + + + def self.col_str_format + '%-s'.dup + end + + + def self.col_padded_str_format(padding = 3) + "%-#{padding}s#{@col_separator}".dup + end + + + def self.formatted_numeric_col(index, row_size, max_length) + # Don't output @col_separator at the end of a column when printing the last column + (index == row_size - 1) ? numeric_base_col_format(max_length) : numeric_col_format_not_last(max_length) + end + + + # append the column separator to the formatted column + def self.numeric_col_format_not_last(max_length) + "#{numeric_base_col_format(max_length)}#{@col_separator}" + end + + + def self.numeric_base_col_format(max_length) + "%#{max_length}s" + end +end diff --git a/spec/services/google_analytics/array_arrays_as_table_spec.rb b/spec/services/google_analytics/array_arrays_as_table_spec.rb new file mode 100644 index 000000000..b85006448 --- /dev/null +++ b/spec/services/google_analytics/array_arrays_as_table_spec.rb @@ -0,0 +1,209 @@ +require 'spec_helper' + +require File.join(__dir__, '..', '..', '..', 'app/services/google-analytics/array_arrays_as_table') + +# Yes, this 'over tests' ArrayArraysAsTable + + +RSpec.describe ArrayArraysAsTable do + + + def make_col_headers(columns: 0) + column_headers = [] + columns.times { |col| column_headers << "column_#{col}" } + column_headers + end + + + def entry_string(row, col) + "row#{row}_col#{col}" + end + + + def make_nested_arrays(columns: 0, rows: 0) + nested_array = [] + + nested_array << make_col_headers(columns: columns) + rows.times do |row| + this_row = [] + columns.times { |col| this_row << yield(row, col) } + nested_array << this_row + end + + nested_array + end + + + def make_string_nested_arrays(columns: 0, rows: 0) + make_string_entries_method = lambda { |row, col| "row#{row}_col#{col}" } + make_nested_arrays(columns: columns, rows: rows, &make_string_entries_method) + end + + + def make_numeric_nested_arrays(columns: 0, rows: 0) + make_numeric_entries_method = lambda { |row, col| row * col } + make_nested_arrays(columns: columns, rows: rows, &make_numeric_entries_method) + end + + + # ----------------------------------------------------------------------- + + + describe 'print_table' do + + let(:array_3x4) { make_string_nested_arrays(columns: 3, rows: 4) } + let(:default_result) { described_class.print_table(array_3x4) } + + + it 'creates a String with the output' do + expected_3x4 = "column_0 column_1 column_2\nrow0_col0 row0_col1 row0_col2\nrow1_col0 row1_col1 row1_col2\nrow2_col0 row2_col1 row2_col2\nrow3_col0 row3_col1 row3_col2\n" + expect(default_result).to be_a String + expect(default_result).to eq expected_3x4 + end + + it 'number of rows = 1(for column headers) + rows' do + expect(default_result.lines.size).to eq 5 + end + + it 'each sub array (each single row) size = number of columns' do + expect(default_result.lines.map { |row| row.split(' ').size }).to eq [3, 3, 3, 3, 3] + end + + it 'columns are padded with blanks so they are the width of the widest string in that column (including column headers)' do + + array_2x11 = make_string_nested_arrays(columns: 2, rows: 11) + comma_sep_cols_semicolon_row_ends = described_class.print_table(array_2x11, { column_separator: ',', row_end: ';' }) + + rows = comma_sep_cols_semicolon_row_ends.lines(';') + first_cols = rows.map { |row| row.lines(',').first } + + # The first 10 columns are padded by 1 space (they are col_0 .. col9). + # remove the trailing comma from each (left on there from the call to .lines) + 10.times do |n| + without_comma = first_cols[n - 1].delete_suffix(',') + expect(without_comma.lstrip.size).to eq(first_cols[n - 1].size - 1) + end + + # The last column is the widest, so it has no padding + last_col_without_comma = first_cols.last.delete_suffix(',') + expect(last_col_without_comma.lstrip.size).to eq(last_col_without_comma.size) + end + + + describe 'can handle arrays with numbers (instead of Strings)' do + + let(:numeric_4x3) { make_numeric_nested_arrays(columns: 4, rows: 3) } + + it 'can handle arrays with numbers (instead of Strings)' do + expected_numeric_4x3 = "column_0,column_1,column_2,column_3\n 0, 0, 0, 0\n 0, 1, 2, 3\n 0, 2, 4, 6\n" + expect(described_class.print_table(numeric_4x3, column_separator: ',')).to eq expected_numeric_4x3 + end + + + it 'numbers are padded to the widest entry in the column (including the width of the column header)' do + + result = described_class.print_table(numeric_4x3, column_separator: ',') + + rows = result.lines + (rows.size - 1).times do |row_num| + row = rows[row_num + 1] + cols = row.lines(',') + + cols.each do |column| + # Each numeric column has just 1 digit. They are padded with 7 spaces on the left (prefixed) to match the width of the widest entry in the column (the column header) + expect(column.lstrip.size + 7).to eq(column.size) + end + end + end + + end + + describe 'options' do + + describe ':indent - number of spaces to indent the table' do + + let(:expected_indent_4_spaces) { " column_0 column_1 column_2\n row0_col0 row0_col1 row0_col2\n row1_col0 row1_col1 row1_col2\n row2_col0 row2_col1 row2_col2\n row3_col0 row3_col1 row3_col2\n" } + + it 'default is 0' do + rows = default_result.lines + first_cols = rows.map { |row| row.lines(',').first } + first_cols.each do |first_column| + expect(first_column.lstrip.size).to eq(first_column.size) + end + end + + it 'specify the number of spaces to indent' do + indent_4_spaces = described_class.print_table(array_3x4, indent: 4, column_separator: ',') + rows = indent_4_spaces.lines + first_cols = rows.map { |row| row.lines(',').first } + + first_cols.each do |first_column| + expect(first_column.lstrip.size).to eq(first_column.size - 4) + end + + expected_result = " column_0 ,column_1 ,column_2\n row0_col0,row0_col1,row0_col2\n row1_col0,row1_col1,row1_col2\n row2_col0,row2_col1,row2_col2\n row3_col0,row3_col1,row3_col2\n" + expect(indent_4_spaces).to eq expected_result + end + end + + describe ':first_colwidth - the width of the first column, which is often a row label' do + + let(:expected_firstwidth_12) { "column_0 ,column_1 ,column_2\nrow0_col0 ,row0_col1,row0_col2\nrow1_col0 ,row1_col1,row1_col2\nrow2_col0 ,row2_col1,row2_col2\nrow3_col0 ,row3_col1,row3_col2\n" } + + + it 'default is none, which sets the width just like all the other columns' do + default_first_width = described_class.print_table(array_3x4, column_separator: ',') + rows = default_first_width.lines + default_col_widths = rows.map { |row| row.lines(',').first.size } + expect(default_col_widths).to eq [10, 10, 10, 10, 10] # 9 + 1 (9 = the width of the widest string in the column; + 1 because the split will include the comma) + end + + it 'sets the width for the first column (often is row labels)' do + first_width_12 = described_class.print_table(array_3x4, colwidth: 12, column_separator: ',') + rows = first_width_12.lines + first_col_widths = rows.map { |row| row.lines(',').first.size } + + expect(first_col_widths).to eq [13, 13, 13, 13, 13] # 12 + 1 (+ 1 because the split will include the comma) + expect(first_width_12).to eq expected_firstwidth_12 + end + end + + + describe ':row_end - can specify the string to append to the end of each row' do + + it 'default is \n' do + expect(default_result.split("\n").size).to eq 5 + end + + it 'specify a row ending' do + expected_3x4_row_end_semicolons = "column_0 column_1 column_2;row0_col0 row0_col1 row0_col2;row1_col0 row1_col1 row1_col2;row2_col0 row2_col1 row2_col2;row3_col0 row3_col1 row3_col2;" + + rows_end_with_semicolon = described_class.print_table(array_3x4, row_end: ';') + expect(rows_end_with_semicolon.lines.size).to eq 1 # just the one whole string, not split by any line endings + expect(rows_end_with_semicolon.lines(";").size).to eq 5 + expect(rows_end_with_semicolon).to eq expected_3x4_row_end_semicolons + end + end + + + describe ':column_separator - can specify the String to append to the end of each column (a column separator)' do + + it 'default is 2 spaces' do + expect(default_result.lines(' ').size).to eq((4 * 3) - 1) + end + + it 'specify a column separator (comma and space)' do + expected_3x4_column_separator_comma = "column_0 ,column_1 ,column_2\nrow0_col0,row0_col1,row0_col2\nrow1_col0,row1_col1,row1_col2\nrow2_col0,row2_col1,row2_col2\nrow3_col0,row3_col1,row3_col2\n" + + + cols_separated_with_comma = described_class.print_table(array_3x4, column_separator: ',') + expect(cols_separated_with_comma.lines(',').size).to eq((4 * 3) - 1) + expect(cols_separated_with_comma).to eq expected_3x4_column_separator_comma + end + end + end + + end + + +end From dfed3aa96c927312773b2e5587805f14518a564a Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Mon, 23 Dec 2019 13:54:38 -0800 Subject: [PATCH 4/5] squash! add gem 'google-api-client' --- Gemfile.lock | 726 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 726 insertions(+) create mode 100644 Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..bd2b9701c --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,726 @@ +GEM + remote: https://rubygems.org/ + specs: + aasm (4.11.1) + actioncable (5.2.3) + actionpack (= 5.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.3) + actionview (= 5.2.3) + activesupport (= 5.2.3) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.3) + activesupport (= 5.2.3) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.3) + activesupport (= 5.2.3) + globalid (>= 0.3.6) + activemodel (5.2.3) + activesupport (= 5.2.3) + activerecord (5.2.3) + activemodel (= 5.2.3) + activesupport (= 5.2.3) + arel (>= 9.0) + activestorage (5.2.3) + actionpack (= 5.2.3) + activerecord (= 5.2.3) + marcel (~> 0.3.1) + activesupport (5.2.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + after_commit_action (1.1.0) + activerecord (>= 3.0.0) + activesupport (>= 3.0.0) + airbrussh (1.4.0) + sshkit (>= 1.6.1, != 1.7.0) + arel (9.0.0) + ast (2.4.0) + autoprefixer-rails (9.7.1) + execjs + aws-eventstream (1.0.3) + aws-partitions (1.238.0) + aws-sdk-core (3.76.0) + aws-eventstream (~> 1.0, >= 1.0.2) + aws-partitions (~> 1, >= 1.228.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.25.0) + aws-sdk-core (~> 3, >= 3.71.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.53.0) + aws-sdk-core (~> 3, >= 3.71.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.1.0) + aws-eventstream (~> 1.0, >= 1.0.2) + axiom-types (0.1.1) + descendants_tracker (~> 0.0.4) + ice_nine (~> 0.11.0) + thread_safe (~> 0.3, >= 0.3.1) + backports (3.15.0) + bcrypt (3.1.13) + better_errors (2.5.1) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + bindex (0.8.1) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.4.5) + msgpack (~> 1.0) + bootstrap (4.1.3) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.9, < 2) + sass (>= 3.5.2) + bootstrap-toggle-rails (2.2.1.0) + bootstrap-will_paginate (1.0.0) + will_paginate + builder (3.2.3) + bullet (6.0.2) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) + byebug (11.0.1) + capistrano (3.11.2) + airbrussh (>= 1.0.0) + i18n + rake (>= 10.0.0) + sshkit (>= 1.9.0) + capistrano-bundler (1.1.4) + capistrano (~> 3.1) + sshkit (~> 1.2) + capistrano-env-config (0.3.0) + capistrano (~> 3.0) + dotenv (~> 2.0) + capistrano-rails (1.4.0) + capistrano (~> 3.1) + capistrano-bundler (~> 1.1) + capistrano-rbenv (2.1.4) + capistrano (~> 3.1) + sshkit (~> 1.3) + capistrano-ssh-doctor (1.0.0) + capistrano (>= 3.1) + capybara (3.29.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.5) + xpath (~> 3.2) + chartkick (3.3.0) + childprocess (1.0.1) + rake (< 13.0) + chronic (0.10.2) + city-state (0.0.13) + rubyzip (~> 1.1) + ckeditor (4.3.0) + orm_adapter (~> 0.5.0) + terrapin + climate_control (0.2.0) + cliver (0.3.2) + codeclimate-engine-rb (0.4.1) + virtus (~> 1.0) + codeclimate-test-reporter (1.0.7) + simplecov + coderay (1.1.2) + coercible (1.0.0) + descendants_tracker (~> 0.0.1) + concurrent-ruby (1.1.5) + cookies_eu (1.7.6) + js_cookie_rails (~> 2.2.0) + counter_culture (2.2.4) + activerecord (>= 4.2) + activesupport (>= 4.2) + after_commit_action (~> 1.0) + coveralls (0.8.23) + json (>= 1.8, < 3) + simplecov (~> 0.16.1) + term-ansicolor (~> 1.3) + thor (>= 0.19.4, < 2.0) + tins (~> 1.6) + crack (0.4.3) + safe_yaml (~> 1.0.0) + crass (1.0.5) + css_parser (1.7.0) + addressable + cucumber (3.1.2) + builder (>= 2.1.2) + cucumber-core (~> 3.2.0) + cucumber-expressions (~> 6.0.1) + cucumber-wire (~> 0.0.1) + diff-lcs (~> 1.3) + gherkin (~> 5.1.0) + multi_json (>= 1.7.5, < 2.0) + multi_test (>= 0.1.2) + cucumber-core (3.2.1) + backports (>= 3.8.0) + cucumber-tag_expressions (~> 1.1.0) + gherkin (~> 5.0) + cucumber-expressions (6.0.1) + cucumber-rails (2.0.0) + capybara (>= 2.12, < 4) + cucumber (>= 3.0.2, < 4) + mime-types (>= 2.0, < 4) + nokogiri (~> 1.8) + railties (>= 4.2, < 7) + cucumber-tag_expressions (1.1.1) + cucumber-timecop (0.0.6) + chronic + cucumber + timecop + cucumber-wire (0.0.1) + database_cleaner (1.7.0) + debug_inspector (0.0.3) + declarative (0.0.10) + declarative-option (0.1.0) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + devise (4.7.1) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) + diff-lcs (1.3) + docile (1.3.2) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.5) + dotenv-rails (2.7.5) + dotenv (= 2.7.5) + railties (>= 3.2, < 6.1) + email_spec (2.2.0) + htmlentities (~> 4.3.3) + launchy (~> 2.1) + mail (~> 2.7) + equalizer (0.0.11) + erb2haml (0.1.5) + html2haml + erubi (1.9.0) + erubis (2.7.0) + exception_notification (4.2.2) + actionmailer (>= 4.0, < 6) + activesupport (>= 4.0, < 6) + exception_notification-rake (0.3.0) + exception_notification (~> 4.2.0) + rake (>= 0.9.0) + execjs (2.7.0) + factory_bot (5.1.1) + activesupport (>= 4.2.0) + factory_bot_rails (5.1.1) + factory_bot (~> 5.1.0) + railties (>= 4.2.0) + faraday (0.17.1) + multipart-post (>= 1.2, < 3) + ffaker (2.13.0) + ffi (1.11.2) + flay (2.12.1) + erubis (~> 2.7.0) + path_expander (~> 1.0) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) + flog (4.6.3) + path_expander (~> 1.0) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.8) + font-awesome-sass (5.5.0.1) + sassc (>= 1.11) + geocoder (1.5.2) + gherkin (5.1.0) + globalid (0.4.2) + activesupport (>= 4.2.0) + google-api-client (0.36.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + googleauth (0.10.0) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.12) + groupdate (4.2.0) + activesupport (>= 5) + haml (5.1.2) + temple (>= 0.8.0) + tilt + haml-rails (2.0.1) + actionpack (>= 5.1) + activesupport (>= 5.1) + haml (>= 4.0.6, < 6.0) + html2haml (>= 1.0.1) + railties (>= 5.1) + hashdiff (1.0.0) + hashie (4.0.0) + high_voltage (3.0.0) + highline (2.0.3) + html2haml (2.2.0) + erubis (~> 2.7.0) + haml (>= 4.0, < 6) + nokogiri (>= 1.6.0) + ruby_parser (~> 3.5) + htmlentities (4.3.4) + http-cookie (1.0.3) + domain_name (~> 0.5) + httparty (0.17.1) + mime-types (~> 3.0) + multi_xml (>= 0.5.2) + httpclient (2.8.3) + i18n (1.7.0) + concurrent-ruby (~> 1.0) + i18n-js (3.5.0) + i18n (>= 0.6.6) + i18n-tasks (0.9.29) + activesupport (>= 4.0.2) + ast (>= 2.1.0) + erubi + highline (>= 2.0.0) + i18n + parser (>= 2.2.3.0) + rails-i18n + rainbow (>= 2.2.2, < 4.0) + terminal-table (>= 1.5.1) + ice_nine (0.11.2) + imgkit (1.6.2) + jaro_winkler (1.5.4) + jbuilder (2.9.1) + activesupport (>= 4.2.0) + jmespath (1.4.0) + jquery-rails (4.3.5) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + js_cookie_rails (2.2.0) + railties (>= 3.1) + json (2.2.0) + jwt (2.2.1) + kwalify (0.7.2) + launchy (2.4.3) + addressable (~> 2.3) + libv8 (6.7.288.46.1-x86_64-darwin-15) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.3.1) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + mailgun-ruby (1.2.0) + rest-client (~> 2.0.2) + marcel (0.3.3) + mimemagic (~> 0.3.2) + memoist (0.16.2) + meta-tags (2.11.1) + actionpack (>= 3.2.0, < 6.1) + method_source (0.9.2) + mime-types (3.3) + mime-types-data (~> 3.2015) + mime-types-data (3.2019.1009) + mimemagic (0.3.3) + mini_magick (4.9.5) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + mini_racer (0.2.4) + libv8 (>= 6.3) + minitest (5.13.0) + msgpack (1.3.1) + multi_json (1.14.1) + multi_test (0.1.2) + multi_xml (0.6.0) + multipart-post (2.1.1) + net-scp (2.0.0) + net-ssh (>= 2.6.5, < 6.0.0) + net-ssh (5.2.0) + netrc (0.11.0) + nio4r (2.5.2) + nokogiri (1.10.5) + mini_portile2 (~> 2.4.0) + nokogumbo (2.0.1) + nokogiri (~> 1.8, >= 1.8.4) + orgnummer (0.2.0) + orm_adapter (0.5.0) + os (1.0.1) + paperclip (6.0.0) + activemodel (>= 4.2.0) + activesupport (>= 4.2.0) + mime-types + mimemagic (~> 0.3.0) + terrapin (~> 0.6.0) + parallel (1.17.0) + parser (2.6.5.0) + ast (~> 2.4.0) + path_expander (1.1.0) + pg (0.21.0) + poltergeist (1.18.1) + capybara (>= 2.1, < 4) + cliver (~> 0.3.1) + websocket-driver (>= 0.2.0) + polyamorous (2.3.0) + activerecord (>= 5.0) + popper_js (1.14.5) + premailer (1.11.1) + addressable + css_parser (>= 1.6.0) + htmlentities (>= 4.0.0) + premailer-rails (1.10.3) + actionmailer (>= 3) + premailer (~> 1.7, >= 1.7.9) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-byebug (3.7.0) + byebug (~> 11.0) + pry (~> 0.10) + psych (3.1.0) + public_suffix (4.0.1) + puma (3.12.1) + pundit (2.1.0) + activesupport (>= 3.0.0) + pundit-matchers (1.6.0) + rspec-rails (>= 3.0.0) + rack (2.0.7) + rack-mini-profiler (1.0.2) + rack (>= 1.2.0) + rack-test (1.1.0) + rack (>= 1.0, < 3) + railroady (1.5.3) + rails (5.2.3) + actioncable (= 5.2.3) + actionmailer (= 5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + activemodel (= 5.2.3) + activerecord (= 5.2.3) + activestorage (= 5.2.3) + activesupport (= 5.2.3) + bundler (>= 1.3.0) + railties (= 5.2.3) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + rails-i18n (5.1.3) + i18n (>= 0.7, < 2) + railties (>= 5.0, < 6) + railties (5.2.3) + actionpack (= 5.2.3) + activesupport (= 5.2.3) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rainbow (3.0.0) + rake (12.3.3) + ransack (2.3.0) + actionpack (>= 5.0) + activerecord (>= 5.0) + activesupport (>= 5.0) + i18n + polyamorous (= 2.3.0) + rb-fsevent (0.10.3) + rb-inotify (0.10.0) + ffi (~> 1.0) + rb-readline (0.5.5) + reek (5.4.0) + codeclimate-engine-rb (~> 0.4.0) + kwalify (~> 0.7.0) + parser (>= 2.5.0.0, < 2.7, != 2.5.1.1) + psych (~> 3.1.0) + rainbow (>= 2.0, < 4.0) + regexp_parser (1.6.0) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + responders (3.0.0) + actionpack (>= 5.0) + railties (>= 5.0) + rest-client (2.0.2) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + retriable (3.1.2) + routing-filter (0.6.2) + actionpack (>= 4.2, < 6) + activesupport (>= 4.2, < 6) + rspec-core (3.8.2) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-rails (3.8.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.2) + rubocop (0.75.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.6) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 1.7) + rubocop-rspec (1.35.0) + rubocop (>= 0.60.0) + ruby-progressbar (1.10.1) + ruby_dep (1.5.0) + ruby_parser (3.14.1) + sexp_processor (~> 4.9) + rubycritic (4.1.0) + flay (~> 2.8) + flog (~> 4.4) + launchy (= 2.4.3) + parser (~> 2.6.0) + rainbow (~> 3.0) + reek (~> 5.0, < 6.0) + ruby_parser (~> 3.8) + tty-which (~> 0.4.0) + virtus (~> 1.0) + rubyzip (1.3.0) + safe_yaml (1.0.5) + sanitize (5.0.0) + crass (~> 1.0.2) + nokogiri (>= 1.8.0) + nokogumbo (~> 2.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.1.0) + railties (>= 5.2.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sassc (2.2.1) + ffi (~> 1.9) + selenium-webdriver (3.142.3) + childprocess (>= 0.5, < 2.0) + rubyzip (~> 1.2, >= 1.2.2) + sexp_processor (4.13.0) + shoulda-matchers (4.1.2) + activesupport (>= 4.2.0) + show_me_the_cookies (5.0.0) + capybara (>= 2, < 4) + signet (0.12.0) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simplecov (0.16.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + slack-notifier (2.3.2) + smarter_csv (1.2.6) + spring (2.1.0) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sshkit (1.20.0) + net-scp (>= 1.1.2) + net-ssh (>= 2.8.0) + temple (0.8.2) + term-ansicolor (1.7.1) + tins (~> 1.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + terrapin (0.6.0) + climate_control (>= 0.0.3, < 1.0) + thor (0.20.3) + thread_safe (0.3.6) + tilt (2.0.10) + timecop (0.9.1) + tins (1.22.0) + tty-which (0.4.1) + turbolinks (5.2.0) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uber (0.1.0) + uglifier (4.1.20) + execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.6) + unicode-display_width (1.6.0) + uniform_notifier (1.13.0) + vcr (5.0.0) + virtus (1.0.5) + axiom-types (~> 0.1) + coercible (~> 1.0) + descendants_tracker (~> 0.0, >= 0.0.3) + equalizer (~> 0.0, >= 0.0.9) + warden (1.2.8) + rack (>= 2.0.6) + web-console (3.7.0) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + webdrivers (3.9.2) + nokogiri (~> 1.6) + rubyzip (~> 1.0) + selenium-webdriver (~> 3.0) + webmock (3.7.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + websocket-driver (0.7.1) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.4) + will_paginate (3.2.1) + wkhtmltoimage-binary (0.12.5) + xpath (3.2.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + aasm (~> 4.11.1) + aws-sdk-s3 + bcrypt (~> 3.1.7) + better_errors + binding_of_caller + bootsnap + bootstrap (~> 4.1.3) + bootstrap-toggle-rails + bootstrap-will_paginate + bullet + capistrano (~> 3.11) + capistrano-bundler (~> 1.1.2) + capistrano-env-config + capistrano-rails (~> 1.4) + capistrano-rbenv (~> 2.0) + capistrano-ssh-doctor (~> 1.0) + chartkick + city-state + ckeditor (~> 4.2, >= 4.2.4) + codeclimate-test-reporter (~> 1.0.0) + cookies_eu + counter_culture (~> 2.0) + coveralls (>= 0.8.21) + cucumber-rails + cucumber-timecop + database_cleaner + devise + dotenv + dotenv-rails + email_spec + erb2haml + exception_notification + exception_notification-rake (~> 0.3.0) + factory_bot_rails + ffaker + font-awesome-sass (~> 5.5.0) + geocoder + google-api-client + groupdate + haml-rails + hashie + high_voltage (~> 3.0.0) + httparty + i18n-js (>= 3.0.0.rc11) + i18n-tasks (~> 0.9.21) + imgkit + jbuilder (~> 2.5) + jquery-rails + jwt + launchy + mailgun-ruby + meta-tags + mini_magick + mini_racer + nokogiri + orgnummer + paperclip (~> 6.0.0) + pg (~> 0.18) + poltergeist + popper_js (~> 1.14.3) + premailer-rails + pry + pry-byebug + puma (~> 3.0) + pundit + pundit-matchers + rack-mini-profiler + railroady + rails (= 5.2.3) + rake + ransack + rb-readline + routing-filter + rspec-rails + rubocop + rubocop-rspec + rubycritic + rubyzip (>= 1.2.1) + sanitize + sass-rails (~> 5.0) + selenium-webdriver + shoulda-matchers + show_me_the_cookies + simplecov (>= 0.13.0) + slack-notifier + smarter_csv + spring + spring-watcher-listen (~> 2.0.0) + timecop + turbolinks (~> 5) + uglifier (>= 1.3.0) + vcr + web-console + webdrivers (~> 3.0) + webmock + will_paginate + wkhtmltoimage-binary + +RUBY VERSION + ruby 2.5.1p57 + +BUNDLED WITH + 1.17.3 From 92661acd03bc5c6d8444fa61ad35001a9cac6108 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Mon, 23 Dec 2019 13:55:14 -0800 Subject: [PATCH 5/5] classes to fetch and format GoogleAnalytics; get company pages --- app/services/google-analytics/ga_explorer.rb | 177 ++++++++++++++++++ .../google-analytics/ga_response_displayer.rb | 133 +++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 app/services/google-analytics/ga_explorer.rb create mode 100644 app/services/google-analytics/ga_response_displayer.rb diff --git a/app/services/google-analytics/ga_explorer.rb b/app/services/google-analytics/ga_explorer.rb new file mode 100644 index 000000000..20b4fde77 --- /dev/null +++ b/app/services/google-analytics/ga_explorer.rb @@ -0,0 +1,177 @@ +require 'googleauth' +require 'google/apis/analyticsreporting_v4' + + +#-------------------------- +# +# @class GAExplorer +# +# @desc Responsibility: Fetch and explore data from Google Analytics. Expects +# the credentials to be in the JSON file identified by JSON_SECRETS_FILE. +# (Currently this class reads the file name from ENV.) +# +# @example +# fetched_ga_reports = GAExplorer.get_reports # Gets the default data: company page hits for the last 30 days +# +# +# Google API Analytics classes: @see google-api-client-0.36.0/generated/google/apis/analyticsreporting_v4/classes.rb +# +# Helpful code: +# @see https://medium.com/fiatinsight/using-the-google-analytics-reporting-api-v4-with-rails-8d3c2f689807 +# @see https://gist.github.com/CoryFoy/9edf1e039e174c00c209e930a1720ce0 +# +# Definitions of API Analytics classes _and_ parameters and eums +# @see https://www.any-api.com/googleapis_com/analyticsreporting/docs/Definitions +# Ex: DimensionFilterClause +# @see https://www.any-api.com/googleapis_com/analyticsreporting/docs/Definitions/DimensionFilterClause +# +# +# Can use GAResponseDisplayer to display the response as a table. +# Ex: +# fetched_ga_reports = GAExplorer.get_reports +# GAResponseDisplayer.response_reports_as_tables(fetched_ga_reports) +# +# +# @authors Herman Lule (Luleherll @ github) and Ashley Engelund (ashley.engelund@gmail.com weedySeaDragon @ github) +# @date 12/12/19 +# +#-------------------------- +# +class GAExplorer + + ANALYTICS4 = Google::Apis::AnalyticsreportingV4 # short alias for the Analytics module + + SCOPE = ANALYTICS4::AUTH_ANALYTICS_READONLY.freeze + JSON_SECRETS_FILE = File.join(Rails.root, ENV['SHF_GOOGLE_APPLICATION_CREDENTIALS_FILE']) + + GA_PAGE_PATH = 'ga:pagePath' + GA_PAGE_VIEWS = 'ga:pageviews' + GA_COUNTRY_ISOCODE = 'ga:countryIsoCode' + + COMPANIY_PAGES_REGEXP = '/hundforetag/[^/]*$'.freeze + + # ------------------------------------------------------------------------- + + + # Get data from Google Analytics for company page hits for the last 30 days + # + # @return [Array] - the reports with the data. + # (Will have just 1 report based on the request made.) + def self.get_reports + analytics = authorized_analytics + analytics.batch_get_reports(build_request_with(:company_page_hits_30d)) + end + + + # Create the Hash parameters for a ReportRequest by calling the report_info_method + # + # @param [Symbol] report_info_method - the method to call to create the Hash. Default = :company_page_hits_30d + # @param [Array] date_ranges - the date ranges for the report. Default = [Google::Apis::AnalyticsreportingV4::DateRange.new(start_date: '30DaysAgo', end_date: 'today')] + # + # @return [Google::Apis::AnalyticsreportingV4::GetReportsRequest] - a report request with the single report request constructed with the :report_info_method for the date ranges :date_ranges + # + def self.build_request_with(report_info_method = :company_page_hits_30d, + date_ranges: [date_range_days_ago(30)]) + + report_info = { view_id: ENV['SHF_GOOGLE_ANALYTICS_VIEW_ID'], + date_ranges: date_ranges, + }.merge(send report_info_method.to_sym) + + ANALYTICS4::GetReportsRequest.new( + report_requests: [ANALYTICS4::ReportRequest.new(report_info)] + ) + end + + + # @return [Google::Apis::AnalyticsreportingV4::AnalyticsReportingService] - the analytics service, authorized with the credentials from the JSON Google credentials file + def self.authorized_analytics + credentials = Google::Auth::ServiceAccountCredentials.make_creds({ json_key_io: File.open(JSON_SECRETS_FILE), scope: SCOPE }) + + analytics = ANALYTICS4::AnalyticsReportingService.new + analytics.authorization = credentials + analytics + end + + + # Create the report_info Hash for a ReportRequest that will return + # a page page dimension, with the pageViews metric for it, + # for the past 30 days, ordered (sorted) by the page path. + # This also includes a pivot (= a 2nd metric to apply to the dimension) + # for the pageViews by the country ISO code. This will help see which + # page views are coming from countries that are meaningful, and what are cruft. + # + # @return [Hash] - a hash with :metrics, :dimensions, :dimensionFilterClauses, :order_bys + def self.company_page_hits_30d + + return { dimensions: [page_path_dimension], + dimension_filter_clauses: [dim_filter_company_pages], + metrics: [page_views_metric], + pivots: [iso_country_pivot], + order_bys: [ANALYTICS4::OrderBy.new(field_name: GA_PAGE_PATH)] + } + end + + + # 'dimension' is the list of rows in a table of info returned + # @return Google::Apis::AnalyticsreportingV4::Dimension for page paths + def self.page_path_dimension + ANALYTICS4::Dimension.new(name: GA_PAGE_PATH) + end + + + # A Dimension Filter that will get + # all pages that match the RegularExpression '/hundforetag/[^/]*$'] + # This will match pages that end with /hundforetag/ + # Ex: + # /hundforetag/100 + # /en/hundforetag/100 + # /sv/hundforetag/100 + # /hundforetag/100?fbxxx-zzzzzzzzz + # + # @return [Google::Apis::AnalyticsreportingV4::DimensionFilter] - the DimensionFilter with the regular expression for company pages + def self.dim_filter_company_pages + dim_filters = [ + ANALYTICS4::DimensionFilter.new(dimension_name: GA_PAGE_PATH, + operator: 'REGEXP', + expressions: [COMPANIY_PAGES_REGEXP]) + ] + ANALYTICS4::DimensionFilterClause.new(filters: dim_filters) + end + + + # 'metric' is the 'columns' in a table of info returned + # # @return Google::Apis::AnalyticsreportingV4::Metric for page views + def self.page_views_metric + ANALYTICS4::Metric.new(expression: GA_PAGE_VIEWS) + end + + + # @return Google::Apis::AnalyticsreportingV4::Pivot on the country dimension with the page_view_metric metric + def self.iso_country_pivot + ANALYTICS4::Pivot.new(dimensions: [country_dimension], metrics: [page_views_metric]) + end + + + # 'dimension' is the list of rows in a table of info returned + # @return Google::Apis::AnalyticsreportingV4::Dimension for country ISO codes + def self.country_dimension + ANALYTICS4::Dimension.new(name: GA_COUNTRY_ISOCODE) + end + + + # return a DateRange that is :days_ago from today + # @see https://developers.google.com/analytics/devguides/reporting/core/v3/reference#startDate + # Date values can be for a specific date by using the pattern YYYY-MM-DD + # or relative by using today, yesterday, or the NdaysAgo pattern. + # Values must match [0-9]{4}-[0-9]{2}-[0-9]{2}|today|yesterday|[0-9]+(daysAgo). + # + # @param [Integer] days_ago - the number of days ago for the start of the date range (relative to today). Default is 30 + # @param [Integer] start_days_ago - the number of days ago for the ned of the date range (relative to today). Default is 0 (= today) + # @return [Google::Apis::AnalyticsreportingV4::DateRange] - a date range that is :days_ago relative to 'today' (= '0DaysAgo') + # + + def self.date_range_days_ago(days_ago = 30, start_days_ago = 0) + ANALYTICS4::DateRange.new(start_date: "#{days_ago.to_i}DaysAgo", end_date: "#{start_days_ago.to_i}DaysAgo") + end + +end diff --git a/app/services/google-analytics/ga_response_displayer.rb b/app/services/google-analytics/ga_response_displayer.rb new file mode 100644 index 000000000..b559f85b1 --- /dev/null +++ b/app/services/google-analytics/ga_response_displayer.rb @@ -0,0 +1,133 @@ +require 'google/apis/analyticsreporting_v4' +require_relative File.join(__dir__, 'array_arrays_as_table') + +#-------------------------- +# +# @class GAResponseDisplayer +# +# @desc Responsibility: Display a Google::Apis::AnalyticsreportingV4::GetReportsResponse in a readable form +# +# @example +# # Assume that the list of reports is fetched. This example uses GAExplorer to get reports +# fetched_ga_reports = GAExplorer.get_reports # Gets SHF company page hits for the last 30 days +# puts GAResponseDisplayer.response_reports_as_tables +# +# @author Ashley Engelund (ashley.engelund@gmail.com weedySeaDragon @ github) +# @date 12/14/19 +# +#-------------------------- +# +class GAResponseDisplayer + + ANALYTICS4 = Google::Apis::AnalyticsreportingV4 # short alias for the Analytics module + + # Format each report in the response as a simple table. + # + # @return [String] - reports formatted as simple tables, each separated by 2 blank lines (\n\n) + def self.response_reports_as_tables(response = ANALYTICS4::GetReportsResponse.new) + response.reports.map { |report| "#{report_as_table(report)}\n\n" }.join('') + end + + + # Format the report as a table. Append simple totals at the bottom + # + # @return [String] - the formatted info + def self.report_as_table(report) + data = report.data + rows = data.rows + + max_dim_name_length = max_dimension_name_length(rows) + col_heads = column_headers(report.column_header) + + rows_and_cols_as_table(rows, col_heads, max_dim_name_length) + + totals_as_columns(data.totals, col_heads, max_dim_name_length) + + " #{data.row_count} rows\n" + end + + + # @return [Array] - array of Strings that form the column headers + # TODO This only prints out the first dimension. If there are multiple dimensions, it won't print them. + def self.column_headers(report_column_header) + + dimension_headers = report_column_header.dimensions + metric_header = report_column_header.metric_header + metric_headers = metric_header.metric_header_entries + pivot_col_heads = pivot_col_headers(metric_header.pivot_headers) + + [[dimension_headers.first] + metric_headers.map(&:name) + pivot_col_heads.flatten] + end + + + def self.max_dimension_name_length(rows) + # rubocop:disable UncommunicativeVariableName + rows.map { |row| row.dimensions }.flatten.max { |dimension_a, dimension_b| dimension_a.length <=> dimension_b.length }.length + end + + + # @return [String] - rows and column headers formatting as a table + def self.rows_and_cols_as_table(rows, col_heads, max_row_dimension_length = 0) + rows_as_arrays = rows.map { |row| report_row_to_a(row) } + ArrayArraysAsTable.print_table(col_heads + rows_as_arrays, colwidth: max_row_dimension_length) + end + + + # @return [String] - a separator line ('-----') followed by the totals, formatted in columns + def self.totals_as_columns(totals, col_heads, max_row_dimension_length = 0) + total_rows = [] + totals.each do |data_total| + total_rows << ['TOTAL '.ljust(max_row_dimension_length)].concat(date_range_value_to_a(data_total)) + end + + total_separator(max_row_dimension_length) + + ArrayArraysAsTable.print_table(col_heads + total_rows, colwidth: max_row_dimension_length) + end + + + # Could use map, but it looses some readability + def self.pivot_col_headers(pivot_headers) + pivot_col_heads = [] + pivot_headers.each { |pivot_header| pivot_col_heads << pivot_header.pivot_header_entries.map(&:dimension_values).flatten } + pivot_col_heads + end + + + # Return the report row as an array. Dimension name = array[0] and values[0..n] are array entries [1.. n+1] + # Every entry in the Array is a String + # + # @return [Array] - array of Strings + def self.report_row_to_a(report_row, max_row_dim_length: 0) + + row_dimensions = report_row.dimensions + row_metrics = report_row.metrics + + row_dimensions.map do |row_dimension| + dimension_with_metrics_to_a(row_dimension.ljust(max_row_dim_length), row_metrics) + end.flatten + end + + + # Array with the row dimension, followed by the metric values + # Could use map, but it looses some readability: + # row_metrics.map{ | date_range_value | [row_dimension_value] + date_range_value_to_a(date_range_value) }.flatten + def self.dimension_with_metrics_to_a(row_dimension_value, row_metrics) + dim_metrics = [] + row_metrics.each do |date_range_value| + dim_metrics << row_dimension_value + dim_metrics.concat(date_range_value_to_a(date_range_value)) + end + dim_metrics + end + + + def self.date_range_value_to_a(date_range_value) + [].concat(date_range_value.values).concat(date_range_value.pivot_value_regions.map(&:values).flatten) + end + + + def self.total_separator(max_col_length) + # Arbitrarily adding 14 more dashes to make the line longer + # TODO make this a constant; don't make it arbitrary + '-' * (max_col_length + 14) + "\n" + end + +end