shopify_dashboard_plus 0.0.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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