shopify_dashboard_plus 0.0.3 → 0.0.4
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.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/lib/shopify_dashboard_plus.rb +31 -254
- data/lib/shopify_dashboard_plus/helpers.rb +202 -0
- data/lib/shopify_dashboard_plus/report.rb +194 -0
- data/lib/{version/shopify_dashboard_plus.rb → shopify_dashboard_plus/version.rb} +1 -1
- data/public/css/common.css +45 -3
- data/shopify_dashboard_plus.gemspec +2 -1
- data/views/layout.erb +3 -2
- data/views/report.erb +127 -29
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9cb5e599bcb7d985aecabdaa34959e0bd0ac515
|
4
|
+
data.tar.gz: a6ddcb38acf2a363c585ab864b93b62003bd3c5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
##
|
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,
|
11
|
-
set :views,
|
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 =
|
18
|
-
|
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
|
-
#
|
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
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
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
|
-
|
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
|
data/public/css/common.css
CHANGED
@@ -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:#
|
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:
|
25
|
-
padding-top:
|
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 =
|
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
|
-
<
|
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
|
29
|
-
|
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
|
-
<
|
33
|
-
|
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
|
-
<
|
38
|
-
|
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
|
-
<
|
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 & Discounts</h3>
|
48
81
|
<div class="graph-container">
|
49
82
|
<div class="graph-border">
|
50
|
-
<
|
51
|
-
|
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
|
-
<
|
55
|
-
|
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
|
-
<
|
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
|
-
<
|
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
|
-
<
|
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
|
-
<
|
81
|
-
|
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
|
-
<
|
90
|
-
|
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
|
-
<
|
99
|
-
|
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
|
-
<
|
103
|
-
|
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
|
-
<
|
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
|
-
<
|
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.
|
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-
|
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/
|
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
|