firebase-stats 1.0.3 → 1.0.7

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
  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