shopify_dashboard_plus 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5eec23dd7be2f6961002521056ee0b78c7b16f47
4
- data.tar.gz: 9476b25596ca4545fe7700488a54322210408195
3
+ metadata.gz: f9cb5e599bcb7d985aecabdaa34959e0bd0ac515
4
+ data.tar.gz: a6ddcb38acf2a363c585ab864b93b62003bd3c5d
5
5
  SHA512:
6
- metadata.gz: cd5f93de262d3d3a86f49c12d3d8800e6d398372188b6e565d9f6fbbb2ff65185b9dc61a2cb25726b77b1b036ca12106497d174aed3396d48ae1e849bffce3c0
7
- data.tar.gz: 8b1526903f8805beaecbb761dae82156c4eecd9fc200950488e7bc74ae2f96cf6448dc05e2bfb703d6cd5299f23e5dc70bebf4adcb95cf83191f32f116e5a660
6
+ metadata.gz: 698165b5c02d12e72464cf9030dea2ceaeb06ce302d04f442f24a56a71d6b8c165ce9c70c914b1d9a44a51a63c463b71371eb924f46ff2543ccdc671506cc469
7
+ data.tar.gz: b5e7b6ab374f757618c6f2b8e9026918b0dbe50d7537159554987f527446430af7d578d4388f408f2c11af70242d0eee0e6e2e27b4101f37c23ff40ed109b6cb
data/README.md CHANGED
@@ -20,6 +20,8 @@ Choose the interval over which to get data and see it displayed as:
20
20
  * Proportion of Items Sold Per Price Point
21
21
  * Number of Items Sold Per Price Point
22
22
  * Revenue per Price Point
23
+ * Total Savings per Discount Code
24
+ * Number of Uses per Discount Code
23
25
 
24
26
  *Countries*
25
27
  * Proportion of Sales per Country
@@ -39,16 +41,14 @@ Choose the interval over which to get data and see it displayed as:
39
41
  ## Usage
40
42
  * To install manually:
41
43
  * `git clone https://github.com/at1as/Shopify_Dashboard_Plus.git`
42
- * To install via the gem:
44
+ * To install using the gem:
43
45
  * `gem install shopify_dashboard_plus`
44
- * Note: Gem build will generally trail the repo by days-to-weeks
45
46
  * For dependencies see:
46
47
  * `shopify_dashboard_plus.gemfile`
47
48
  * Retrieve `API Key`, `Password` and `Shop Name` for your store from Shopify Admin
48
49
  * Run (key, password & name can be passed as environment variables, or later thorugh the UI):
49
50
  * `SHP_KEY="my_key" SHP_PWD="my_password" SHP_NAME="my_shop" ./lib/shopify_dashboard_plus.rb`
50
51
 
51
- ## TODO
52
+ ## Notes
53
+ * Tested and developed with Ruby 2.1.2 on Mac OS 10.10
52
54
 
53
- * Not all floats render with a two-digit precision
54
- * Limited to 250 results
@@ -5,19 +5,25 @@ require 'tilt/erubis'
5
5
  require 'shopify_api'
6
6
  require 'uri'
7
7
  require 'chartkick'
8
+ require_relative 'shopify_dashboard_plus/helpers'
9
+ require_relative 'shopify_dashboard_plus/report'
10
+ require_relative 'shopify_dashboard_plus/version'
8
11
 
9
12
  configure do
10
- set :public_dir, File.expand_path('../../public', __FILE__)
11
- set :views, File.expand_path('../../views', __FILE__)
13
+ set :public_dir, "#{__dir__}/../public"
14
+ set :views, "#{__dir__}/../views"
12
15
 
13
16
  $connected ||= false
14
- $flash ||= nil
15
17
  $metrics ||= false
18
+ $flash ||= nil
16
19
 
17
- HELP = "Set global environment variables before calling script, or call with ENV variables: \
18
- \t\nExample: SHP_KEY=\"<shop_key>\" SHP_PWD=\"<shop_password>\" SHP_NAME=\"<shop_name>\" ./lib/shopify-dashboard.rb\n"
20
+ HELP = <<-END
21
+ Set global variables through the WebUI or call with the correct ENV variables:
22
+ Example: SHP_KEY=\"<shop_key>\" SHP_PWD=\"<shop_password>\" SHP_NAME=\"<shop_name>\" ./lib/shopify-dashboard.rb
23
+ END
19
24
 
20
- # Connect if Environment variables were set
25
+ # If Environment variables were set connect to Shopify Admin immediately
26
+ # Refuse to start server if variables are passed but incorrect
21
27
  if ENV["SHP_KEY"] && ENV["SHP_PWD"] && ENV["SHP_NAME"]
22
28
  API_KEY = ENV["SHP_KEY"]
23
29
  PASSWORD = ENV["SHP_PWD"]
@@ -30,263 +36,33 @@ configure do
30
36
  $shop_name = SHOP_NAME
31
37
  $connected = true
32
38
  rescue Exception => e
33
- puts "\nFailed to connect using provided credentials...(Exception: #{e}\n #{HELP}"
39
+ puts "\nFailed to connect using provided credentials...(Exception: #{e})\n #{HELP}"
34
40
  exit
35
41
  end
36
42
  end
37
- end
38
-
39
-
40
- helpers do
41
- include Rack::Utils
42
- alias_method :h, :escape_html
43
-
44
- ## Connection & Setup Helpers
45
- # Bind to Shopify Store
46
- def set_connection(key, pwd, name)
47
- begin
48
- shop_url = "https://#{key}:#{pwd}@#{name}.myshopify.com/admin"
49
- ShopifyAPI::Base.site = shop_url
50
- shop = ShopifyAPI::Shop.current
51
- $shop_name = name
52
- connection_open
53
- rescue
54
- connection_closed
55
- end
56
- end
57
-
58
- def connection_closed
59
- $connected = false
60
- end
61
-
62
- def connection_open
63
- $connected = true
64
- end
65
-
66
- def connected?
67
- $connected
68
- end
69
-
70
- def shop_name
71
- $shop_name
72
- end
73
-
74
-
75
- ## Generic Helpers
76
- def hash_to_list(unprocessed_hash)
77
- return_list = []
78
- unprocessed_hash.each do |key, value|
79
- return_list.append([key, value])
80
- end
81
- end
82
-
83
- def date_today
84
- DateTime.now.strftime('%Y-%m-%d')
85
- end
86
-
87
- # Validate User Date is valid and start date <= end date
88
- def validate_date_range(from, to)
89
- interval_start = DateTime.parse(from) rescue nil
90
- interval_end = DateTime.parse(to) rescue nil
91
43
 
92
- if interval_start && interval_end
93
- if interval_start <= interval_end
94
- return true
95
- else
96
- return false
97
- end
98
- end
99
-
100
- false
101
- end
102
-
103
-
104
- ## Metrics Helpers
105
- def get_total_revenue(orders)
106
- revenue = orders.collect{|order| order.total_price.to_f }.inject(:+).round(2) rescue 0
107
- revenue ||= 0
108
- end
109
44
 
110
- def get_daily_revenues(start_date, end_date, orders)
111
- # Create hash entry for total interval over which to inspect sales
112
- revenue_per_day = {}
113
- days = DateTime.parse(end_date).mjd - DateTime.parse(start_date).mjd
114
- (0..days).each{ |day| revenue_per_day[(DateTime.parse(end_date) - day).strftime("%Y-%m-%d")] = 0 }
115
-
116
- # Retreive orders between start and end date (up to 50)
117
- revenue = orders.collect{|order| [order.created_at, order.total_price.to_f]}
118
-
119
- # Filter order details into daily totals and return
120
- revenue.each do |sale|
121
- revenue_per_day[DateTime.parse(sale[0]).strftime('%Y-%m-%d')] += sale[1]
45
+ # When adding 44.95.round(2) + 940.6.round(2) the precision of the result will be 985.5500000000001
46
+ # In a sample of 100_000_000_000 entries, the precision will round up cents
47
+ # Since all numbers are currency, the plus method will trim to two decimals
48
+ # Numbers returned as: 44, 44.5, or 44.51
49
+ class Fixnum
50
+ def plus(amount)
51
+ result = self + (amount.to_f rescue 0)
52
+ result.round(2)
122
53
  end
123
- revenue_per_day
124
54
  end
125
55
 
126
- def hash_to_graph_format(sales)
127
-
128
- # ChartKick requires a strange format to build graphs. For instance, an array of
129
- # {:name => <item_name>, :data => [[<customer_id>, <item_price>], [<customer_id>, <item_price>]]}
130
- # places <customer_id> on the independent (x) axis, and stacks item each item by y-axis by price
131
-
132
- # This hash will return repeated values (i.e., :data => [["item 1", 6], ["item 1", 6]])
133
- # ChartKick will ignore repeated entries. Use `format_hash_for_stacked_graph_repeat` to merge entries before submission
134
-
135
- name_hash = sales.collect{|sale| {:name => sale[:name], :data => []}}.uniq
136
-
137
- sales.collect do |old_hash|
138
- name_hash.collect do |new_hash|
139
- if old_hash[:name] == new_hash[:name]
140
- new_hash[:data].push(old_hash[:data])
141
- end
142
- end
56
+ class Float
57
+ def plus(amount)
58
+ result = self + (amount.to_f rescue 0)
59
+ result.round(2)
143
60
  end
144
-
145
- name_hash
146
61
  end
62
+ end
147
63
 
64
+ helpers ApplicationHelpers
148
65
 
149
- def hash_to_graph_format_merge(sales)
150
-
151
- # ChartKick requires an annoying format to build graphs. For instance, an array of entries formated as
152
- # { :name => <item_name>, :data => [[<customer_id>, <item_price>], [<customer_id>, <item_price>]] }
153
- # places <customer_id> on the independent (x) axis, and stacks item each item by y-axis by price
154
-
155
- name_hash = sales.collect{|sale| {:name => sale[:name], :data => []}}.uniq
156
-
157
- sales.collect do |old_hash|
158
- name_hash.collect do |new_hash|
159
- if old_hash[:name] == new_hash[:name]
160
- new_hash[:data].push(old_hash[:data])
161
- end
162
- end
163
- end
164
-
165
- # name_hash may contain repeated values (i.e., :data => [["item 1", 6], ["item 1", 6]])
166
- # ChartKick will ignore repeated entries, so they need to be consolidated
167
- # i.e., :data => [["item1", 12]]
168
-
169
- name_hash.each_with_index do |item, index|
170
- consolidated_data = Hash.new(0)
171
- item[:data].each do |purchase_entry|
172
- consolidated_data[purchase_entry[0]] += purchase_entry[1]
173
- end
174
- name_hash[index][:data] = hash_to_list(consolidated_data)
175
- end
176
-
177
- name_hash
178
- end
179
-
180
-
181
- def get_detailed_revenue_metrics(start_date, end_date = DateTime.now)
182
- desired_fields = ["total_price", "created_at", "billing_address", "currency", "line_items", "customer", "referring_site"]
183
- revenue_metrics = ShopifyAPI::Order.find(:all, :params => { :created_at_min => start_date,
184
- :created_at_max => end_date,
185
- :page => 1,
186
- :limit => 250,
187
- :fields => desired_fields })
188
-
189
- # Revenue
190
- total_revenue = get_total_revenue(revenue_metrics)
191
- avg_revenue = (total_revenue/(DateTime.parse(end_date).mjd - DateTime.parse(start_date).mjd + 1)).round(2) rescue "N/A"
192
- daily_revenue = get_daily_revenues(start_date, end_date, revenue_metrics)
193
-
194
- # Countries & Currencies
195
- currencies = Hash.new(0)
196
- sales_per_country = Hash.new(0)
197
- revenue_per_country = []
198
- revenue_per_country_uniq = []
199
-
200
- # Products
201
- products = Hash.new(0)
202
- revenue_per_product = Hash.new(0)
203
-
204
- # Prices
205
- prices = Hash.new(0)
206
- revenue_per_price_point = Hash.new(0)
207
-
208
- # Customers
209
- customers = []
210
- customer_sales = []
211
- customer_sales_uniq = []
212
-
213
- # Referrals
214
- referring_pages = Hash.new(0)
215
- referring_sites = Hash.new(0)
216
- revenue_per_referral_page = Hash.new(0)
217
- revenue_per_referral_site = Hash.new(0)
218
-
219
- revenue_metrics.each do |order|
220
-
221
- if order.attributes['currency']
222
- currencies[order.currency] += 1
223
- end
224
- if order.attributes['billing_address']
225
- sales_per_country[order.billing_address.country] += 1
226
- end
227
- if order.attributes['referring_site']
228
- if order.attributes['referring_site'].empty?
229
- referring_pages['None'] += 1
230
- referring_sites['None'] += 1
231
- else
232
- host = URI(order.referring_site).host.downcase
233
- host = host.start_with?('www.') ? host[4..-1] : host
234
- page = order.referring_site
235
- page = page.start_with?('http://') ? page[7..-1] : page
236
- page = page.start_with?('https://') ? page[8..-1] : page
237
- referring_pages[page] += 1
238
- referring_sites[host] += 1
239
- end
240
- order.line_items.each do |line_item|
241
- if order.attributes['referring_site'].empty?
242
- revenue_per_referral_page['None'] += line_item.price.to_f.round(2) rescue 0
243
- revenue_per_referral_site['None'] += line_item.price.to_f.round(2) rescue 0
244
- else
245
- host = URI(order.referring_site).host
246
- host = host.start_with?('www.') ? host[4..-1] : host
247
- page = order.referring_site
248
- page = page.start_with?('http://') ? page[7..-1] : page
249
- page = page.start_with?('https://') ? page[8..-1] : page
250
- revenue_per_referral_site[host] += line_item.price.to_f.round(2) rescue 0
251
- revenue_per_referral_page[page] += line_item.price.to_f.round(2) rescue 0
252
- end
253
- end
254
- end
255
-
256
- order.line_items.each do |line_item|
257
- products[line_item.title] += 1
258
- prices[line_item.price] += 1
259
- revenue_per_price_point[line_item.price] += line_item.price.to_f.round(2) rescue 0
260
- revenue_per_product[line_item.title] += line_item.price.to_f.round(2) rescue 0
261
-
262
- revenue_per_country.push({:name => line_item.title, :data => [order.billing_address.country, line_item.price.to_f]})
263
- customer_sales.push({:name => line_item.title, :data => [order.customer.id.to_s, line_item.price.to_f]})
264
- end
265
-
266
- customer_sales_uniq = hash_to_graph_format(customer_sales)
267
- revenue_per_country_uniq = hash_to_graph_format_merge(revenue_per_country)
268
- end
269
-
270
- metrics = { :currencies => currencies,
271
- :sales_per_country => sales_per_country,
272
- :revenue_per_country => revenue_per_country_uniq,
273
- :products => products,
274
- :prices => (prices.sort_by{|x,y| x.to_f }.to_h rescue {}),
275
- :customer_sales => customer_sales_uniq,
276
- :referring_sites => (referring_sites.sort().to_h rescue {}),
277
- :referring_pages => (referring_pages.sort().to_h rescue {}),
278
- :revenue_per_referral_site => (revenue_per_referral_site.sort().to_h rescue {}),
279
- :revenue_per_referral_page => (revenue_per_referral_page.sort().to_h rescue {}),
280
- :total_revenue => total_revenue,
281
- :average_revenue => avg_revenue,
282
- :daily_revenue => daily_revenue,
283
- :revenue_per_product => revenue_per_product,
284
- :revenue_per_price_point => (revenue_per_price_point.sort_by{|x,y| x.to_f }.to_h rescue {})
285
- }
286
-
287
- return metrics
288
- end
289
- end
290
66
 
291
67
  before do
292
68
  $flash = nil
@@ -300,11 +76,12 @@ get '/' do
300
76
  # If no start date is set, default to match end date
301
77
  # If no date parameters are set, default both to today
302
78
  @today = date_today
303
-
304
79
  from = (params[:from] if not params[:from].empty? rescue nil) || (params[:to] if not params[:to].empty? rescue nil) || @today
305
80
  to = (params[:to] if not params[:to].empty? rescue nil) || @today
306
81
 
307
- if validate_date_range(from, to)
82
+ to = @today if to > @today
83
+
84
+ if date_range_valid?(from, to)
308
85
  @metrics = get_detailed_revenue_metrics(from, to)
309
86
  $metrics = true
310
87
  else
@@ -331,7 +108,7 @@ end
331
108
 
332
109
  post '/disconnect' do
333
110
  API_KEY, API_PWD, SHP_NAME = "", "", ""
334
- connection_closed
111
+ close_connection
335
112
 
336
113
  erb :connect
337
114
  end
@@ -0,0 +1,202 @@
1
+ module ApplicationHelpers
2
+
3
+ include Rack::Utils
4
+ alias_method :h, :escape_html
5
+
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
+ ]
16
+
17
+ ## Connection & Setup Helpers
18
+
19
+ def set_connection(key, pwd, name)
20
+ shop_url = "https://#{key}:#{pwd}@#{name}.myshopify.com/admin"
21
+ ShopifyAPI::Base.site = shop_url
22
+ shop = ShopifyAPI::Shop.current
23
+ $shop_name = name
24
+ open_connection
25
+ rescue
26
+ close_connection
27
+ end
28
+
29
+ def close_connection
30
+ $connected = false
31
+ end
32
+
33
+ def open_connection
34
+ $connected = true
35
+ end
36
+
37
+ def connected?
38
+ $connected
39
+ end
40
+
41
+ def shop_name
42
+ $shop_name
43
+ end
44
+
45
+
46
+ ## Generic Helpers
47
+
48
+ def date_today
49
+ DateTime.now.strftime('%Y-%m-%d')
50
+ end
51
+
52
+ def days(num)
53
+ num.to_i == 1 ? "#{num} Day" : "#{num} Days"
54
+ end
55
+
56
+ def strip_protocol(page)
57
+ page.sub(/\Ahttps?:\/\//, "")
58
+ end
59
+
60
+ def get_host(site)
61
+ URI(site).host.downcase.sub(/\Awww\./, "")
62
+ end
63
+
64
+ def date_range_valid?(from, to)
65
+ DateTime.parse(from) <= DateTime.parse(to)
66
+ rescue ArgumentError
67
+ false
68
+ end
69
+
70
+
71
+ ## Metrics Helpers
72
+
73
+ def display_as_currency(value)
74
+ ShopifyAPI::Shop.current.money_with_currency_format.gsub("{{amount}}", value.to_s)
75
+ rescue
76
+ 'N/A'
77
+ end
78
+
79
+ def get_date_range(first, last)
80
+ DateTime.parse(last).mjd - DateTime.parse(first).mjd
81
+ end
82
+
83
+ def get_total_revenue(orders)
84
+ totals = orders.map { |order| order.total_price.to_f }
85
+ totals.inject(:+).round(2)
86
+ rescue
87
+ 0
88
+ end
89
+
90
+ def get_average_revenue(total_revenue, duration)
91
+ (total_revenue/duration).round(2)
92
+ rescue
93
+ 'N/A'
94
+ end
95
+
96
+ def get_daily_revenues(start_date, end_date, orders)
97
+ # Create hash entry for every day within interval over which to inspect sales
98
+ revenue_per_day = {}
99
+ days = get_date_range(start_date, end_date)
100
+ (0..days).each{ |day| revenue_per_day[(DateTime.parse(end_date) - day).strftime("%Y-%m-%d")] = 0 }
101
+
102
+ # Retreive array of ActiveRecord::Collections, each containing orders between the start and end date
103
+ order_details = orders.map{ |order| [order.created_at, order.total_price.to_f] }
104
+
105
+ # Filter order details into daily totals and return
106
+ order_details.each do |(date, total)|
107
+ day_index = DateTime.parse(date).strftime('%Y-%m-%d')
108
+ revenue_per_day[day_index] = revenue_per_day[day_index].plus(total)
109
+ end
110
+ revenue_per_day
111
+ end
112
+
113
+ def hash_to_graph_format(sales, merge_results: false)
114
+
115
+ # ChartKick requires a strange format to build graphs. For instance, an array of
116
+ # {:name => <item_name>, :data => [[<customer_id>, <item_price>], [<customer_id>, <item_price>]]}
117
+ # places <customer_id> on the independent (x) axis, and stacks each item (item_name) on the y-axis by price (item_price)
118
+
119
+ name_hash = sales.map{ |sale| {:name => sale[:name], :data => []} }.uniq
120
+
121
+ sales.map do |old_hash|
122
+ name_hash.map do |new_hash|
123
+ if old_hash[:name] == new_hash[:name]
124
+ new_hash[:data].push(old_hash[:data])
125
+ end
126
+ end
127
+ end
128
+
129
+ # This hash will return repeated values (i.e., :data => [["item 1", 6], ["item 1", 6]])
130
+ # ChartKick will ignore repeated entries, so the totals need to be merged
131
+ # i.e., :data => [["item1", 12]]
132
+ if merge_results
133
+ name_hash.each_with_index do |item, index|
134
+ consolidated_data = Hash.new(0)
135
+ item[:data].each do |purchase_entry|
136
+ consolidated_data[purchase_entry[0]] = consolidated_data[purchase_entry[0]].plus(purchase_entry[1])
137
+ end
138
+ name_hash[index][:data] = consolidated_data.to_a
139
+ end
140
+ end
141
+
142
+ name_hash
143
+ end
144
+
145
+ # Return order query parameters hash
146
+ def order_parameters_paginate(start_date, end_date, page)
147
+ {
148
+ :created_at_min => start_date + " 0:00",
149
+ :created_at_max => end_date + " 23:59:59",
150
+ :limit => 250,
151
+ :page => page,
152
+ :fields => DESIRED_FIELDS
153
+ }
154
+ end
155
+
156
+
157
+ # Return array of ActiveRecord::Collections, each containing up to :limit (250) orders
158
+ # Continue to query next page until less than :limit orders are returned, indicating no next pages with orders matching query
159
+ def get_list_of_orders(start_date, end_date)
160
+
161
+ # Get first 250 results matching query
162
+ params = order_parameters_paginate(start_date, end_date, 1)
163
+ revenue_metrics = [ShopifyAPI::Order.find(:all, :params => params)]
164
+
165
+ # If the amount of results equal to the limit (250) were returned, pass the query on to the next page (orders 251 to 500)
166
+ while revenue_metrics.last.length == 250
167
+ params = order_parameters_paginate(start_date, end_date, revenue_metrics.length + 1)
168
+ revenue_metrics << ShopifyAPI::Order.find(:all, :params => params)
169
+ end
170
+
171
+ revenue_metrics.flat_map{ |orders| orders.map{ |order| order }}
172
+ end
173
+
174
+
175
+ def get_detailed_revenue_metrics(start_date, end_date = DateTime.now)
176
+
177
+ order_list = get_list_of_orders(start_date, end_date)
178
+
179
+ # Revenue
180
+ total_revenue = get_total_revenue(order_list)
181
+ duration = get_date_range(start_date, end_date) + 1
182
+ avg_revenue = get_average_revenue(total_revenue, duration)
183
+ daily_revenue = get_daily_revenues(start_date, end_date, order_list)
184
+ max_daily_revenue = daily_revenue.max_by{ |k,v| v }[1]
185
+
186
+ # Retrieve Metrics
187
+ sales_report = ShopifyDashboardPlus::SalesReport.new(order_list).to_h
188
+ revenue_report = ShopifyDashboardPlus::RevenueReport.new(order_list).to_h
189
+ traffic_report = ShopifyDashboardPlus::TrafficReport.new(order_list).to_h
190
+ discounts_report = ShopifyDashboardPlus::DiscountReport.new(order_list).to_h
191
+ metrics = {
192
+ :total_revenue => total_revenue,
193
+ :average_revenue => avg_revenue,
194
+ :daily_revenue => daily_revenue,
195
+ :max_daily_revenue => max_daily_revenue,
196
+ :duration => duration
197
+ }
198
+
199
+ [sales_report, revenue_report, traffic_report, discounts_report, metrics].inject(&:merge)
200
+ end
201
+
202
+ end
@@ -0,0 +1,194 @@
1
+ require_relative 'helpers'
2
+
3
+ module ShopifyDashboardPlus
4
+
5
+ class Report
6
+ include ApplicationHelpers
7
+
8
+ def initialize(orders)
9
+ @orders = orders
10
+ @line_items = orders.flat_map{ |order| order.line_items.map { |line_item| line_item }}
11
+ end
12
+ 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
+ :sales_per_country => sales_per_country,
73
+ :sales_per_price => sales_per_price_point,
74
+ :sales_per_product => sales_per_product,
75
+ :sales_per_customer => sales_per_customer,
76
+ :number_of_sales => number_of_sales
77
+ }
78
+ end
79
+ end
80
+
81
+
82
+ class RevenueReport < Report
83
+ def revenue_per_country
84
+ revenue_per_country = []
85
+ @orders.each do |order|
86
+ order.line_items.each do |item|
87
+ revenue_per_country.push({:name => item.title,
88
+ :data => [order.billing_address.country, item.price.to_f]})
89
+ end
90
+ end
91
+ hash_to_graph_format(revenue_per_country, merge_results: true)
92
+ end
93
+
94
+ def revenue_per_price_point
95
+ revenue_per_price_point = Hash.new(0)
96
+ @line_items.each do |item|
97
+ revenue_per_price_point[item.price] = revenue_per_price_point[item.price].plus(item.price)
98
+ end
99
+ revenue_per_price_point.sort_by{ |x,y| x.to_f }.to_h rescue {}
100
+ end
101
+
102
+ def revenue_per_product
103
+ revenue_per_product = Hash.new(0.0)
104
+ @line_items.each do |item|
105
+ revenue_per_product[item.title] = revenue_per_product[item.title].plus(item.price)
106
+ end
107
+ revenue_per_product
108
+ end
109
+
110
+ def to_h
111
+ {
112
+ :revenue_per_country => revenue_per_country,
113
+ :revenue_per_product => revenue_per_product,
114
+ :revenue_per_price_point => revenue_per_price_point,
115
+ }
116
+ end
117
+ end
118
+
119
+
120
+ class DiscountReport < Report
121
+
122
+ def discount_usage
123
+ discount_value, discount_used = Hash.new(0.0), Hash.new(0)
124
+
125
+ @orders.each do |order|
126
+ if order.attributes['discount_codes']
127
+ order.discount_codes.each do |discount_code|
128
+ discount_value[discount_code.code] = discount_value[discount_code.code].plus(discount_code.amount)
129
+ discount_used[discount_code.code] += 1
130
+ end
131
+ end
132
+ end
133
+ {
134
+ :discount_savings => discount_value,
135
+ :discount_quantity => discount_used
136
+ }
137
+ end
138
+
139
+ def to_h
140
+ discount_usage
141
+ end
142
+ end
143
+
144
+
145
+ class TrafficReport < Report
146
+
147
+ def number_of_referrals
148
+ referring_sites, referring_pages = Hash.new(0), Hash.new(0)
149
+
150
+ @orders.each do |order|
151
+ if order.attributes['referring_site'].empty?
152
+ referring_pages['None'] += 1
153
+ referring_sites['None'] += 1
154
+ else
155
+ host = get_host(order.referring_site)
156
+ page = strip_protocol(order.referring_site)
157
+ referring_pages[page] += 1
158
+ referring_sites[host] += 1
159
+ end
160
+ end
161
+ {
162
+ :referral_sites => (referring_sites.sort().to_h rescue {}),
163
+ :referral_pages => (referring_pages.sort().to_h rescue {})
164
+ }
165
+ end
166
+
167
+ def traffic_revenue
168
+ revenue_per_referral_page, revenue_per_referral_site = Hash.new(0.0), Hash.new(0.0)
169
+
170
+ @orders.each do |order|
171
+ order.line_items.each do |item|
172
+ if order.attributes['referring_site'].empty?
173
+ revenue_per_referral_page['None'] = revenue_per_referral_page['None'].plus(item.price)
174
+ revenue_per_referral_site['None'] = revenue_per_referral_site['None'].plus(item.price)
175
+ else
176
+ host = get_host(order.referring_site)
177
+ page = strip_protocol(order.referring_site)
178
+ revenue_per_referral_site[host] = revenue_per_referral_site[host].plus(item.price)
179
+ revenue_per_referral_page[page] = revenue_per_referral_page[page].plus(item.price)
180
+ end
181
+ end
182
+ end
183
+ {
184
+ :revenue_per_referral_site => revenue_per_referral_site.sort().to_h,
185
+ :revenue_per_referral_page => revenue_per_referral_page.sort().to_h
186
+ }
187
+ end
188
+
189
+ def to_h
190
+ traffic_revenue.merge(number_of_referrals)
191
+ end
192
+ end
193
+
194
+ end
@@ -1,3 +1,3 @@
1
1
  module ShopifyDashboardPlus
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -1,3 +1,6 @@
1
+ body{
2
+ background-color:#444;
3
+ }
1
4
  .graph-container{
2
5
  width:100%;
3
6
  text-align:center;
@@ -5,7 +8,7 @@
5
8
  .graph-border{
6
9
  border:1px solid #e3e3e3;
7
10
  border-radius:4px;
8
- background-color:#f5f5f5;
11
+ background-color:#fff;
9
12
  padding-right:10px;
10
13
  padding-left:10px;
11
14
  margin-left:auto;
@@ -14,6 +17,26 @@
14
17
  margin-bottom:10px;
15
18
  max-width:1200px;
16
19
  }
20
+ .graph-seperator{
21
+ margin-top:0px;
22
+ margin-bottom:0px;
23
+ margin-left:40px;
24
+ margin-right:40px;
25
+ }
26
+ .graph-metrics{
27
+ margin-left:20px;
28
+ margin-right:20px;
29
+ text-align:center;
30
+ display:inline-block;
31
+ }
32
+ .graph-metric{
33
+ margin-left:20px;
34
+ margin-right:20px;
35
+ display:inline-block;
36
+ }
37
+ .grey{
38
+ color:#666;
39
+ }
17
40
  h3.section-divider{
18
41
  background-color: #666;
19
42
  color: white;
@@ -21,13 +44,32 @@ h3.section-divider{
21
44
  margin-bottom:15px;
22
45
  margin-left: -10px;
23
46
  margin-right: -10px;
24
- padding-bottom: 5px;
25
- padding-top: 5px;
47
+ padding-bottom: 10px;
48
+ padding-top: 10px;
26
49
  text-align: center;
27
50
  }
51
+ h3.money{
52
+ margin-top:0px;
53
+ color:#337ab7;
54
+ }
55
+ h3{
56
+ font-weight:300;
57
+ }
58
+ h4{
59
+ color:#555;
60
+ }
61
+ h4.graph-heading{
62
+ font-weight:400;
63
+ font-size:22px;
64
+ }
28
65
  .flash{
29
66
  color:red;
30
67
  }
68
+ .date-input{
69
+ width:100px;
70
+ margin-left:auto;
71
+ margin-right:auto;
72
+ }
31
73
 
32
74
  /*
33
75
  // NOTE: media queries not working will chartkick,
@@ -4,11 +4,12 @@ lib = File.expand_path('../lib', __FILE__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  require 'date'
7
+ require 'shopify_dashboard_plus/version'
7
8
 
8
9
  Gem::Specification.new do |spec|
9
10
  spec.name = "shopify_dashboard_plus"
10
11
  spec.date = Date.today.to_s
11
- spec.version = "0.0.3"
12
+ spec.version = ShopifyDashboardPlus::VERSION
12
13
  spec.authors = ["Jason Willems"]
13
14
  spec.email = ["jason@willems.ca"]
14
15
  spec.summary = "Extended dashboard for shopify admin"
data/views/layout.erb CHANGED
@@ -5,8 +5,9 @@
5
5
  <script>
6
6
  var Chartkick = {"language": "en"};
7
7
  </script>
8
- <script src="http://www.google.com/jsapi"></script>
9
- <script src="js/chartkick.min.js"></script>
8
+ <script src="http://www.google.com/jsapi"></script> <!-- Chart library dependency for chartkick -->
9
+ <script src="js/chartkick.min.js"></script> <!-- Front end chart library -->
10
+ <script src="/js/spin.min.js"></script> <!-- Spinner Animation -->
10
11
  </head>
11
12
  <body>
12
13
  <%= yield %>
data/views/report.erb CHANGED
@@ -1,16 +1,15 @@
1
1
  <div id="table_bounds" style="margin-left:10px; margin-right:10px; margin-bottom:40px">
2
2
  <div class="well" style="text-align:center; max-width:600px; margin-left:auto; margin-right:auto; margin-top:30px">
3
3
  <h2 style="margin-top:10px; margin-bottom:20px">Shopify Dashboard Plus</h2>
4
- <hr>
5
4
  <% if $flash %><p class="flash"><%= $flash %></p><% end %>
6
5
  <div style="margin-bottom:20px;">
7
6
  Retrieve metrics over the following period <% if shop_name %>for: <br/><i><b><%= shop_name %>.myshopify.com/admin</b></i><% end %>
8
7
  </div>
9
8
  <form class="form-inline" id="set-date" method="get" action="/">
10
9
  <h4 style="margin-bottom:10px">
11
- <input id="from" name="from" class="form-control form-field-small" value="<%= h(params[:from]) %>" pattern="^[1-2][0-9]{3}-[0-3][0-9]-[0-3][0-9]$" placeholder="<%= @today %>">
10
+ <input id="from" name="from" class="form-control form-field-small date-input" value="<%= h(params[:from]) %>" pattern="^[1-2][0-9]{3}-[0-3][0-9]-[0-3][0-9]$" placeholder="<%= @today %>">
12
11
  to
13
- <input id="to" name="to" class="form-control form-field-small" value="<%= h(params[:to]) %>" pattern="^[1-2][0-9]{3}-[0-3][0-9]-[0-3][0-9]$"% placeholder="<%= @today %>">
12
+ <input id="to" name="to" class="form-control form-field-small date-input" value="<%= h(params[:to]) %>" pattern="^[1-2][0-9]{3}-[0-3][0-9]-[0-3][0-9]$"% placeholder="<%= @today %>">
14
13
  </h4>
15
14
  <input type="submit" class="btn btn-primary" value="Get Data" style="width:200px">
16
15
  <form>
@@ -23,52 +22,120 @@
23
22
  <h3 class="section-divider">Sales</h3>
24
23
  <div class="graph-container">
25
24
  <div class="graph-border">
26
- <div id="daily_revenue" onclick="fullscreen_graph(this)" style="text-align:center"><h4>Daily Sales</h4></div>
25
+ <h4 class="graph-heading">
26
+ Daily Sales
27
+ </h4>
28
+ <hr class="graph-seperator">
27
29
  <%= column_chart @metrics[:daily_revenue], library: {hAxis: {direction: -1}} %>
28
- <div style="text-align:center"><h5>Total Sales: <%= @metrics[:total_revenue] %></h5></div>
29
- <div style="text-align:center"><h5>Average Per Day: <%= @metrics[:average_revenue] %></h5></div>
30
+ <div class="graph-metrics">
31
+ <div class="graph-metric">
32
+ <h5 class="grey">Duration</h5>
33
+ <h3 class="money"><%= days(@metrics[:duration]) %></h3>
34
+ </div>
35
+ <div class="graph-metric">
36
+ <h5 class="grey">Number of Sales</h5>
37
+ <h3 class="money"><%= @metrics[:number_of_sales] %></h3>
38
+ </div>
39
+ <div class="graph-metric">
40
+ <h5 class="grey">Total Revenue</h5>
41
+ <h3 class="money"><%= display_as_currency @metrics[:total_revenue] %></h3>
42
+ </div>
43
+ <div class="graph-metric">
44
+ <h5 class="grey">Average Daily Revenue</h5>
45
+ <h3 class="money"><%= display_as_currency @metrics[:average_revenue] %></h3>
46
+ </div>
47
+ <div class="graph-metric">
48
+ <h5 class="grey">Max Daily Revenue</h5>
49
+ <h3 class="money"><%= display_as_currency @metrics[:max_daily_revenue] %></h3>
50
+ </div>
51
+ </div>
30
52
  </div>
53
+
31
54
  <div class="graph-border">
32
- <div style="text-align:center"><h4>Proportion of Sales per Product</h4></div>
33
- <%= pie_chart @metrics[:products] %>
55
+ <h4 class="graph-heading">
56
+ Proportion of Sales per Product
57
+ </h4>
58
+ <hr class="graph-seperator">
59
+ <%= pie_chart @metrics[:sales_per_product] %>
34
60
  </div>
35
61
 
36
62
  <div class="graph-border">
37
- <div style="text-align:center"><h4>Number of Sales per Product</h4></div>
38
- <%= column_chart @metrics[:products] %>
63
+ <h4 class="graph-heading">
64
+ Number of Sales per Product
65
+ </h4>
66
+ <hr class="graph-seperator">
67
+ <%= column_chart @metrics[:sales_per_product] %>
39
68
  </div>
69
+
40
70
  <div class="graph-border">
41
- <div style="text-align:center"><h4>Revenue per Product</h4></div>
71
+ <h4 class="graph-heading">
72
+ Revenue per Product
73
+ </h4>
74
+ <hr class="graph-seperator">
42
75
  <%= column_chart @metrics[:revenue_per_product] %>
43
76
  </div>
44
77
  </div>
45
78
 
46
79
 
47
- <h3 class="section-divider">Prices</h3>
80
+ <h3 class="section-divider">Prices &amp; Discounts</h3>
48
81
  <div class="graph-container">
49
82
  <div class="graph-border">
50
- <div style="text-align:center"><h4>Proportion of Items Sold Per Price Point</h4></div>
51
- <%= pie_chart @metrics[:prices] %>
83
+ <h4 class="graph-heading">
84
+ Proportion of Items Sold Per Price Point
85
+ </h4>
86
+ <hr class="graph-seperator">
87
+ <%= pie_chart @metrics[:sales_per_price] %>
52
88
  </div>
89
+
53
90
  <div class="graph-border">
54
- <div style="text-align:center"><h4>Number of Items Sold Per Price Point</h4></div>
55
- <%= column_chart @metrics[:prices] %>
91
+ <h4 class="graph-heading">
92
+ Number of Items Sold Per Price Point
93
+ </h4>
94
+ <hr class="graph-seperator">
95
+ <%= column_chart @metrics[:sales_per_price] %>
56
96
  </div>
97
+
57
98
  <div class="graph-border">
58
- <div style="text-align:center"><h4>Revenue per Price Point</h4></div>
99
+ <h4 class="graph-heading">
100
+ Revenue per Price Point
101
+ </h4>
102
+ <hr class="graph-seperator">
59
103
  <%= column_chart @metrics[:revenue_per_price_point] %>
60
104
  </div>
105
+
106
+ <div class="graph-border">
107
+ <h4 class="graph-heading">
108
+ Total Savings per Discount Code
109
+ </h4>
110
+ <hr class="graph-seperator">
111
+ <%= column_chart @metrics[:discount_savings] %>
112
+ </div>
113
+
114
+ <div class="graph-border">
115
+ <h4 class="graph-heading">
116
+ Number of Uses per Discount Code
117
+ </h4>
118
+ <hr class="graph-seperator">
119
+ <%= column_chart @metrics[:discount_quantity] %>
120
+ </div>
61
121
  </div>
62
122
 
63
123
 
64
124
  <h3 class="section-divider">Countries</h3>
65
125
  <div class="graph-container">
66
126
  <div class="graph-border">
67
- <div style="text-align:center"><h4>Proportion of Sales per Country</h4></div>
127
+ <h4 class="graph-heading">
128
+ Proportion of Sales per Country
129
+ </h4>
130
+ <hr class="graph-seperator">
68
131
  <%= pie_chart @metrics[:sales_per_country] %>
69
132
  </div>
133
+
70
134
  <div class="graph-border">
71
- <div style="text-align:center"><h4>Revenue per Country</h4></div>
135
+ <h4 class="graph-heading">
136
+ Revenue per Country
137
+ </h4>
138
+ <hr class="graph-seperator">
72
139
  <%= column_chart @metrics[:revenue_per_country], stacked: true %>
73
140
  </div>
74
141
  </div>
@@ -77,8 +144,11 @@
77
144
  <h3 class="section-divider">Currencies</h3>
78
145
  <div class="graph-container">
79
146
  <div class="graph-border">
80
- <div style="text-align:center"><h4>Currencies Used per Purchase</h4></div>
81
- <%= pie_chart @metrics[:currencies] %>
147
+ <h4 class="graph-heading">
148
+ Currencies Used per Purchase
149
+ </h4>
150
+ <hr class="graph-seperator">
151
+ <%= pie_chart @metrics[:currencies_per_sale] %>
82
152
  </div>
83
153
  </div>
84
154
 
@@ -86,8 +156,11 @@
86
156
  <h3 class="section-divider">Customers</h3>
87
157
  <div class="graph-container">
88
158
  <div class="graph-border">
89
- <div style="text-align:center"><h4>Purchases per Customer</h4></div>
90
- <%= column_chart @metrics[:customer_sales], stacked: true %>
159
+ <h4 class="graph-heading">
160
+ Purchases per Customer
161
+ </h4>
162
+ <hr class="graph-seperator">
163
+ <%= column_chart @metrics[:sales_per_customer], stacked: true %>
91
164
  </div>
92
165
  </div>
93
166
 
@@ -95,23 +168,48 @@
95
168
  <h3 class="section-divider">Traffic Metrics</h3>
96
169
  <div class="graph-container">
97
170
  <div class="graph-border">
98
- <div style="text-align:center"><h4>Referrals per Site</h4></div>
99
- <%= column_chart @metrics[:referring_sites] %>
171
+ <h4 class="graph-heading">
172
+ Referrals per Site
173
+ </h4>
174
+ <hr class="graph-seperator">
175
+ <%= column_chart @metrics[:referral_sites] %>
100
176
  </div>
177
+
101
178
  <div class="graph-border">
102
- <div style="text-align:center"><h4>Referrals per Site Pages</h4></div>
103
- <%= column_chart @metrics[:referring_pages] %>
179
+ <h4 class="graph-heading">
180
+ Referrals per Site Pages
181
+ </h4>
182
+ <hr class="graph-seperator">
183
+ <%= column_chart @metrics[:referral_pages] %>
104
184
  </div>
185
+
105
186
  <div class="graph-border">
106
- <div style="text-align:center"><h4>Revenue Per Referral Site</h4></div>
187
+ <h4 class="graph-heading">
188
+ Revenue Per Referral Site
189
+ </h4>
190
+ <hr class="graph-seperator">
107
191
  <%= column_chart @metrics[:revenue_per_referral_site] %>
108
192
  </div>
193
+
109
194
  <div class="graph-border">
110
- <div style="text-align:center"><h4>Revenue Per Referral Site Page</h4></div>
195
+ <h4 class="graph-heading">
196
+ Revenue Per Referral Site Page
197
+ </h4>
198
+ <hr class="graph-seperator">
111
199
  <%= column_chart @metrics[:revenue_per_referral_page] %>
112
200
  </div>
113
201
  </div>
114
202
 
203
+
204
+ <div style="margin-top:30px; margin-left:auto; margin-right:auto; text-align:center; color:#999">
205
+ Generated <%= Time.now %>
206
+ </div>
207
+
208
+ <script type="text/javascript">
209
+ window.onload = function(){
210
+ //document.getElementById("loading").style.display = "none"
211
+ }
212
+ </script>
115
213
  <!--
116
214
  <div id="fullscreen-graph" style="z-index:10; margin-left:auto; margin-right:auto;"></div>
117
215
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify_dashboard_plus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Willems
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-24 00:00:00.000000000 Z
11
+ date: 2015-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -147,7 +147,9 @@ files:
147
147
  - README.md
148
148
  - bin/shopify_dashboard_plus.rb
149
149
  - lib/shopify_dashboard_plus.rb
150
- - lib/version/shopify_dashboard_plus.rb
150
+ - lib/shopify_dashboard_plus/helpers.rb
151
+ - lib/shopify_dashboard_plus/report.rb
152
+ - lib/shopify_dashboard_plus/version.rb
151
153
  - public/css/bootstrap.min.css
152
154
  - public/css/common.css
153
155
  - public/js/chartkick.min.js