shopify_dashboard_plus 0.0.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +16 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile.lock +99 -35
  5. data/README.md +16 -7
  6. data/Rakefile +13 -0
  7. data/bin/shopify_dashboard_plus.rb +1 -1
  8. data/config.ru +4 -0
  9. data/lib/shopify_dashboard_plus.rb +40 -52
  10. data/lib/shopify_dashboard_plus/currency.rb +31 -0
  11. data/lib/shopify_dashboard_plus/discount_report.rb +36 -0
  12. data/lib/shopify_dashboard_plus/helpers.rb +65 -51
  13. data/lib/shopify_dashboard_plus/report.rb +4 -196
  14. data/lib/shopify_dashboard_plus/revenue_report.rb +55 -0
  15. data/lib/shopify_dashboard_plus/sales_report.rb +86 -0
  16. data/lib/shopify_dashboard_plus/traffic_report.rb +65 -0
  17. data/lib/shopify_dashboard_plus/version.rb +3 -1
  18. data/shopify_dashboard_plus.gemspec +17 -7
  19. data/test/fixtures/vcr_cassettes/.gitkeep +0 -0
  20. data/test/fixtures/vcr_cassettes/authenticate.yml +88 -0
  21. data/test/fixtures/vcr_cassettes/multiple_pages_orders.yml +1544 -0
  22. data/test/fixtures/vcr_cassettes/orders_from_2010_01_01.yml +815 -0
  23. data/test/fixtures/vcr_cassettes/orders_from_2010_01_01_to_2015_01_01.yml +566 -0
  24. data/test/fixtures/vcr_cassettes/orders_no_paramaters.yml +81 -0
  25. data/test/fixtures/vcr_cassettes/orders_none.yml +77 -0
  26. data/test/fixtures/vcr_cassettes/orders_to_2015-06-26.yml +81 -0
  27. data/test/resources/anonymizer.rb +125 -0
  28. data/test/resources/modify_data.rb +110 -0
  29. data/test/strip_sensitive_data.rb +79 -0
  30. data/test/test_app.rb +60 -0
  31. data/test/test_frontend.rb +76 -0
  32. data/test/test_mockdata.rb +237 -0
  33. data/views/connect.erb +6 -6
  34. data/views/layout.erb +2 -3
  35. data/views/report.erb +1 -1
  36. metadata +190 -59
@@ -1,37 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'currency'
4
+
1
5
  module ApplicationHelpers
2
6
 
3
7
  include Rack::Utils
8
+ using Currency
9
+
4
10
  alias_method :h, :escape_html
5
11
 
6
- DESIRED_FIELDS = [
7
- "total_price",
8
- "created_at",
9
- "billing_address",
10
- "currency",
11
- "line_items",
12
- "customer",
13
- "referring_site",
14
- "discount_codes"
15
- ]
12
+ DESIRED_FIELDS = %w[
13
+ total_price
14
+ created_at
15
+ billing_address
16
+ currency
17
+ line_items
18
+ customer
19
+ referring_site
20
+ discount_codes
21
+ ].freeze
22
+
23
+ ## Authentication Helpers
24
+
25
+ def authenticated?
26
+ session[:logged_in]
27
+ end
28
+
16
29
 
17
30
  ## Connection & Setup Helpers
18
-
31
+
19
32
  def set_connection(key, pwd, name)
20
- shop_url = "https://#{key}:#{pwd}@#{name}.myshopify.com/admin"
21
- ShopifyAPI::Base.site = shop_url
33
+ ShopifyAPI::Base.site = "https://#{key}:#{pwd}@#{name}.myshopify.com/admin"
22
34
  shop = ShopifyAPI::Shop.current
35
+
23
36
  $shop_name = name
37
+ $currency = shop.money_with_currency_format
38
+
39
+ session[:logged_in] = true
24
40
  open_connection
25
- rescue
41
+ rescue SocketError, ActiveResource::ResourceNotFound => e
42
+ puts "Exception: #{e}"
26
43
  close_connection
27
44
  end
28
45
 
29
46
  def close_connection
30
47
  $connected = false
48
+ session[:logged_in] = false
31
49
  end
32
50
 
33
51
  def open_connection
34
52
  $connected = true
53
+ session[:logged_in] = true
35
54
  end
36
55
 
37
56
  def connected?
@@ -71,11 +90,11 @@ module ApplicationHelpers
71
90
  ## Metrics Helpers
72
91
 
73
92
  def max_hash_key_exclude_value(unsorted_hash, exclude_value)
74
- unsorted_hash.sort_by{ |k, v| v }.map{ |k, v| [k, v] unless k.downcase == exclude_value }.compact.last
93
+ unsorted_hash.sort_by { |_, v| v }.map { |k, v| [k, v] unless k.downcase == exclude_value }.compact.last
75
94
  end
76
95
 
77
96
  def display_as_currency(value)
78
- ShopifyAPI::Shop.current.money_with_currency_format.gsub("{{amount}}", value.to_s)
97
+ $currency.gsub("{{amount}}", value.to_s)
79
98
  rescue
80
99
  'N/A'
81
100
  end
@@ -92,41 +111,39 @@ module ApplicationHelpers
92
111
  end
93
112
 
94
113
  def get_average_revenue(total_revenue, duration)
95
- (total_revenue/duration).round(2)
114
+ (total_revenue / duration).round(2)
96
115
  rescue
97
116
  'N/A'
98
117
  end
99
-
118
+
100
119
  def get_daily_revenues(start_date, end_date, orders)
101
120
  # Create hash entry for every day within interval over which to inspect sales
102
121
  revenue_per_day = {}
103
122
  days = get_date_range(start_date, end_date)
104
- (0..days).each{ |day| revenue_per_day[(DateTime.parse(end_date) - day).strftime("%Y-%m-%d")] = 0 }
123
+ (0..days).each { |day| revenue_per_day[(DateTime.parse(end_date) - day).strftime("%Y-%m-%d")] = 0 }
105
124
 
106
125
  # Retreive array of ActiveRecord::Collections, each containing orders between the start and end date
107
- order_details = orders.map{ |order| [order.created_at, order.total_price.to_f] }
108
-
126
+ order_details = orders.map { |order| [order.created_at, order.total_price.to_f] }
127
+
109
128
  # Filter order details into daily totals and return
110
129
  order_details.each do |(date, total)|
111
130
  day_index = DateTime.parse(date).strftime('%Y-%m-%d')
112
131
  revenue_per_day[day_index] = revenue_per_day[day_index].plus(total)
113
132
  end
133
+
114
134
  revenue_per_day
115
135
  end
116
136
 
117
137
  def hash_to_graph_format(sales, merge_results: false)
118
-
119
138
  # ChartKick requires a strange format to build graphs. For instance, an array of
120
139
  # {:name => <item_name>, :data => [[<customer_id>, <item_price>], [<customer_id>, <item_price>]]}
121
140
  # places <customer_id> on the independent (x) axis, and stacks each item (item_name) on the y-axis by price (item_price)
122
141
 
123
- name_hash = sales.map{ |sale| {:name => sale[:name], :data => []} }.uniq
124
-
142
+ name_hash = sales.map { |sale| { :name => sale[:name], :data => [] } }.uniq
143
+
125
144
  sales.map do |old_hash|
126
145
  name_hash.map do |new_hash|
127
- if old_hash[:name] == new_hash[:name]
128
- new_hash[:data].push(old_hash[:data])
129
- end
146
+ new_hash[:data].push(old_hash[:data]) if old_hash[:name] == new_hash[:name]
130
147
  end
131
148
  end
132
149
 
@@ -146,38 +163,34 @@ module ApplicationHelpers
146
163
  name_hash
147
164
  end
148
165
 
149
- # Return order query parameters hash
150
166
  def order_parameters_paginate(start_date, end_date, page)
151
- {
152
- :created_at_min => start_date + " 0:00",
153
- :created_at_max => end_date + " 23:59:59",
154
- :limit => 250,
155
- :page => page,
156
- :fields => DESIRED_FIELDS
167
+ {
168
+ :created_at_min => start_date + " 0:00",
169
+ :created_at_max => end_date + " 23:59:59",
170
+ :limit => 250,
171
+ :page => page,
172
+ :fields => DESIRED_FIELDS
157
173
  }
158
174
  end
159
175
 
160
-
161
- # Return array of ActiveRecord::Collections, each containing up to :limit (250) orders
162
- # Continue to query next page until less than :limit orders are returned, indicating no next pages with orders matching query
163
176
  def get_list_of_orders(start_date, end_date)
177
+ # Return array of ActiveRecord::Collections, each containing up to :limit (250) orders
178
+ # Continue to query next page until less than :limit orders are returned, indicating no next pages with orders matching query
164
179
 
165
180
  # Get first 250 results matching query
166
181
  params = order_parameters_paginate(start_date, end_date, 1)
167
182
  revenue_metrics = [ShopifyAPI::Order.find(:all, :params => params)]
168
-
183
+
169
184
  # If the amount of results equal to the limit (250) were returned, pass the query on to the next page (orders 251 to 500)
170
185
  while revenue_metrics.last.length == 250
171
186
  params = order_parameters_paginate(start_date, end_date, revenue_metrics.length + 1)
172
187
  revenue_metrics << ShopifyAPI::Order.find(:all, :params => params)
173
188
  end
174
189
 
175
- revenue_metrics.flat_map{ |orders| orders.map{ |order| order }}
190
+ revenue_metrics.flat_map { |orders| orders.map { |order| order } }
176
191
  end
177
-
178
192
 
179
193
  def get_detailed_revenue_metrics(start_date, end_date = DateTime.now)
180
-
181
194
  order_list = get_list_of_orders(start_date, end_date)
182
195
 
183
196
  # Revenue
@@ -185,22 +198,23 @@ module ApplicationHelpers
185
198
  duration = get_date_range(start_date, end_date) + 1
186
199
  avg_revenue = get_average_revenue(total_revenue, duration)
187
200
  daily_revenue = get_daily_revenues(start_date, end_date, order_list)
188
- max_daily_revenue = daily_revenue.max_by{ |k,v| v }[1]
189
-
201
+ max_daily_revenue = daily_revenue.max_by { |_, v| v }[1]
202
+
190
203
  # Retrieve Metrics
191
- sales_report = ShopifyDashboardPlus::SalesReport.new(order_list).to_h
192
- revenue_report = ShopifyDashboardPlus::RevenueReport.new(order_list).to_h
193
- traffic_report = ShopifyDashboardPlus::TrafficReport.new(order_list).to_h
204
+ sales_report = ShopifyDashboardPlus::SalesReport.new(order_list).to_h
205
+ revenue_report = ShopifyDashboardPlus::RevenueReport.new(order_list).to_h
206
+ traffic_report = ShopifyDashboardPlus::TrafficReport.new(order_list).to_h
194
207
  discounts_report = ShopifyDashboardPlus::DiscountReport.new(order_list).to_h
195
- metrics = {
196
- :total_revenue => total_revenue,
197
- :average_revenue => avg_revenue,
198
- :daily_revenue => daily_revenue,
208
+ metrics = {
209
+ :total_revenue => total_revenue,
210
+ :average_revenue => avg_revenue,
211
+ :daily_revenue => daily_revenue,
199
212
  :max_daily_revenue => max_daily_revenue,
200
- :duration => duration
213
+ :duration => duration
201
214
  }
202
215
 
203
216
  [sales_report, revenue_report, traffic_report, discounts_report, metrics].inject(&:merge)
204
217
  end
205
218
 
206
219
  end
220
+
@@ -1,206 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'helpers'
2
4
 
3
5
  module ShopifyDashboardPlus
4
-
5
6
  class Report
6
7
  include ApplicationHelpers
7
8
 
8
9
  def initialize(orders)
9
- @orders = orders
10
- @line_items = orders.flat_map{ |order| order.line_items.map { |line_item| line_item }}
10
+ @orders = orders
11
+ @line_items = orders.flat_map { |order| order.line_items.map { |line_item| line_item } }
11
12
  end
12
13
  end
13
-
14
-
15
- class SalesReport < Report
16
- def sales_per_country
17
- sales_per_country = Hash.new(0)
18
-
19
- @orders.each do |order|
20
- sales_per_country[order.billing_address.country] += 1 if order.attributes['billing_address']
21
- end
22
- sales_per_country
23
- end
24
-
25
- def sales_per_product
26
- sales_per_product = Hash.new(0)
27
-
28
- @line_items.each do |item|
29
- sales_per_product[item.title] += 1
30
- end
31
-
32
- sales_per_product
33
- end
34
-
35
- def sales_per_customer
36
- customer_sales = []
37
- @orders.each do |order|
38
- order.line_items.each do |item|
39
- customer_name = "#{order.customer.first_name} #{order.customer.last_name} (#{order.customer.email})"
40
- customer_sales.push({ :name => item.title,
41
- :data => [customer_name, item.price.to_f]})
42
- end
43
- end
44
- hash_to_graph_format(customer_sales)
45
- end
46
-
47
- def sales_per_price_point
48
- sales_per_price_point = Hash.new(0)
49
-
50
- @line_items.each do |item|
51
- sales_per_price_point[item.price] += 1
52
- end
53
- sales_per_price_point.sort_by{ |x,y| x.to_f }.to_h rescue {}
54
- end
55
-
56
- def number_of_sales
57
- @orders.length
58
- end
59
-
60
- def currencies_per_sale
61
- currencies = Hash.new(0)
62
-
63
- @orders.each do |order|
64
- currencies[order.currency] += 1 if order.attributes['currency']
65
- end
66
- currencies
67
- end
68
-
69
- def to_h
70
- {
71
- :currencies_per_sale => currencies_per_sale,
72
- :most_used_currency => currencies_per_sale.sort_by{ |k, v| v }.last,
73
- :sales_per_country => sales_per_country,
74
- :most_sales_per_country => sales_per_country.sort_by{ |k, v| v }.last,
75
- :sales_per_price => sales_per_price_point,
76
- :top_selling_price_point => sales_per_price_point.sort_by{ |k, v| v }.last,
77
- :sales_per_product => sales_per_product,
78
- :top_selling_product => sales_per_product.sort_by{ |k, v| v }.last,
79
- :sales_per_customer => sales_per_customer,
80
- :number_of_sales => number_of_sales
81
- }
82
- end
83
- end
84
-
85
-
86
- class RevenueReport < Report
87
- def revenue_per_country
88
- revenue_per_country = []
89
- @orders.each do |order|
90
- order.line_items.each do |item|
91
- revenue_per_country.push({:name => item.title,
92
- :data => [order.billing_address.country, item.price.to_f]})
93
- end
94
- end
95
- hash_to_graph_format(revenue_per_country, merge_results: true)
96
- end
97
-
98
- def revenue_per_price_point
99
- revenue_per_price_point = Hash.new(0)
100
- @line_items.each do |item|
101
- revenue_per_price_point[item.price] = revenue_per_price_point[item.price].plus(item.price)
102
- end
103
- revenue_per_price_point.sort_by{ |x,y| x.to_f }.to_h rescue {}
104
- end
105
-
106
- def revenue_per_product
107
- revenue_per_product = Hash.new(0.0)
108
- @line_items.each do |item|
109
- revenue_per_product[item.title] = revenue_per_product[item.title].plus(item.price)
110
- end
111
- revenue_per_product
112
- end
113
-
114
- def to_h
115
- {
116
- :revenue_per_country => revenue_per_country,
117
- :revenue_per_product => revenue_per_product,
118
- :top_grossing_product => revenue_per_product.sort_by{ |k, v| v }.last,
119
- :revenue_per_price_point => revenue_per_price_point,
120
- :top_grossing_price_point => revenue_per_price_point.sort_by{ |k, v| v}.last
121
- }
122
- end
123
- end
124
-
125
-
126
- class DiscountReport < Report
127
-
128
- def discount_usage
129
- discount_value, discount_used = Hash.new(0.0), Hash.new(0)
130
-
131
- @orders.each do |order|
132
- if order.attributes['discount_codes']
133
- order.discount_codes.each do |discount_code|
134
- discount_value[discount_code.code] = discount_value[discount_code.code].plus(discount_code.amount)
135
- discount_used[discount_code.code] += 1
136
- end
137
- end
138
- end
139
- {
140
- :discount_savings => discount_value,
141
- :top_discount_savings => discount_value.sort_by{ |k, v| v }.last,
142
- :discount_quantity => discount_used,
143
- :most_used_discount_code => discount_used.sort_by{ |k, v| v }.last
144
- }
145
- end
146
-
147
- def to_h
148
- discount_usage
149
- end
150
- end
151
-
152
-
153
- class TrafficReport < Report
154
-
155
- def number_of_referrals
156
- referring_sites, referring_pages = Hash.new(0), Hash.new(0)
157
-
158
- @orders.each do |order|
159
- if order.attributes['referring_site'].empty?
160
- referring_pages['None'] += 1
161
- referring_sites['None'] += 1
162
- else
163
- host = get_host(order.referring_site)
164
- page = strip_protocol(order.referring_site)
165
- referring_pages[page] += 1
166
- referring_sites[host] += 1
167
- end
168
- end
169
- {
170
- :referral_sites => referring_sites.sort().to_h,
171
- :top_referral_site => max_hash_key_exclude_value(referring_sites, 'none'),
172
- :referral_pages => referring_pages.sort().to_h,
173
- :top_referral_page => max_hash_key_exclude_value(referring_pages, 'none')
174
- }
175
- end
176
-
177
- def traffic_revenue
178
- revenue_per_referral_page, revenue_per_referral_site = Hash.new(0.0), Hash.new(0.0)
179
-
180
- @orders.each do |order|
181
- order.line_items.each do |item|
182
- if order.attributes['referring_site'].empty?
183
- revenue_per_referral_page['None'] = revenue_per_referral_page['None'].plus(item.price)
184
- revenue_per_referral_site['None'] = revenue_per_referral_site['None'].plus(item.price)
185
- else
186
- host = get_host(order.referring_site)
187
- page = strip_protocol(order.referring_site)
188
- revenue_per_referral_site[host] = revenue_per_referral_site[host].plus(item.price)
189
- revenue_per_referral_page[page] = revenue_per_referral_page[page].plus(item.price)
190
- end
191
- end
192
- end
193
- {
194
- :revenue_per_referral_site => revenue_per_referral_site.sort().to_h,
195
- :top_referral_site_revenue => max_hash_key_exclude_value(revenue_per_referral_site, 'none'),
196
- :revenue_per_referral_page => revenue_per_referral_page.sort().to_h,
197
- :top_referral_page_revenue => max_hash_key_exclude_value(revenue_per_referral_page, 'none')
198
- }
199
- end
200
-
201
- def to_h
202
- traffic_revenue.merge(number_of_referrals)
203
- end
204
- end
205
-
206
14
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'currency'
4
+ require_relative 'helpers'
5
+ require_relative 'report'
6
+
7
+ module ShopifyDashboardPlus
8
+ class RevenueReport < Report
9
+ using Currency
10
+
11
+ def revenue_per_country
12
+ revenue_per_country = []
13
+ @orders.each do |order|
14
+ order.line_items.each do |item|
15
+ revenue_per_country.push(
16
+ :name => item.title,
17
+ :data => [order.billing_address.country, item.price.to_f]
18
+ )
19
+ end
20
+ end
21
+
22
+ hash_to_graph_format(revenue_per_country, merge_results: true)
23
+ end
24
+
25
+ def revenue_per_price_point
26
+ revenue_per_price_point = Hash.new(0)
27
+ @line_items.each do |item|
28
+ revenue_per_price_point[item.price] = revenue_per_price_point[item.price].plus(item.price)
29
+ end
30
+
31
+ revenue_per_price_point.sort_by { |x, _| x.to_f }.to_h
32
+ rescue
33
+ {}
34
+ end
35
+
36
+ def revenue_per_product
37
+ revenue_per_product = Hash.new(0.0)
38
+ @line_items.each do |item|
39
+ revenue_per_product[item.title] = revenue_per_product[item.title].plus(item.price)
40
+ end
41
+
42
+ revenue_per_product
43
+ end
44
+
45
+ def to_h
46
+ {
47
+ :revenue_per_country => revenue_per_country,
48
+ :revenue_per_product => revenue_per_product,
49
+ :top_grossing_product => revenue_per_product.sort_by { |_, v| v }.last,
50
+ :revenue_per_price_point => revenue_per_price_point,
51
+ :top_grossing_price_point => revenue_per_price_point.sort_by { |_, v| v }.last
52
+ }
53
+ end
54
+ end
55
+ end