firebase-stats 1.0.3 → 1.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aaeab070afaf270810993c11137363d74ff5700131a95e52e798cb8e62337525
4
- data.tar.gz: d6fd22e36775bf9ffa6fcab773c027839fa3f67dc6eb0f5aec18ac526bf8a6d0
3
+ metadata.gz: 6f242227d13ffe84bca59bc31022c4d8628e8df5a7685d5ae1d6dd97c1aa937c
4
+ data.tar.gz: 06e6280fdcab4baac6818eb42f1adb9c7a4153e81620557a0da8115b4f21c7bf
5
5
  SHA512:
6
- metadata.gz: ffd869cca7971501c8d066c0ca2a5b9e1ec266c2e33d078345b313678e6df4a64af29b1a337ba64017af63d5379c6991d6fc1e955fc82a5462b80b184816946e
7
- data.tar.gz: be9b8f09955a53e35335e0c7c80c8e0f340ebe0649e8da6249e128ddd9edabf3f284c2ab5b8027b5b8559b799c29d04be10ebef519dcfc70bc1cd663572e3ddd
6
+ metadata.gz: c3e663d068be4f12c0c3ea98fd7aeda11184550a0ca3732effe19a1999733fab5fa960c14a6dfbd5f55ba6549908d8665f34d93917d1768b101dff50f69b383b
7
+ data.tar.gz: ef547e9e6b067dba32befac728beb80eeccd1f9217b4641cc358f1f72765eff5bfba90ad79dde97fdc7a09b5d97ff42ae6de3be00164ea8de759ec85d2e6f673
data/bin/firebase-stats CHANGED
@@ -29,13 +29,17 @@ command :devices do |c|
29
29
  c.option '--platform STRING', String, 'Show only stats for this OS. Either ios, android or all (default)'
30
30
 
31
31
  c.action do |args, options|
32
- stats = FirebaseStats::Reader.new
33
- stats.parse_file(args[0])
32
+ begin
33
+ stats = FirebaseStats::Reader.new
34
+ stats.parse_file(args[0])
34
35
 
35
- platform = map_platform(options)
36
+ platform = map_platform(options)
36
37
 
37
- wrapper = FirebaseStats::Wrapper.new(stats, platform)
38
- tp wrapper.devices(friendly: options.friendly, limit: options.limit)
38
+ wrapper = FirebaseStats::Wrapper.new(stats)
39
+ tp wrapper.devices(friendly: options.friendly, limit: options.limit, platform: platform)
40
+ rescue FirebaseStats::SectionNotFoundError => error
41
+ print_data_error(error)
42
+ end
39
43
  end
40
44
  end
41
45
 
@@ -48,21 +52,23 @@ command :os do |c|
48
52
  c.option '--platform STRING', String, 'Show only stats for this OS. Either ios, android or all (default)'
49
53
 
50
54
  c.action do |args, options|
51
- stats = FirebaseStats::Reader.new
52
- stats.parse_file(args[0])
55
+ begin
56
+ stats = FirebaseStats::Reader.new
57
+ stats.parse_file(args[0])
58
+
59
+ platform = map_platform(options)
53
60
 
54
- platform = map_platform(options)
61
+ wrapper = FirebaseStats::Wrapper.new(stats)
55
62
 
56
- wrapper = FirebaseStats::Wrapper.new(stats, platform)
63
+ grouped = options.grouped || false
64
+ major_order = options.version_sorted || false
57
65
 
58
- data = options.grouped.nil? ? wrapper.os_version : wrapper.os_grouped
66
+ data = wrapper.os(platform: platform, grouped: grouped, major_order: major_order)
59
67
 
60
- unless options.version_sorted.nil?
61
- data = data.sort_by do |row|
62
- row['version']
63
- end.reverse
68
+ tp data
69
+ rescue FirebaseStats::SectionNotFoundError => error
70
+ print_data_error(error)
64
71
  end
65
- tp data
66
72
  end
67
73
  end
68
74
 
@@ -72,13 +78,15 @@ command :gender do |c|
72
78
  c.description = 'Prints out a table with number of users of each gender'
73
79
 
74
80
  c.action do |args, options|
75
- stats = FirebaseStats::Reader.new
76
- stats.parse_file(args[0])
77
-
78
- platform = map_platform(options)
79
-
80
- wrapper = FirebaseStats::Wrapper.new(stats, platform)
81
- tp wrapper.gender
81
+ begin
82
+ stats = FirebaseStats::Reader.new
83
+ stats.parse_file(args[0])
84
+
85
+ wrapper = FirebaseStats::Wrapper.new(stats)
86
+ tp wrapper.gender
87
+ rescue FirebaseStats::SectionNotFoundError => error
88
+ print_data_error(error)
89
+ end
82
90
  end
83
91
  end
84
92
 
@@ -88,12 +96,26 @@ command :gender_age do |c|
88
96
  c.description = 'Prints out a table with percentage of users of each gender grouped by age'
89
97
 
90
98
  c.action do |args, options|
91
- stats = FirebaseStats::Reader.new
92
- stats.parse_file(args[0])
99
+ begin
100
+ stats = FirebaseStats::Reader.new
101
+ stats.parse_file(args[0])
102
+
103
+ wrapper = FirebaseStats::Wrapper.new(stats)
104
+ tp wrapper.gender_age
105
+ rescue FirebaseStats::SectionNotFoundError => error
106
+ print_data_error(error)
107
+ end
108
+ end
109
+ end
93
110
 
94
- platform = map_platform(options)
111
+ private
95
112
 
96
- wrapper = FirebaseStats::Wrapper.new(stats, platform)
97
- tp wrapper.gender_age
98
- end
113
+
114
+ # @param [SectionNotFoundError] error
115
+ def print_data_error(error)
116
+ tip = FirebaseStats::Wrapper.tip(error.section)
117
+ expected_header = FirebaseStats::Reader.search_string(error.section)
118
+ puts "Unable to find that specific data in the input file"
119
+ puts "For the data you requested, the following CSV header should be contained within the file: '#{expected_header}'"
120
+ puts tip unless tip.nil?
99
121
  end
@@ -0,0 +1,24 @@
1
+ module FirebaseStats
2
+ # Parses the Firebase CSV file into sections
3
+ class DeviceUtils
4
+ # Is this device name an iOS device?
5
+ # @param [CSV::Row] device_name
6
+ def self.ios_device?(device_name)
7
+ device_name.downcase.include?('iphone') or device_name.downcase.include?('ipad') or device_name.downcase.include?('ipod')
8
+ end
9
+
10
+ # Filters a device list to only the requested platform
11
+ # @param [CSV::Table] device_data
12
+ # @param [Symbol] platform One of :all, :ios, :android
13
+ def self.filter_device(device_data, platform)
14
+ case platform
15
+ when :android
16
+ device_data.reject { |row| ios_device? row['Device model'] }
17
+ when :ios
18
+ device_data.select { |row| ios_device? row['Device model'] }
19
+ else
20
+ device_data
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,3 +3,5 @@
3
3
  require 'reader'
4
4
  require 'wrapper'
5
5
  require 'version'
6
+ require 'device_utils'
7
+ require 'section_not_found_error'
data/lib/reader.rb CHANGED
@@ -1,17 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class FirebaseStats
3
+ module FirebaseStats
4
4
  require 'csv'
5
5
 
6
6
  # Parses the Firebase CSV file into sections
7
7
  class Reader
8
- attr_reader :data
9
-
10
8
  def initialize
11
9
  super
12
10
  @data = {}
13
11
  end
14
12
 
13
+ def num_sections
14
+ @data.length
15
+ end
16
+
17
+ def get(section)
18
+ found = @data[section]
19
+ raise SectionNotFoundError.new(section) if found.nil?
20
+ found
21
+ end
22
+
15
23
  # @param [String] filename
16
24
  def parse_file(filename)
17
25
  lines = File.readlines(filename)
@@ -20,6 +28,7 @@ class FirebaseStats
20
28
 
21
29
  # @param [Array<String>] input
22
30
  def parse(input)
31
+ @data = {}
23
32
  curr_lines = []
24
33
  input.each_with_index do |line, idx|
25
34
  curr_lines.push(line) unless comment?(line) || line.strip.empty?
@@ -31,22 +40,15 @@ class FirebaseStats
31
40
  end
32
41
  end
33
42
 
34
- private
35
-
36
- # @param [Array<String>] lines
37
- def process_lines(lines)
38
- section = match_header lines[0]
39
- return if section.nil?
40
-
41
- parsed = CSV.parse(lines.join, headers: true)
42
- @data[section] = parsed if @data[section].nil?
43
+ # Get the string that is used to find a given section in the CSV
44
+ def self.search_string(section)
45
+ header = Reader.mappings.key(section)
46
+ header = "Category,Male,Other,Female" if header.nil? and section == :gender_age
47
+ return header
43
48
  end
44
49
 
45
- # @param [String] header
46
- # @return [Symbol, nil]
47
- # rubocop:disable Metrics/MethodLength
48
- def match_header(header)
49
- mappings = {
50
+ def self.mappings
51
+ {
50
52
  'Day,28-Day,7-Day,1-Day' => :active_users,
51
53
  'Day,Average engagement time' => :daily_engagement,
52
54
  'Page path and screen class,User engagement,Screen views' => :screens,
@@ -59,16 +61,42 @@ class FirebaseStats
59
61
  'Device model,Users' => :devices,
60
62
  'OS with version,Users' => :os_version,
61
63
  'Gender,Users' => :gender,
62
- 'Category,Female,Male' => :gender_age,
63
64
  'Platform,Users' => :platform,
64
65
  'Platform,Users,% Total,User engagement,Total revenue' => :platform_engagement
65
66
  }
67
+ end
68
+
69
+ private
70
+
71
+ # @param [Array<String>] lines
72
+ def process_lines(lines)
73
+ section = match_header lines[0]
74
+ return if section.nil?
75
+
76
+ parsed = CSV.parse(lines.join, headers: true)
77
+ @data[section] = parsed if @data[section].nil?
78
+ end
79
+
80
+ # Maps a given CSV header to a section
81
+ # @param [String] header The CSV header line to parse
82
+ # @return [Symbol, nil] The section, or nil if not found
83
+ # rubocop:disable Metrics/MethodLength
84
+ def match_header(header)
85
+ # All of the section headers that can be found in the CSV file, mapping to our internal section symbols
86
+
87
+ cleaned_header = header.strip
88
+ section = Reader.mappings[cleaned_header]
66
89
 
67
- mappings[header.strip]
90
+ # Kludge for gender_age parsing as the headers aren't always in the right order, so rule out
91
+ # all the other sections first
92
+ section = :gender_age if section.nil? and cleaned_header.include? 'Category,'
93
+ section
68
94
  end
69
95
  # rubocop:enable Metrics/MethodLength
70
96
 
97
+ # Is this line a comment
71
98
  # @param [String] line
99
+ # @return [Boolean]
72
100
  def comment?(line)
73
101
  line.include?('#')
74
102
  end
@@ -0,0 +1,9 @@
1
+ module FirebaseStats
2
+ class SectionNotFoundError < StandardError
3
+ attr_reader :section
4
+
5
+ def initialize(section)
6
+ @section = section
7
+ end
8
+ end
9
+ end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class FirebaseStats
4
- VERSION = '1.0.3'
3
+ module FirebaseStats
4
+ VERSION = '1.0.7'
5
5
  end
data/lib/wrapper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class FirebaseStats
3
+ module FirebaseStats
4
4
  require 'csv'
5
5
  require 'android/devices'
6
6
  require 'open-uri'
@@ -8,65 +8,41 @@ class FirebaseStats
8
8
  # Transforms the parsed Firebase file into something more user friendly
9
9
  class Wrapper
10
10
  # @param [FirebaseStats::Reader] stats
11
- # @param [Symbol] platform One of :all, :ios, :android
12
- def initialize(stats, platform = :all)
11
+ def initialize(stats)
13
12
  super()
14
13
  @stats = stats
15
- @platform = platform
16
- end
17
-
18
- def os_version
19
- filtered = filter_os(@stats.data[:os_version], @platform)
20
-
21
- cleaned = []
22
- filtered.each do |row|
23
- cleaned << {
24
- 'version' => row['OS with version'],
25
- 'count' => row['Users'].to_i,
26
- 'percentage' => as_percentage(os_total, row['Users'].to_f)
27
- }
28
- end
29
- cleaned
30
- end
31
-
32
- def os_grouped
33
- raw_os = @stats.data[:os_version]
34
-
35
- grouped = case @platform
36
- when :ios
37
- ios_os_group(raw_os)
38
- when :android
39
- android_os_group(raw_os)
40
- else
41
- android_os_group(raw_os).merge ios_os_group(raw_os)
42
- end
43
- computed = []
44
- grouped.each do |k, v|
45
- version_name = k
46
- total = v.map { |version| version['Users'].to_i }.reduce(0, :+)
47
- computed << { 'version' => version_name, 'total' => total, 'percentage' => as_percentage(os_total, total.to_f) }
48
- end
49
- computed
50
14
  end
51
15
 
52
- def os_total
53
- filtered = filter_os(@stats.data[:os_version], @platform)
54
- total = 0
55
- filtered.each do |row|
56
- total += row['Users'].to_i
57
- end
58
- total
16
+ # Get all OS versions, grouped by Major version
17
+ # @param [Symbol] platform One of :all, :ios, :android
18
+ # @param [Boolean] grouped Group by Major OS version
19
+ # @param [Boolean] major_order Order by Major OS version (instead of percentage)
20
+ def os(platform: :all, grouped: true, major_order: true)
21
+ os_data = all_os
22
+ filtered = filter_os(os_data, platform)
23
+
24
+ data = if grouped
25
+ make_group_stats(filtered, platform)
26
+ else
27
+ filtered
28
+ end
29
+
30
+ major_order ? major_version_sort(data) : data
59
31
  end
60
32
 
61
- def devices(friendly: false, limit: 10)
62
- filtered = filter_device(@stats.data[:devices], @platform)
33
+ # Gets all devices
34
+ # @param [Boolean] friendly Transform the Android model numbers into their human numaes
35
+ # @param [Integer] limit Number of devices to turn
36
+ # @param [Symbol] platform One of :all, :ios, :android
37
+ def devices(friendly: false, limit: 10, platform: :all)
38
+ filtered = DeviceUtils.filter_device(@stats.get(:devices), platform)
63
39
  filtered = filtered.take(limit || 10)
64
40
  cleaned = []
65
41
  filtered.each do |row|
66
42
  device = {
67
43
  'model' => row['Device model']
68
44
  }
69
- if friendly && ((@platform == :all) || (@platform == :android))
45
+ if friendly && ((platform == :all) || (platform == :android))
70
46
  mapped = Android::Devices.search_by_model(row['Device model'])
71
47
  device['friendly'] = if mapped.nil?
72
48
  row['Device model']
@@ -82,25 +58,26 @@ class FirebaseStats
82
58
  end
83
59
 
84
60
  def gender
85
- raw = @stats.data[:gender]
61
+ raw = @stats.get(:gender)
86
62
  data = []
87
63
  raw.each do |row|
88
64
  data << {
89
65
  'gender' => row['Gender'],
90
- 'count' => row['Users']
66
+ 'count' => row['Users'].to_i
91
67
  }
92
68
  end
93
69
  data
94
70
  end
95
71
 
96
72
  def gender_age
97
- raw = @stats.data[:gender_age]
73
+ raw = @stats.get(:gender_age)
98
74
  data = []
99
75
  raw.each do |row|
100
76
  data << {
101
77
  'age' => row['Category'],
102
78
  'male' => (row['Male'].to_f * 100).round(2),
103
- 'female' => (row['Female'].to_f * 100).round(2)
79
+ 'female' => (row['Female'].to_f * 100).round(2),
80
+ 'other' => (row['Other'].to_f * 100).round(2)
104
81
  }
105
82
  end
106
83
  data
@@ -113,32 +90,14 @@ class FirebaseStats
113
90
  def filter_os(os_data, platform)
114
91
  case platform
115
92
  when :android
116
- os_data.select { |row| row['OS with version'].downcase.include?('android') }
93
+ os_data.select { |row| row['version'].downcase.include?('android') }
117
94
  when :ios
118
- os_data.select { |row| row['OS with version'].downcase.include?('ios') }
95
+ os_data.select { |row| row['version'].downcase.include?('ios') }
119
96
  else
120
97
  os_data
121
98
  end
122
99
  end
123
100
 
124
- # @param [CSV::Table] device_data
125
- # @param [Symbol] platform One of :all, :ios, :android
126
- def filter_device(device_data, platform)
127
- case platform
128
- when :android
129
- device_data.reject { |row| ios_device? row['Device model'] }
130
- when :ios
131
- device_data.select { |row| ios_device? row['Device model'] }
132
- else
133
- device_data
134
- end
135
- end
136
-
137
- # @param [CSV::Row] device_name
138
- def ios_device?(device_name)
139
- device_name.downcase.include?('iphone') or device_name.downcase.include?('ipad') or device_name.downcase.include?('ipod')
140
- end
141
-
142
101
  def as_percentage(total, value)
143
102
  percentage = (value / total) * 100
144
103
  if percentage < 0.01
@@ -149,11 +108,68 @@ class FirebaseStats
149
108
  end
150
109
 
151
110
  def ios_os_group(os_details)
152
- filter_os(os_details, :ios).group_by { |row| row['OS with version'].match('(iOS [0-9]{1,2})').captures[0] }
111
+ filter_os(os_details, :ios).group_by { |row| row['version'].match('(iOS [0-9]{1,2})').captures[0] }
153
112
  end
154
113
 
155
114
  def android_os_group(os_details)
156
- filter_os(os_details, :android).group_by { |row| row['OS with version'].match('(Android [0-9]{1,2})').captures[0] }
115
+ filter_os(os_details, :android).group_by { |row| row['version'].match('(Android [0-9]{1,2})').captures[0] }
116
+ end
117
+
118
+ # Get all OS versions
119
+ def all_os
120
+ data = @stats.get(:os_version)
121
+
122
+ data.map do |row|
123
+ {
124
+ 'version' => row['OS with version'],
125
+ 'count' => row['Users'].to_i
126
+ }
127
+ end
128
+ end
129
+
130
+ def make_group_stats(os_data, platform)
131
+ data = make_os_groups(os_data, platform)
132
+
133
+ total_devices = os_total(os_data)
134
+ data.map do |k, v|
135
+ version_name = k
136
+ group_total = v.map { |version| version['count'].to_i }.reduce(0, :+)
137
+ { 'version' => version_name,
138
+ 'total' => group_total,
139
+ 'percentage' => as_percentage(total_devices.to_f, group_total) }
140
+ end
141
+ end
142
+
143
+ def make_os_groups(os_data, platform)
144
+ case platform
145
+ when :ios
146
+ ios_os_group(os_data)
147
+ when :android
148
+ android_os_group(os_data)
149
+ else
150
+ android_os_group(os_data).merge ios_os_group(os_data)
151
+ end
152
+ end
153
+
154
+ def os_total(os_data)
155
+ os_data.map { |row| row['count'] }.reduce(0, :+)
156
+ end
157
+
158
+ def major_version_sort(data)
159
+ data.sort_by do |row|
160
+ version = row['version']
161
+ number = version.match('([0-9.]+)').captures[0]
162
+ Gem::Version.new(number)
163
+ end.reverse
164
+ end
165
+
166
+ def self.tip(section)
167
+ tips = {
168
+ :os_version => "This data can now be found in the Audiences section of Firebase Analytics. Before you export the CSV file, change one of the charts to `Users` by `OS with version`",
169
+ :gender_age => "Note: The columns for Gender+Age are not always in the same order, but this is taken into account when searching",
170
+ :devices => "Note: If you export from the Tech Details: Device Model page, this is currently unsupported as it has two different headers. Use the export from the main Dashboard page"
171
+ }
172
+ tips[section]
157
173
  end
158
174
  end
159
175
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firebase-stats
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - chedabob
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-19 00:00:00.000000000 Z
11
+ date: 2022-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: android-devices
@@ -103,9 +103,10 @@ extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
105
  - bin/firebase-stats
106
- - lib/.DS_Store
106
+ - lib/device_utils.rb
107
107
  - lib/firebase-stats.rb
108
108
  - lib/reader.rb
109
+ - lib/section_not_found_error.rb
109
110
  - lib/version.rb
110
111
  - lib/wrapper.rb
111
112
  homepage: https://github.com/chedabob/firebase-stats
@@ -127,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
128
  - !ruby/object:Gem::Version
128
129
  version: '0'
129
130
  requirements: []
130
- rubygems_version: 3.2.14
131
+ rubygems_version: 3.0.9
131
132
  signing_key:
132
133
  specification_version: 4
133
134
  summary: Firebase CSV Stats CLI
data/lib/.DS_Store DELETED
Binary file