merge develop
This commit is contained in:
commit
c91fd0cb0e
|
|
@ -41,7 +41,7 @@ gem 'uuidtools', '2.1.2'
|
|||
gem 'jquery-rails' # , '2.3.0' # pinned because jquery-ui-rails was split from jquery-rails, but activeadmin doesn't support this gem yet
|
||||
gem 'jquery-ui-rails', '4.2.1'
|
||||
gem 'rails3-jquery-autocomplete'
|
||||
gem 'activeadmin', '0.6.2'
|
||||
gem 'activeadmin', github: 'activeadmin', branch: '0-6-stable'
|
||||
gem 'mime-types', '1.25'
|
||||
gem 'meta_search'
|
||||
gem 'fog', "~> 1.18.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
ActiveAdmin.register Cohort, :as => 'Cohorts' do
|
||||
|
||||
menu :label => 'Cohorts', :parent => 'Analytics'
|
||||
|
||||
config.sort_order = 'group_start_desc'
|
||||
config.batch_actions = false
|
||||
config.clear_action_items!
|
||||
config.filters = false
|
||||
config.per_page = 50
|
||||
|
||||
controller do
|
||||
|
||||
def scoped_collection
|
||||
objs = super
|
||||
Cohort.alltime_cohorts! if 0 == Cohort.where(all_time: true).count
|
||||
objs = objs.where(all_time: true).order('group_start DESC')
|
||||
logger.debug("*** scoped_collection: #{objs.to_sql}")
|
||||
objs
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
index :title => "All-Time Cohorts" do
|
||||
column 'Cohort' do |cc| cc.group_start_str end
|
||||
column Cohort::TOTAL_LABELS[:registered_users] do |cc| cc.data_val(:registered_users) end
|
||||
|
||||
column Cohort::TOTAL_LABELS[:first_downloaded_client_at] do |cc|
|
||||
cc.data_val(:first_downloaded_client_at)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:first_downloaded_client_at, true) end
|
||||
|
||||
column Cohort::TOTAL_LABELS[:first_certified_gear_at] do |cc|
|
||||
cc.data_val(:first_certified_gear_at)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:first_certified_gear_at, true) end
|
||||
|
||||
column Cohort::TOTAL_LABELS[:music_sessions_user_history] do |cc|
|
||||
cc.data_val(:music_sessions_user_history)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:music_sessions_user_history, true) end
|
||||
|
||||
column Cohort::TOTAL_LABELS[:jam_track_rights] do |cc|
|
||||
cc.data_val(:jam_track_rights)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:jam_track_rights, true) end
|
||||
|
||||
column Cohort::TOTAL_LABELS[:recorded_tracks] do |cc|
|
||||
cc.data_val(:recorded_tracks)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:recorded_tracks, true) end
|
||||
|
||||
column Cohort::TOTAL_LABELS[:friendships] do |cc|
|
||||
cc.data_val(:friendships)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:friendships, true) end
|
||||
|
||||
column Cohort::TOTAL_LABELS[:invited_users] do |cc|
|
||||
cc.data_val(:invited_users)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:invited_users, true) end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
ActiveAdmin.register Cohort, :as => 'Cohorts Monthly' do
|
||||
|
||||
menu :label => 'Cohorts Monthly', :parent => 'Analytics'
|
||||
|
||||
config.sort_order = 'group_start_desc'
|
||||
config.batch_actions = false
|
||||
config.clear_action_items!
|
||||
config.per_page = 50
|
||||
|
||||
filter(:monthly_start, as: :select, collection: Cohort.monthly_starts)
|
||||
filter(:monthly_end, as: :select, collection: Cohort.monthly_ends)
|
||||
|
||||
controller do
|
||||
def scoped_collection
|
||||
objs = super
|
||||
args = params[:q] || {}
|
||||
if !(args[:monthly_start_eq].nil? || args[:monthly_end_eq].nil?)
|
||||
mstart, mend = Time.parse(args[:monthly_start_eq]), Time.parse(args[:monthly_end_eq])
|
||||
if mstart < mend
|
||||
Cohort.monthly_cohorts(mstart, mend) # populate monthlys
|
||||
objs = objs.where(monthly_start: mstart, monthly_end: mend)
|
||||
end
|
||||
end
|
||||
objs.where(all_time: false).order('group_start DESC')
|
||||
end
|
||||
end
|
||||
|
||||
index :title => "Monthly Cohorts" do
|
||||
column 'Cohort' do |cc| cc.group_start_str end
|
||||
column Cohort::MONTHLY_LABELS[:registered_users] do |cc| cc.data_val(:registered_users) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:first_downloaded_client_at] do |cc|
|
||||
cc.data_val(:first_downloaded_client_at)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:first_downloaded_client_at, true) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:first_certified_gear_at] do |cc|
|
||||
cc.data_val(:first_certified_gear_at)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:first_certified_gear_at, true) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:music_sessions_user_history_1] do |cc|
|
||||
cc.data_val(:music_sessions_user_history_1)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:music_sessions_user_history_1, true) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:music_sessions_user_history_2_5] do |cc|
|
||||
cc.data_val(:music_sessions_user_history_2_5)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:music_sessions_user_history_2_5, true) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:music_sessions_user_history_6_] do |cc|
|
||||
cc.data_val(:music_sessions_user_history_6_)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:music_sessions_user_history_6_, true) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:jam_track_rights] do |cc|
|
||||
cc.data_val(:jam_track_rights)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:jam_track_rights, true) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:recorded_tracks] do |cc|
|
||||
cc.data_val(:recorded_tracks)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:recorded_tracks, true) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:friendships] do |cc|
|
||||
cc.data_val(:friendships)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:friendships, true) end
|
||||
|
||||
column Cohort::MONTHLY_LABELS[:invited_users] do |cc|
|
||||
cc.data_val(:invited_users)
|
||||
end
|
||||
column '%' do |cc| cc.data_val(:invited_users, true) end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -25,7 +25,6 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
|
|||
column :original_artist
|
||||
column :name
|
||||
column :status
|
||||
column :preview do |jam_track| jam_track.has_preview? ? (link_to "Download", jam_track.sign_url(3600)) : 'None' end
|
||||
column :master_track do |jam_track| jam_track.master_track.nil? ? 'None' : (link_to "Download", jam_track.master_track.url_by_sample_rate(44)) end
|
||||
column :licensor
|
||||
column :genre
|
||||
|
|
|
|||
|
|
@ -0,0 +1,290 @@
|
|||
require 'date'
|
||||
|
||||
class Cohort < ActiveRecord::Base
|
||||
|
||||
EARLIEST_DATE = Time.parse('2014-03-01')
|
||||
|
||||
KEY_SET_TOTAL = [:registered_users, :first_downloaded_client_at, :first_certified_gear_at, :music_sessions_user_history, 'play_jamtr', :jam_track_rights, :recorded_tracks, :friendships, :invited_users]
|
||||
|
||||
TOTAL_LABELS = {
|
||||
registered_users: 'Registered Users',
|
||||
first_downloaded_client_at: 'Downloaded app',
|
||||
first_certified_gear_at: 'Certified Gear',
|
||||
music_sessions_user_history: 'Played Online',
|
||||
play_jamtr: 'Played JamTrack',
|
||||
jam_track_rights: 'Purchased JamTrack',
|
||||
recorded_tracks: 'Made Recording',
|
||||
friendships: 'Connected w/Friend',
|
||||
invited_users: 'Invite Others',
|
||||
}
|
||||
|
||||
# KEY_SET_TOTAL = [:registered_users, :first_downloaded_client_at, :first_certified_gear_at, :music_session_histories, 'play_jamtr', :jam_track_rights, :recordings, :friendships, :invited_users]
|
||||
KEY_SET_MONTH = [:registered_users, :first_downloaded_client_at, :first_certified_gear_at, :visit_1, :visit_2_5, :visit_6_, :play_online_1, :play_online_2_5, :play_online_6_, :play_jamtr_1, :play_jamtr_2_5, :play_jamtr_6_, :jam_track_rights_redeemed, :jam_track_rights, :recorded_tracks, :friendships, :invited_users]
|
||||
|
||||
MONTHLY_LABELS = {
|
||||
registered_users: 'Registered Users',
|
||||
first_downloaded_client_at: 'Downloaded app',
|
||||
first_certified_gear_at: 'Certified Gear',
|
||||
music_sessions_user_history_1: 'Played Online 1',
|
||||
music_sessions_user_history_2_5: 'Played Online 2-5',
|
||||
music_sessions_user_history_6_: 'Played Online 6+',
|
||||
play_jamtr: 'Played JamTrack',
|
||||
jam_track_rights: 'Purchased JamTrack',
|
||||
recorded_tracks: 'Made Recording',
|
||||
friendships: 'Connected w/Friend',
|
||||
invited_users: 'Invite Others',
|
||||
}
|
||||
|
||||
serialize :data_set, JSON
|
||||
|
||||
before_create do
|
||||
self.data_set ||= {}
|
||||
end
|
||||
|
||||
def self.date_tuples(from,to)
|
||||
prec = from.size
|
||||
start = Date.new(*from)
|
||||
finish = Date.new(*to)
|
||||
|
||||
filter_on = [:day,:mon].first(3-prec)
|
||||
filter = ->(d) { filter_on.all? {|attr| d.send(attr) == 1 } }
|
||||
|
||||
(start..finish)
|
||||
.select(&filter)
|
||||
.map { |d| [d.year,d.mon,d.day].first(prec) }
|
||||
end
|
||||
|
||||
def self.earliest_cohort
|
||||
starting = User.where(admin: false).order(:created_at).first.created_at
|
||||
starting = EARLIEST_DATE if starting < EARLIEST_DATE
|
||||
end
|
||||
|
||||
def self.cohort_group_ranges(starting=nil, ending=nil)
|
||||
starting ||= self.earliest_cohort
|
||||
ending ||= Time.now
|
||||
dates = self.date_tuples([starting.year, starting.month], [ending.year, ending.month])
|
||||
ranges = []
|
||||
dates.each_with_index do |d1, idx|
|
||||
d2 = dates[idx+1] || [Time.now.next_month.year,Time.now.next_month.month]
|
||||
rr = Time.parse("#{d1[0]}-#{d1[1]}-1")..(Time.parse("#{d2[0]}-#{d2[1]}-1") - 1.second)
|
||||
ranges << rr
|
||||
end
|
||||
ranges
|
||||
end
|
||||
|
||||
def self.generate_monthly_cohorts(monthly_start, monthly_end)
|
||||
Cohort.delete_all(['all_time = ?',false])
|
||||
self.cohort_group_ranges.collect do |range|
|
||||
next if range.first > monthly_end
|
||||
cc = Cohort.new
|
||||
cc.group_start = range.first
|
||||
cc.group_end = range.last
|
||||
cc.monthly_start = monthly_start
|
||||
cc.monthly_end = monthly_end
|
||||
cc.all_time = false
|
||||
cc.save!
|
||||
cc
|
||||
end
|
||||
end
|
||||
|
||||
def self.generate_all_time_cohorts
|
||||
self.cohort_group_ranges.collect do |range|
|
||||
unless cc = Cohort.where(group_start: range.first).where(all_time: true).limit(1).first
|
||||
cc = Cohort.new
|
||||
cc.group_start = range.first
|
||||
cc.group_end = range.last
|
||||
cc.all_time = true
|
||||
cc.save!
|
||||
end
|
||||
cc
|
||||
end
|
||||
end
|
||||
|
||||
def _put_data_set(key, count, num_user)
|
||||
self.data_set[key.to_s] = count
|
||||
xx = (count.to_f / num_user.to_f)
|
||||
self.data_set["#{key}%"] = 100.0 * xx.round(2)
|
||||
end
|
||||
|
||||
def self.cohort_users(cohort)
|
||||
User.where(created_at: cohort.group_start..cohort.group_end)
|
||||
end
|
||||
|
||||
def _played_online_subquery(constraint)
|
||||
where = if constraint.is_a?(Range)
|
||||
"played.cnt >= #{constraint.first} AND played.cnt <= #{constraint.last}"
|
||||
else
|
||||
"played.cnt #{constraint}"
|
||||
end
|
||||
start_date = all_time ? self.group_start : self.monthly_start
|
||||
end_date = all_time ? self.group_end : self.monthly_end
|
||||
sql =<<SQL
|
||||
SELECT played.user_id FROM
|
||||
(SELECT user_id, COUNT(*) cnt FROM music_sessions_user_history msuh1
|
||||
WHERE
|
||||
msuh1.created_at >= '#{start_date}' AND msuh1.created_at <= '#{end_date}' AND
|
||||
EXTRACT(EPOCH FROM (msuh1.session_removed_at - msuh1.created_at)) >= 900 AND
|
||||
(SELECT COUNT(*) FROM music_sessions_user_history msuh2
|
||||
WHERE msuh1.music_session_id = msuh2.music_session_id
|
||||
) > 1
|
||||
GROUP BY user_id
|
||||
) played
|
||||
WHERE #{where}
|
||||
SQL
|
||||
end
|
||||
|
||||
def _subquery(assoc_key, num_user)
|
||||
assoc = User.reflections[assoc_key]
|
||||
start_date = all_time ? self.group_start : self.monthly_start
|
||||
end_date = all_time ? self.group_end : self.monthly_end
|
||||
sql =<<SQL
|
||||
SELECT #{assoc.foreign_key} FROM #{assoc.class_name.constantize.table_name} tt
|
||||
WHERE
|
||||
tt.created_at >= '#{start_date}' AND
|
||||
tt.created_at <= '#{end_date}'
|
||||
SQL
|
||||
yield(sql) if block_given?
|
||||
self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||
end
|
||||
|
||||
def _monthly!
|
||||
unless 0 < num_user = self.class.cohort_users(self).count
|
||||
self.update_attribute(:data_set, {})
|
||||
return
|
||||
end
|
||||
|
||||
self.data_set['registered_users'] = num_user
|
||||
num_user = num_user.to_f
|
||||
|
||||
qq = self.class.cohort_users(self)
|
||||
.where(first_downloaded_client_at: self.monthly_start..self.monthly_end)
|
||||
_put_data_set(:first_downloaded_client_at, qq.count, num_user)
|
||||
|
||||
qq = self.class.cohort_users(self)
|
||||
.where(first_certified_gear_at: self.monthly_start..self.monthly_end)
|
||||
_put_data_set(:first_certified_gear_at, qq.count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :invited_users, num_user)
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :recorded_tracks, num_user)
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :jam_track_rights, num_user)
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :jam_track_rights, num_user) do |subsql|
|
||||
subsql += " AND tt.redeemed = 't' "
|
||||
end
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :friendships, num_user)
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
sql = _played_online_subquery(' = 1 ')
|
||||
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||
_put_data_set(:music_sessions_user_history_1, count, num_user)
|
||||
|
||||
sql = _played_online_subquery(2..5)
|
||||
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||
_put_data_set(:music_sessions_user_history_2_5, count, num_user)
|
||||
|
||||
sql = _played_online_subquery(' >= 6')
|
||||
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||
_put_data_set(:music_sessions_user_history_6_, count, num_user)
|
||||
|
||||
self.save!
|
||||
end
|
||||
|
||||
def _join_user_all_time(assoc_ref)
|
||||
assoc_ref.active_record
|
||||
.joins("INNER JOIN users AS uu ON uu.id = #{assoc_ref.foreign_key}")
|
||||
.where(created_at: self.group_start..self.group_end)
|
||||
.where(['uu.created_at >= ? AND uu.created_at <= ?', self.group_start, self.group_end])
|
||||
end
|
||||
|
||||
def _all_time!
|
||||
unless 0 < num_user = self.class.cohort_users(self).count
|
||||
self.update_attribute(:data_set, {})
|
||||
return
|
||||
end
|
||||
|
||||
self.data_set['registered_users'] = num_user
|
||||
num_user = num_user.to_f
|
||||
|
||||
count = self.class.cohort_users(self)
|
||||
.where(['first_downloaded_client_at IS NOT NULL'])
|
||||
.count
|
||||
_put_data_set('first_downloaded_client_at', count, num_user)
|
||||
|
||||
count = self.class.cohort_users(self)
|
||||
.where(['first_certified_gear_at IS NOT NULL'])
|
||||
.count
|
||||
_put_data_set('first_certified_gear_at', count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :invited_users, num_user)
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :recorded_tracks, num_user)
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :friendships, num_user)
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
count = _subquery(assoc_key = :jam_track_rights, num_user)
|
||||
_put_data_set(assoc_key, count, num_user)
|
||||
|
||||
sql = _played_online_subquery(' >= 1')
|
||||
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||
_put_data_set(:music_sessions_user_history, count, num_user)
|
||||
|
||||
self.save!
|
||||
end
|
||||
|
||||
def populate!
|
||||
self.all_time ? _all_time! : _monthly!
|
||||
end
|
||||
|
||||
def self.alltime_cohorts!
|
||||
Cohort.generate_all_time_cohorts.each do |cc|
|
||||
cc._all_time!
|
||||
end
|
||||
end
|
||||
|
||||
def self.monthly_cohorts(monthly_start, monthly_end)
|
||||
self.generate_monthly_cohorts(monthly_start, monthly_end).compact.each do |cc|
|
||||
cc._monthly!
|
||||
end
|
||||
end
|
||||
|
||||
def group_start_str
|
||||
self.group_start.strftime('%Y-%m-%d')
|
||||
end
|
||||
|
||||
def group_end_str
|
||||
self.group_end.strftime('%Y-%m-%d')
|
||||
end
|
||||
|
||||
def data_val(col, percent=false)
|
||||
if percent
|
||||
val = self.data_set["#{col}%"]
|
||||
val ? '%0.f' % val : ''
|
||||
else
|
||||
self.data_set[col.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
def self.monthly_starts
|
||||
self.cohort_group_ranges.collect do |rr|
|
||||
rr.first.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def self.monthly_ends
|
||||
self.cohort_group_ranges.collect do |rr|
|
||||
rr.last.to_s
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
= f.input :name, :input_html => { :rows=>1, :maxlength=>200 }
|
||||
= f.input :description, :input_html => { :rows=>5, :maxlength=>1000 }
|
||||
= f.input :plan_code, :label=>'Recurly Plan Code', :required=>true, :hint => 'Must match plan code in Recurly'
|
||||
= f.input :version, :label => 'Version', :hint => 'Increment this value whenever you invalidate (update) the media in the JamTrack. Changing JMEP does not count as a version change; changing anything about a track (audio, instrument, part) does.'
|
||||
//= f.input :initial_play_silence, :label => 'Initial Play Silence (seconds)'
|
||||
= f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: true
|
||||
= f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false, hint: 'Only set to Production when end users should be able to purchase this JamTrack'
|
||||
|
|
@ -23,15 +22,11 @@
|
|||
= f.input :public_performance_royalty, :label => 'Public Performance Royalty'
|
||||
= f.input :reproduction_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
|
||||
= f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
|
||||
= f.input :preview_start_time_raw, :label=>'Preview Start Time', :hint => 'MM:SS:MLS', :as => :string
|
||||
- unless f.object.nil? || f.object[:preview_url].nil?
|
||||
.current_file_holder style='margin-bottom:10px'
|
||||
a href=f.object.sign_url(3600) style='padding:0 0 0 20px'
|
||||
| Download
|
||||
|
||||
//= f.input :url, :as => :file, :label => 'Audio File'
|
||||
= f.input :jmep_text, :as => :text, :label => "JMEP Text", :input_html => {:rows => 5 }, :hint => 'Tap-Ins & Lead Silence. Examples: https://jamkazam.atlassian.net/wiki/pages/viewpage.action?pageId=39289025#JamKazamMeta-EventProcessor(JMEP)-CommonExamples'
|
||||
= f.input :jmep_json, :as => :text, :label => "JMEP Json", :input_html => {:rows => 5, :readonly => true }, :hint => 'Readonly field. This is shown here just so you can see what your JMEP got converted to readily'
|
||||
= f.input :version, :label => 'Version', :hint => 'Increment this value whenever you invalidate (update) the media in the JamTrack. Changing JMEP does not count as a version change; changing anything about a track (audio, instrument, part) does.'
|
||||
|
||||
= f.semantic_fields_for :jam_track_tracks do |track|
|
||||
= render 'jam_track_track_fields', f: track
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@
|
|||
= f.input :track_type, :as => :select, collection: ['Track', 'Master'], include_blank: false
|
||||
= f.input :instrument, collection: Instrument.all, include_blank: false
|
||||
= f.input :part, :required=>true, :input_html => { :rows=>1, :maxlength=>20, :type=>'numeric' }
|
||||
|
||||
= f.input :position
|
||||
= f.input :preview_start_time_raw, :label => 'Preview Start Time', :hint => 'MM:SS:MLS', :as => :string
|
||||
- unless f.object.nil? || f.object[:preview_url].nil?
|
||||
.current_file_holder style='margin-bottom:10px'
|
||||
a href=f.object.preview_sign_url(3600) style='padding:0 0 0 20px'
|
||||
| Download Preview
|
||||
|
||||
- if f.object.new_record?
|
||||
p style='margin-left:10px'
|
||||
|
|
@ -16,12 +20,12 @@
|
|||
- unless f.object.nil? || f.object[:url_48].nil?
|
||||
.current_file_holder style='margin-bottom:10px'
|
||||
a href=f.object.sign_url(3600) style='padding:0 0 0 20px'
|
||||
| Download
|
||||
| #{File.basename(f.object[:url_48])}
|
||||
|
||||
= f.input :url_44, :as => :file, :label => 'Track file (44kHz)'
|
||||
- unless f.object.nil? || f.object[:url_44].nil?
|
||||
.current_file_holder style='margin-bottom:10px'
|
||||
a href=f.object.sign_url(3600, 44) style='padding:0 0 0 20px'
|
||||
| Download
|
||||
| #{File.basename(f.object[:url_44])}
|
||||
|
||||
= link_to_remove_association "Delete Track", f, class: 'button', style: 'margin-left:10px'
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
class JamRuby::JamTrackTrack
|
||||
|
||||
# add a custom validation
|
||||
|
||||
attr_accessor :preview_generate_error
|
||||
|
||||
validate :preview
|
||||
|
||||
def preview
|
||||
if preview_generate_error
|
||||
errors.add(:preview_start_time, preview_generate_error)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# this is used by active admin/jam-admin
|
||||
def preview_start_time_raw
|
||||
if self.preview_start_time.nil? || self.preview_start_time.nil?
|
||||
''
|
||||
else
|
||||
seconds = self.preview_start_time.to_f/1000
|
||||
time = Time.at(seconds)
|
||||
time.strftime("%M:%S:#{(self.preview_start_time % 1000).to_s.rjust(3, '0')}")
|
||||
end
|
||||
end
|
||||
|
||||
# this is used by active admin/jam-admin
|
||||
def preview_start_time_raw=(new_value)
|
||||
|
||||
value = nil
|
||||
if new_value == nil || new_value == ''
|
||||
value = nil
|
||||
else
|
||||
if new_value && new_value.kind_of?(String) && new_value.include?(':')
|
||||
bits = new_value.split(':')
|
||||
if bits.length != 3
|
||||
raise "format of preview start time must be MM:SS:MLS"
|
||||
end
|
||||
|
||||
value = (bits[0].to_i * 60000) + (bits[1].to_i * 1000) + (bits[2].to_i)
|
||||
|
||||
else
|
||||
raise "format of preview start time must be MM:SS:MLS"
|
||||
end
|
||||
end
|
||||
|
||||
if !value.nil? && value != self.preview_start_time
|
||||
self.preview_start_time = value
|
||||
generate_preview
|
||||
else
|
||||
self.preview_start_time = value
|
||||
end
|
||||
end
|
||||
|
||||
def generate_preview
|
||||
|
||||
begin
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
|
||||
input = File.join(tmp_dir, 'in.ogg')
|
||||
output = File.join(tmp_dir, 'out.ogg')
|
||||
|
||||
start = self.preview_start_time.to_f / 1000
|
||||
stop = start + 20
|
||||
|
||||
raise 'no track' unless self["url_44"]
|
||||
|
||||
s3_manager.download(self.url_by_sample_rate(44), input)
|
||||
|
||||
command = "sox \"#{input}\" \"#{output}\" trim #{start} #{stop}"
|
||||
|
||||
@@log.debug("trimming using: " + command)
|
||||
|
||||
sox_output = `#{command}`
|
||||
|
||||
result_code = $?.to_i
|
||||
|
||||
if result_code != 0
|
||||
@@log.debug("fail #{result_code}")
|
||||
@preview_generate_error = "unable to execute cut command #{sox_output}"
|
||||
else
|
||||
@@log.debug("uploading preview to #{self.preview_filename}")
|
||||
|
||||
s3_manager.upload(self.preview_filename, output)
|
||||
|
||||
# and finally update the JamTrackTrack with the new info
|
||||
self["preview_url"] = self.preview_filename
|
||||
self["preview_md5"] = ::Digest::MD5.file(output).hexdigest
|
||||
self["preview_length"] = File.new(output).size
|
||||
self.save!
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
@@log.error("error in sox command #{e.to_s}")
|
||||
@preview_generate_error = e.to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -6,7 +6,6 @@ class JamRuby::JamTrack
|
|||
|
||||
before_save :jmep_json_generate
|
||||
validate :jmep_text_validate
|
||||
validate :preview
|
||||
|
||||
def jmep_text_validate
|
||||
begin
|
||||
|
|
@ -28,97 +27,4 @@ class JamRuby::JamTrack
|
|||
#errors.add(:jmep_text, err.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def preview
|
||||
if preview_generate_error
|
||||
errors.add(:preview_url, preview_generate_error)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# this is used by active admin/jam-admin
|
||||
def preview_start_time_raw
|
||||
if self.preview_start_time.nil? || self.preview_start_time.nil?
|
||||
''
|
||||
else
|
||||
seconds = self.preview_start_time.to_f/1000
|
||||
time = Time.at(seconds)
|
||||
time.strftime("%M:%S:#{(self.preview_start_time % 1000).to_s.rjust(3, '0')}")
|
||||
end
|
||||
end
|
||||
|
||||
# this is used by active admin/jam-admin
|
||||
def preview_start_time_raw=(new_value)
|
||||
|
||||
value = nil
|
||||
if new_value == nil || new_value == ''
|
||||
value = nil
|
||||
else
|
||||
if new_value && new_value.kind_of?(String) && new_value.include?(':')
|
||||
bits = new_value.split(':')
|
||||
if bits.length != 3
|
||||
raise "format of preview start time must be MM:SS:MLS"
|
||||
end
|
||||
|
||||
value = (bits[0].to_i * 60000) + (bits[1].to_i * 1000) + (bits[2].to_i)
|
||||
|
||||
else
|
||||
raise "format of preview start time must be MM:SS:MLS"
|
||||
end
|
||||
end
|
||||
|
||||
if !value.nil? && value != self.preview_start_time
|
||||
self.preview_start_time = value
|
||||
generate_preview
|
||||
else
|
||||
self.preview_start_time = value
|
||||
end
|
||||
end
|
||||
|
||||
def generate_preview
|
||||
|
||||
begin
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
|
||||
input = File.join(tmp_dir, 'in.ogg')
|
||||
output = File.join(tmp_dir, 'out.ogg')
|
||||
|
||||
start = self.preview_start_time.to_f / 1000
|
||||
stop = start + 20
|
||||
|
||||
master_track = self.master_track
|
||||
|
||||
raise 'no master track' unless master_track
|
||||
|
||||
s3_manager.download(master_track.url_by_sample_rate(44), input)
|
||||
|
||||
command = "sox \"#{input}\" \"#{output}\" trim #{start} #{stop}"
|
||||
|
||||
@@log.debug("trimming using: " + command)
|
||||
|
||||
sox_output = `#{command}`
|
||||
|
||||
result_code = $?.to_i
|
||||
|
||||
if result_code != 0
|
||||
@preview_generate_error = "unable to execute cut command #{sox_output}"
|
||||
else
|
||||
@@log.debug("uploading preview to #{self.preview_filename}")
|
||||
|
||||
s3_manager.upload(self.preview_filename, output)
|
||||
|
||||
# and finally update the JamTrackTrack with the new info
|
||||
self["preview_url"] = self.preview_filename
|
||||
self["preview_md5"] = ::Digest::MD5.file(output).hexdigest
|
||||
self["preview_length"] = File.new(output).size
|
||||
self.save!
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
@preview_generate_error = e.to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -266,3 +266,5 @@ jam_track_importer.sql
|
|||
jam_track_pro_licensing_update.sql
|
||||
jam_track_redeemed.sql
|
||||
connection_metronome.sql
|
||||
preview_jam_track_tracks.sql
|
||||
cohorts.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE cohorts (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
data_set JSON NOT NULL DEFAULT '{}',
|
||||
|
||||
group_start TIMESTAMP NOT NULL,
|
||||
group_end TIMESTAMP NOT NULL,
|
||||
|
||||
all_time BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
monthly_start TIMESTAMP,
|
||||
monthly_end TIMESTAMP,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX index_group_date ON cohorts USING btree (group_start);
|
||||
|
||||
CREATE INDEX msuh_music_session_idx ON music_sessions_user_history USING btree(music_session_id);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
ALTER TABLE jam_tracks DROP COLUMN preview_url;
|
||||
ALTER TABLE jam_tracks DROP COLUMN preview_md5;
|
||||
ALTER TABLE jam_tracks DROP COLUMN preview_length;
|
||||
ALTER TABLE jam_tracks DROP COLUMN preview_start_time;
|
||||
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_url VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_md5 VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_length BIGINT;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN preview_start_time INTEGER;
|
||||
|
|
@ -17,7 +17,7 @@ module JamRuby
|
|||
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price,
|
||||
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
|
||||
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
|
||||
:jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :preview_start_time_raw, as: :admin
|
||||
:jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, as: :admin
|
||||
|
||||
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
|
||||
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
|
||||
|
|
@ -31,7 +31,6 @@ module JamRuby
|
|||
validates :sales_region, inclusion: {in: [nil] + SALES_REGION}
|
||||
validates_format_of :price, with: /^\d+\.*\d{0,2}$/
|
||||
validates :version, presence: true
|
||||
validates :preview_start_time, numericality: {only_integer: true}, length: {in: 1..1000}, :allow_nil => true
|
||||
validates :pro_ascap, inclusion: {in: [true, false]}
|
||||
validates :pro_bmi, inclusion: {in: [true, false]}
|
||||
validates :pro_sesac, inclusion: {in: [true, false]}
|
||||
|
|
@ -92,21 +91,6 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
# create name of the file
|
||||
def preview_filename
|
||||
"jam_track_previews/#{self.original_artist}/#{self.name}/preview-44100.ogg"
|
||||
end
|
||||
|
||||
def has_preview?
|
||||
!self["preview_url"].nil?
|
||||
end
|
||||
# creates a short-lived URL that has access to the object.
|
||||
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
||||
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
||||
# but the url is short lived enough so that it wouldn't be easily shared
|
||||
def sign_url(expiration_time = 120)
|
||||
s3_manager.sign_url(self[:preview_url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
||||
end
|
||||
|
||||
def master_track
|
||||
JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Master').first
|
||||
|
|
|
|||
|
|
@ -7,17 +7,21 @@ module JamRuby
|
|||
# there should only be one Master per JamTrack, but there can be N Track per JamTrack
|
||||
TRACK_TYPE = %w{Track Master}
|
||||
|
||||
@@log = Logging.logger[JamTrackTrack]
|
||||
|
||||
|
||||
mount_uploader :url_48, JamTrackTrackUploader
|
||||
mount_uploader :url_44, JamTrackTrackUploader
|
||||
|
||||
attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, as: :admin
|
||||
attr_accessible :url_44, :url_48, :md5_44, :md5_48, :length_44, :length_48, as: :admin
|
||||
attr_accessible :url_44, :url_48, :md5_44, :md5_48, :length_44, :length_48, :preview_start_time_raw, as: :admin
|
||||
|
||||
attr_accessor :original_audio_s3_path, :skip_uploader
|
||||
|
||||
validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000}
|
||||
validates :part, length: {maximum: 25}
|
||||
validates :track_type, inclusion: {in: TRACK_TYPE }
|
||||
validates :preview_start_time, numericality: {only_integer: true}, length: {in: 1..1000}, :allow_nil => true
|
||||
validates_uniqueness_of :position, scope: :jam_track_id
|
||||
validates_uniqueness_of :part, scope: [:jam_track_id, :instrument_id]
|
||||
# validates :jam_track, presence: true
|
||||
|
|
@ -37,6 +41,24 @@ module JamRuby
|
|||
"#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}"
|
||||
end
|
||||
|
||||
# create name of the file
|
||||
def preview_filename
|
||||
filename("#{File.basename(self["url_44"], ".ogg")}-preview.ogg")
|
||||
end
|
||||
|
||||
def has_preview?
|
||||
!self["preview_url"].nil?
|
||||
end
|
||||
|
||||
# creates a short-lived URL that has access to the object.
|
||||
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
||||
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
||||
# but the url is short lived enough so that it wouldn't be easily shared
|
||||
def preview_sign_url(expiration_time = 120)
|
||||
s3_manager.sign_url(self[:preview_url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
||||
end
|
||||
|
||||
|
||||
def manually_uploaded_filename(mounted_as)
|
||||
if track_type == 'Master'
|
||||
filename("Master Mix-#{mounted_as == :url_48 ? '48000' : '44100'}.ogg")
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
var TEST_TIMEOUT_CALLBACK = 'JK.HandleNetworkTestTimeout';
|
||||
|
||||
var $startNetworkTestBtn = null;
|
||||
var $foreverNetworkTestBtn = null;
|
||||
var $testResults = null;
|
||||
var $testScore = null;
|
||||
var $testText = null;
|
||||
|
|
@ -51,6 +52,8 @@
|
|||
var operatingSystem = null;
|
||||
var PRIME_PUMP_TIME = 1;
|
||||
|
||||
var forever = false;
|
||||
|
||||
// these try to make it such that we only pass a NetworkTest Pass/Failed one time in a new user flow
|
||||
var trackedPass = false;
|
||||
var lastNetworkFailure = null;
|
||||
|
|
@ -313,6 +316,9 @@
|
|||
|
||||
if (success) {
|
||||
$self.triggerHandler(NETWORK_TEST_DONE)
|
||||
if(forever) {
|
||||
prepareNetworkTest();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self.triggerHandler(NETWORK_TEST_FAIL)
|
||||
|
|
@ -875,6 +881,7 @@
|
|||
inGearWizard = _inGearWizard;
|
||||
|
||||
$startNetworkTestBtn = $step.find('.start-network-test');
|
||||
$foreverNetworkTestBtn = $step.find('.forever-network-test')
|
||||
|
||||
if ($startNetworkTestBtn.length == 0) throw 'no start network test button found in network-test'
|
||||
|
||||
|
|
@ -889,8 +896,16 @@
|
|||
$subscore = $step.find('.subscore');
|
||||
$watchVideo = $step.find('.watch-video');
|
||||
$startNetworkTestBtn.on('click', function () {
|
||||
forever = false;
|
||||
prepareNetworkTest();
|
||||
});
|
||||
if(context.JK.currentUserAdmin) {
|
||||
$foreverNetworkTestBtn.on('click', function() {
|
||||
forever = true;
|
||||
prepareNetworkTest();
|
||||
}).show();
|
||||
}
|
||||
|
||||
operatingSystem = context.JK.GetOSAsString();
|
||||
|
||||
initializeVideoWatchButton();
|
||||
|
|
|
|||
|
|
@ -342,6 +342,11 @@
|
|||
margin-top: 20px;
|
||||
}
|
||||
|
||||
a.forever-network-test {
|
||||
margin-top:20px;
|
||||
display:none;
|
||||
}
|
||||
|
||||
.network-test-score {
|
||||
height: 24px;
|
||||
padding: 10px;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
%p Then click on the Start Network Test button below.
|
||||
.center
|
||||
%a.button-orange.start-network-test{href:'#'} START NETWORK TEST
|
||||
%br
|
||||
%a.button-orange.forever-network-test{href:'#'} THE FOREVER TEST (ADMIN ONLY)
|
||||
.wizard-step-column
|
||||
%h2 Test Results
|
||||
.network-test-results.ftue-box
|
||||
|
|
|
|||
Loading…
Reference in New Issue