Compare commits

..

6 Commits

Author SHA1 Message Date
Seth Call cef44bef4c web tests repaired, and one ruby test 2021-02-18 20:16:14 -06:00
Seth Call 7e8b8112a4 missing admin 2021-02-14 23:47:16 -06:00
Seth Call e23bf2e446 typo 2021-02-14 23:36:46 -06:00
Seth Call 55ef4766c4 add migration 2021-02-14 23:33:33 -06:00
Seth Call d05b5df019 Add missing model 2021-02-14 23:12:46 -06:00
Seth Call b3f4539b42 Working on Affiliate Changes -- have updated account and links page 2021-02-14 22:32:27 -06:00
1844 changed files with 37456 additions and 208920 deletions

View File

@ -1,25 +0,0 @@
name: Build Admin
on:
push:
branches:
- develop
jobs:
build:
runs-on: dagger
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Install Dagger
run: |
curl -L https://dl.dagger.io/dagger/install.sh | sh
sudo mv bin/dagger /usr/local/bin/
- name: Login to Gitea Registry
run: echo "${{ gitea.token }}" | docker login git.staging.jamkazam.com -u ${{ gitea.actor }} --password-stdin
- name: Build and Publish with Dagger
working-directory: ./admin
run: |
dagger call build-local --source=. --repo-root=../ publish --address=git.staging.jamkazam.com/seth/jam-cloud-admin:latest

View File

@ -1,41 +0,0 @@
name: Environment Orchestrator
on: [push]
jobs:
orchestrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Component Deployment Gatekeeper
run: |
# JAM_CLUSTER_ENV should be set to 'staging' or 'production' in the Gitea Runner
ENV="${JAM_CLUSTER_ENV:-staging}"
echo "🌐 Cluster Environment: $ENV"
# 1. Extract modes for this environment
ADMIN_MODE=$(jq -r ".environments.$ENV.admin" .jk-deploy.json)
WEB_MODE=$(jq -r ".environments.$ENV.web" .jk-deploy.json)
WS_MODE=$(jq -r ".environments.$ENV.[\"websocket-gateway\"]" .jk-deploy.json)
# 2. Conditional Execution
if [ "$ADMIN_MODE" == "short-circuit" ]; then
echo "⚡ ADMIN: Short-circuit detected. Deploying immediately..."
cd admin && dagger call ship --source=.
else
echo "⏸️ ADMIN: Mode is $ADMIN_MODE. Skipping short-circuit deploy."
fi
if [ "$WEB_MODE" == "short-circuit" ]; then
echo "⚡ WEB: Short-circuit detected. Deploying immediately..."
cd web && dagger call ship --source=.
else
echo "⏸️ WEB: Mode is $WEB_MODE. Skipping short-circuit deploy."
fi
if [ "$WS_MODE" == "short-circuit" ]; then
echo "⚡ WS-GATEWAY: Short-circuit detected. Deploying immediately..."
cd websocket-gateway && just ship
else
echo "⏸️ WS-GATEWAY: Mode is $WS_MODE. Skipping short-circuit deploy."
fi

View File

@ -1,8 +0,0 @@
name: Test Runner
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "Runner is working!"

4
.gitignore vendored
View File

@ -8,7 +8,3 @@ HTML
coverage
dump.rdb
working.png
ruby/.rails5-gems
web/.rails5-gems
websocket-gateway/.rails5-gems
.pg_data/

View File

@ -1 +0,0 @@
2.4.1

3
admin/.gitignore vendored
View File

@ -21,6 +21,3 @@ artifacts
.idea
BUILD_NUMBER
# Gemfile.lock
Gemfile.alt.lock
.byebug_history
.ruby-version

7
admin/.rakeTasks Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
2.4.1

View File

@ -94,12 +94,7 @@ gem 'eventmachine', '1.2.3'
gem 'amqp', '0.9.8'
#gem 'logging-rails', :require => 'logging/rails'
#gem 'pg_migrate', '0.1.14'
if ENV["MODERN_OS"] == "1"
gem 'pg', '0.21.0'
else
# on mac, bundle config build.pg --with-cflags="-Wno-error=implicit-function-declaration"
gem 'pg', '0.17.1', :platform => [:mri, :mswin, :mingw]
end
gem 'pg', '0.17.1', :platform => [:mri, :mswin, :mingw]
gem 'ruby-protocol-buffers', '1.2.2'
gem 'sendgrid', '1.2.0'
gem 'geokit-rails'

View File

@ -1,172 +0,0 @@
source 'http://rubygems.org'
source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/'
ruby_version = ENV["JAM_RUBY_VERSION"]
ruby_version = "2.3.1" if ruby_version.nil?
ruby ruby_version
devenv = ENV["BUILD_NUMBER"].nil?
if devenv
#gem 'jam_db', :path=> "../db/target/ruby_package"
gem 'jampb', :path => "../pb/target/ruby/jampb"
gem 'jam_ruby', :path => "../ruby"
else
source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/' do
#gem 'jam_db', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jampb', "0.1.#{ENV["BUILD_NUMBER"]}"
gem 'jam_ruby', "0.1.#{ENV["BUILD_NUMBER"]}"
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true"
end
end
gem 'activeadmin_addons'
gem 'json', '1.8.6'
gem 'rails', '= 4.2.8'
gem 'protected_attributes'
gem 'rails-observers'
# PINNED TO SUPPORT BOTH 2.3.1 and 2.4.1 Ruby simultaneously
# This should be the same in ruby/admin/web/websocket-gateway
#######
gem 'nokogiri', '1.10.10' # added to pin before 1.11.0, which requires at leaost ruby 2.5. unpin we go above
gem 'sprockets', '3.6.3' # added to pin before 4.0.0, which requiurse at least ruby 2.5
gem 'fog-brightbox', '0.11.0' # pinned until we are on ruby 2.5; then remove
gem 'faraday', '0.9.2' # pinned untnil we are on ruby 2.5; then remove
gem 'ruby-prof', '0.15.9' # pinned until we are on ruby 2.5; then remove
gem 'rubyzip', '1.2.1' # pinned until we are on ruby 2.5; then remove
gem 'recurly', '2.18.16' # should upgrade to 3.x when we have time to validaate
gem 'icalendar', '2.4.0' # pinned until we are on ruby 2.5; then remove
gem 'email_validator', '1.6.0' # pinned until we are on ruby 2.5, then remove
gem 'redis', '3.3.3' # pinned until we are on 2.5; then remove
gem 'redis-namespace', '1.5.3' # pinned until we are on 2.5; then remove
gem 'oj', '3.1.3' # pinned until we are on 2.5; then remove
gem 'bcrypt', '3.1.15'
gem 'sass-rails', '5.0.7' # compiler mismatch issue between build and www
gem 'sass', '3.5.5 '# compiler mismatch issue between build and www
#######
gem 'bootstrap-sass', '2.0.4'
gem 'coffee-rails' #, '~> 3.2.1'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', :platforms => :ruby
gem 'kickbox'
gem 'uglifier' #, '>= 1.0.3'
gem 'net-ssh'
gem 'sprockets-rails', '2.3.2'
# this version is pinned due to this: https://github.com/gregbell/active_admin/issues/1939
gem 'coffee-script-source' #, '~> 1.4.0' # ADD THIS LINE, 1.5.0 doesn't compile ActiveAdmin JavaScript files
gem 'devise' #, '3.3.0'
gem 'will_paginate' #, '3.0.3'
gem 'bootstrap-will_paginate', '0.0.6'
gem 'carrierwave', '0.11.2' #, '0.9.0'
gem 'carrierwave_direct'
gem 'uuidtools', '2.1.2'
gem 'jquery-ui-rails'# , '5.0.5' #, '4.2.1'
gem 'jquery-rails'# , '4.1.1' # both this and jquery-ui-rails are pinned; if you unpin, jquery/autocomplete is missing during precomplie
gem 'rails-jquery-autocomplete' # This is the maintained version of rails3-jquery-autocomplete
gem 'activeadmin' #, '1.0.0.pre4'# github: 'activeadmin', branch: 'master'
gem 'activeadmin-searchable_select'
gem 'mime-types', '1.25'
#gem 'meta_search'
gem 'fog'
gem 'xmlrpc'
gem 'unf', '0.1.3' #optional fog dependency
gem 'country-select'
gem 'aasm' #, '3.0.16'
gem 'postgres-copy', '0.6.0'
gem 'aws-sdk', '~> 1'
gem 'bugsnag', '5.3.2'
gem 'gon'
gem 'cocoon'
gem 'haml-rails'
gem 'resque'
gem 'resque-retry'
gem 'resque-failed-job-mailer'
gem 'resque-lonely_job', '~> 1.0.0'
gem 'eventmachine', '1.2.3'
gem 'amqp', '0.9.8'
#gem 'logging-rails', :require => 'logging/rails'
#gem 'pg_migrate', '0.1.14'
if ENV["MODERN_OS"] == "1"
gem 'pg', '0.21.0'
else
# on mac, bundle config build.pg --with-cflags="-Wno-error=implicit-function-declaration"
gem 'pg', '0.17.1', :platform => [:mri, :mswin, :mingw]
end
gem 'ruby-protocol-buffers', '1.2.2'
gem 'sendgrid', '1.2.0'
gem 'geokit-rails'
gem 'postgres_ext' #, '1.0.0'
gem 'resque_mailer'
gem 'rest-client'
gem 'iso-639'
gem 'sanitize'
gem 'slim'
#gem 'influxdb', '0.1.8'
#gem 'influxdb-rails', '0.1.10'
gem 'influxdb', '0.3.14'
gem 'influxdb-rails', '0.1.12'
gem 'sendgrid_toolkit', '>= 1.1.1'
gem 'stripe'
gem 'zip-codes'
gem 'best_in_place' #, github: 'bernat/best_in_place'
gem 'auto_strip_attributes', '2.6.0'
gem 'elasticsearch'
gem 'logging', '1.7.2'
#group :libv8 do
# gem 'libv8', "~> 4.5.95"
#end
# To use Jbuilder templates for JSON
# gem 'jbuilder'
group :production do
gem 'unicorn'
end
group :package do
gem 'fpm'
end
# Deploy with Capistrano
# gem 'capistrano'
# To use debugger
#gem 'debugger' # not working with 2.1.2p95
group :development, :test do
gem 'capybara', '2.13.0'
gem 'rspec-rails' #, '2.14.2'
gem 'jasmine', '1.3.1'
gem 'execjs', '1.4.0'
#gem 'therubyracer' #, '0.11.0beta8'
gem 'factory_girl_rails' # , '4.1.0'
gem 'database_cleaner' #, '0.7.0'
gem 'launchy', '2.4.3' # can unpin when go to ruby 2.4+
gem 'faker', '1.3.0'
gem 'puma'
gem 'test-unit'
end
group :test do
gem 'simplecov', '~> 0.7.1'
gem 'simplecov-rcov'
# gem 'capybara-webkit'
# gem 'capybara-screenshot', '0.3.22' # 1.0.0 broke compat with rspec. maybe we need newer rspec
# gem 'poltergeist'
end
gem 'pry'
gem 'pry-remote'
gem 'pry-stack_explorer'
#gem 'pry-debugger'

View File

@ -793,7 +793,7 @@ DEPENDENCIES
zip-codes
RUBY VERSION
ruby 2.4.1p111
ruby 2.3.1p112
BUNDLED WITH
1.17.3

View File

@ -1,161 +0,0 @@
module AdCampaignsHelper
def self.spacer(val, total)
percentage = ((val * 100) / total.to_f).round(1).to_s
('%-5.5s' % percentage).gsub(' ', ' ') + '% - ' + val.to_s
end
def self.cac(campaign)
if campaign.subscribed && campaign.subscribed > 0
(campaign.spend/campaign.subscribed.to_f).round(2)
end
end
def self.cac_divide_by_ltv(campaign)
customer_ltv = GenericState.singleton.customer_ltv
if cac(campaign) && customer_ltv && customer_ltv > 0
return (cac(campaign)/customer_ltv.to_f).round(2)
end
end
def self.format_number(num)
if num
num.to_s.reverse.scan(/\d{3}|.+/).join(",").reverse
end
end
end
ActiveAdmin.register JamRuby::AdCampaign, as: 'AdCampaign' do
menu :label => 'Ad Campaigns', :parent => 'Reports'
before_filter :skip_sidebar!, :only => :index
config.batch_actions = false
config.clear_action_items!
config.sort_order = "users.origin_utm_campaign DESC"
config.per_page = 100
config.paginate = true
index do
div do
render 'customer_ltv'
end
column "Campaign" do |campaign|
campaign.origin_utm_campaign
end
column "Medium" do |campaign|
campaign.origin_utm_medium
end
column "End Date" do |campaign|
best_in_place campaign, :end_date, as: :date, url: inplace_update_admin_ad_campaigns_path(campaign: campaign.origin_utm_campaign, medium: campaign.origin_utm_medium), param: 'ad_campaign', classes: 'ac_bip'
end
column "Hard Date" do |campaign|
(campaign.end_date + 45.days).strftime('%Y-%m-%d') if campaign.end_date.present?
end
column "Subscribed" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.subscribed, campaign.joined))
end
column "Spend" do |campaign|
best_in_place campaign, :spend, as: :input, url: inplace_update_admin_ad_campaigns_path(campaign: campaign.origin_utm_campaign, medium: campaign.origin_utm_medium), param: 'ad_campaign', display_with: Proc.new{|spend| number_to_currency(spend) }, classes: 'ac_bip'
end
column "CAC" do |campaign|
number_to_currency(AdCampaignsHelper.cac(campaign)) if AdCampaignsHelper.cac(campaign) && AdCampaignsHelper.cac(campaign) > 0
end
column "LTV/CAC" do |campaign|
AdCampaignsHelper.cac_divide_by_ltv(campaign)
end
column "Referred" do |campaign|
best_in_place campaign, :referred, as: :input, url: inplace_update_admin_ad_campaigns_path(campaign: campaign.origin_utm_campaign, medium: campaign.origin_utm_medium), param: 'ad_campaign', display_with: Proc.new{|referred| AdCampaignsHelper.format_number(referred) }, classes: 'ac_bip'
end
column "Signed Up" do |campaign|
if campaign.referred && campaign.referred > 0
raw(AdCampaignsHelper.spacer(campaign.joined, campaign.referred))
else
raw("? - #{campaign.joined}")
end
end
column "Downloaded" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.downloaded, campaign.joined))
end
column "Ran Client" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.ran_client, campaign.joined))
end
column "FTUE" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.ftue, campaign.joined))
end
column "Any Session" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.any_session, campaign.joined))
end
column "2+ Session" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.real_session, campaign.joined))
end
column "Good Session" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.good_session, campaign.joined))
end
column "Invited" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.invited, campaign.joined))
end
column "Friended" do |campaign|
raw(AdCampaignsHelper.spacer(campaign.friended, campaign.joined))
end
column "Platinum" do |campaign|
campaign.platinum
end
column "Gold" do |campaign|
campaign.gold
end
column "Silver" do |campaign|
campaign.silver
end
end
controller do
def scoped_collection
User.select("users.origin_utm_campaign,
users.origin_utm_medium, COUNT(users.id) AS joined,
COUNT(users.first_downloaded_client_at) AS downloaded,
COUNT(users.first_subscribed_at) AS subscribed,
COUNT(users.first_ran_client_at) AS ran_client,
COUNT(users.first_certified_gear_at) AS ftue,
COUNT(users.first_music_session_at) AS any_session,
COUNT(users.first_real_music_session_at) AS real_session,
COUNT(users.first_good_music_session_at) AS good_session,
COUNT(users.first_invited_at) AS invited,
COUNT(users.first_friended_at) AS friended,
COUNT(CASE WHEN users.first_subscribed_plan_code = 'jamsubplatinum' OR users.first_subscribed_plan_code = 'jamsubplatinumyearly' THEN users.first_subscribed_plan_code END) AS platinum,
COUNT(CASE WHEN users.first_subscribed_plan_code = 'jamsubgold' OR users.first_subscribed_plan_code = 'jamsubgoldyearly' THEN users.first_subscribed_plan_code END) AS gold,
COUNT(CASE WHEN users.first_subscribed_plan_code = 'jamsubsilver' OR users.first_subscribed_plan_code = 'jamsubsilveryearly' THEN users.first_subscribed_plan_code END) AS silver,
ad_campaigns.id,
COALESCE(MAX(ad_campaigns.referred), NULL) as referred,
COALESCE(MAX(ad_campaigns.end_date), NULL) AS end_date,
COALESCE(MAX(ad_campaigns.spend), 0) AS spend").joins("
LEFT JOIN ad_campaigns ON users.origin_utm_campaign = ad_campaigns.campaign
AND users.origin_utm_medium = ad_campaigns.medium").where("
users.origin_utm_campaign IS NOT NULL AND users.origin_utm_medium IS NOT NULL AND users.origin_utm_source = 'paid'").group("
ad_campaigns.id, users.origin_utm_campaign, users.origin_utm_medium").order("
users.origin_utm_campaign DESC")
end
def permitted_params
params.permit :campaign, :medium, :_method, ad_campaign: [:spend, :referred, :end_date]
end
end
collection_action :inplace_update, method: :put do
campaign = permitted_params[:campaign]
medium = permitted_params[:medium]
@ad_campaign = JamRuby::AdCampaign.where(campaign: campaign, medium: medium).first_or_create
respond_to do |format|
if @ad_campaign.update_attributes(permitted_params[:ad_campaign])
format.json { head :ok }
else
format.json{ render :json => @ad_campaign.errors.full_messages, :status => :unprocessable_entity }
end
end
end
end

View File

@ -1,43 +0,0 @@
# module AdCampaignsHelper
# def campaign_brought_in_users(campaign, medium)
# User.where(origin_utm_campaign: campaign, origin_utm_medium: medium)
# end
# end
# ActiveAdmin.register JamRuby::AdCampaign do
# permit_params :campaign, :medium, :spend
# end
# ActiveAdmin.register_page "Ad campaigns" do
# menu parent: 'Reports'
# content :title => "Paid Advertising Report" do
# table_for User.select("users.origin_utm_campaign, users.origin_utm_medium, COALESCE(MAX(ad_campaigns.end_date), NULL) AS end_date, COALESCE(MAX(ad_campaigns.spend), NULL) AS spend").joins("LEFT JOIN ad_campaigns ON users.origin_utm_campaign = ad_campaigns.campaign AND users.origin_utm_medium = ad_campaigns.medium").group("ad_campaigns.id, users.origin_utm_campaign, users.origin_utm_medium") do
# column "Campaign" do |campaign|
# campaign.origin_utm_campaign
# end
# column "Medium" do |campaign|
# campaign.origin_utm_medium
# end
# column "End Date" do |campaign|
# campaign.end_date
# end
# column "Hard Date" do |campaign|
# campaign.end_date + 45.days if campaign.end_date.present?
# end
# column "Subscribed" do |campaign|
# end
# column "Spend" do |campaign|
# best_in_place campaign, :spend, as: :input, url: admin_ad_campaigns_update_path(campaign: campaign.origin_utm_campaign, medium: campaign.origin_utm_medium), param: 'ad_campaign'
# end
# end
# end
# page_action :update, method: :put do
# campaign = params[:campaign]
# medium = params[:medium]
# ad_campaign = AdCampaign.where(campaign: campaign, medium: medium).first_or_initialize
# ad_campaign.attributes = params["ad_campaign"]
# ad_campaign.save!
# respond_with_bip(ad_campaign)
# end
# end

View File

@ -1,108 +0,0 @@
class AffiliateCohortsHelper
def self.percentage(opTop, opBottom)
"#{(opTop/opBottom * 100).round(1)}%"
end
def self.quarter(date)
case date.month
when 1, 2, 3 then 0
when 4, 5, 6 then 1
when 7, 8, 9 then 2
when 10, 11, 12 then 3
end
end
def self.payments_for_months(affiliate_partner, year, start_month, end_month)
JamRuby::AffiliateMonthlyPayment.where(
"affiliate_partner_id = ? AND month >= ? AND month <= ? AND year = ?",
affiliate_partner.id,
start_month,
end_month,
year
).order('month DESC')
end
def self.payments_for_quarter(affiliate_partner, year, quarter)
JamRuby::AffiliateQuarterlyPayment.where(
"affiliate_partner_id = ? AND quarter = ? AND year = ?",
affiliate_partner.id,
quarter,
year
).order('quarter DESC')
end
def self.all_time_payments(affiliate_partner)
JamRuby::AffiliateQuarterlyPayment.where(
"affiliate_partner_id = ?", affiliate_partner.id
)
end
def self.current_quarter_payments(affiliate_partner)
AffiliateCohortsHelper.payments_for_quarter(affiliate_partner,
Date.today.year,
AffiliateCohortsHelper.quarter(Date.today)
)
end
def self.current_quarter_monthly_payments(affiliate_partner)
AffiliateCohortsHelper.payments_for_months(affiliate_partner,
Date.today.beginning_of_quarter.year,
Date.today.beginning_of_quarter.month,
Date.today.end_of_quarter.month
)
end
def self.prior_quarter_payments(affiliate_partner)
prev_quarter_start = (Date.today.beginning_of_quarter - 1.day).beginning_of_quarter
prev_quarter = AffiliateCohortsHelper.quarter(prev_quarter_start)
AffiliateCohortsHelper.payments_for_quarter(affiliate_partner,
prev_quarter_start.year,
prev_quarter
)
end
def self.prior_quarter_payable_amount(affiliate_partner)
total = AffiliateCohortsHelper.prior_quarter_payments(affiliate_partner).inject(0.0){ | sum, payment |
sum += payment.due_amount_in_cents }
paid = AffiliateCohortsHelper.prior_quarter_payments(affiliate_partner).where(paid: false).inject(0.0){ | sum, payment |
sum += payment.due_amount_in_cents }
(total - paid) / 100.0
end
end
ActiveAdmin.register_page "Affiliate Cohorts" do
menu parent: 'Reports'
content :title => "Affiliate Cohorts" do
table_for AffiliatePartner.includes(:partner_user).all do
column 'Affiliate Name' do |partner|
link_to partner.partner_name, admin_affiliate_path(partner)
end
column 'Affiliate ID', :id
column 'Affiliate Email', Proc.new{ | partner | partner&.partner_user&.email}
column 'Affiliate Paypal', Proc.new{| partner | partner.paypal_id }
column 'All Time Users', :referral_user_count
column 'All Time Subscribers', Proc.new{ | partner | partner.subscribed_user_referrals.size }
column 'All Time Subscriber Conversion Rate', Proc.new{ | partner |
AffiliateCohortsHelper.percentage(partner.subscribed_user_referrals.size.to_f, partner.referral_user_count.to_f) }
column 'All Time Revenues', Proc.new{ | partner |
number_to_currency(AffiliateCohortsHelper.all_time_payments(partner).inject(0.0){ | sum, payment | sum += payment.due_amount_in_cents } / 100.0)
}
column 'Current Quarter Revenues', Proc.new{ | partner |
number_to_currency(AffiliateCohortsHelper.current_quarter_payments(partner).inject(0.0){ | sum, payment | sum += payment.due_amount_in_cents } / 100.0 )
}
column 'Current Quarter Revenues by Month', Proc.new{ | partner |
AffiliateCohortsHelper.current_quarter_monthly_payments(partner).each do |monthly_payment|
li "#{Date::MONTHNAMES[monthly_payment.month]} #{monthly_payment.year} - #{number_to_currency(monthly_payment.due_amount_in_cents.to_f / 100.0)}"
end
''.html_safe
}
column 'Prior Quarter Revenues', Proc.new{ | partner |
number_to_currency(AffiliateCohortsHelper.prior_quarter_payments(partner).inject(0.0){ | sum, payment | sum += payment.due_amount_in_cents } / 100.0)
}
column 'Prior Quarter Payable', Proc.new{ | partner |
number_to_currency(AffiliateCohortsHelper.prior_quarter_payable_amount(partner))
}
end
end
end

View File

@ -2,7 +2,7 @@ ActiveAdmin.register JamRuby::AffiliateQuarterlyPayment, :as => 'Affiliate Quart
menu :label => 'Quarterly Reports', :parent => 'Affiliates'
config.sort_order = 'year desc, quarter desc, due_amount_in_cents desc'
config.sort_order = 'due_amount_in_cents DESC'
config.batch_actions = false
config.clear_action_items!
config.filters = true
@ -14,32 +14,18 @@ ActiveAdmin.register JamRuby::AffiliateQuarterlyPayment, :as => 'Affiliate Quart
filter :quarter
filter :closed
filter :paid
filter :jamtracks_sold
filter :subscriptions_count
filter :due_amount_in_cents
form :partial => 'form'
scope("Sorted By Due Amount", default: true) { |scope| scope.order('year desc, quarter desc, due_amount_in_cents desc') }
scope("Sorted By Jamtracks Sold", default: false) { |scope| scope.order('year desc, quarter desc, jamtracks_sold desc') }
scope("Sorted By Subs", default: false) { |scope| scope.order('year desc, quarter desc, subscriptions_count desc') }
scope("Sorted By Newest First") { |scope| scope.order('year desc, quarter desc, id desc') }
scope("Any") { |scope| scope.order('year desc, quarter desc, due_amount_in_cents desc') }
index do
# default_actions # use this for all view/edit/delete links
column 'Year' do |oo| oo.year end
column 'Quarter' do |oo| oo.quarter end
column 'Partner Id' do |oo| oo.affiliate_partner.id end
column 'Partner' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end
column "Tot ($)" do |oo| sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f) end
column "Sub ($)" do |oo| sprintf("$%.2f", oo.subscription_due_amount_in_cents.to_f / 100.to_f) end
column "Jam ($)" do |oo| sprintf("$%.2f", oo.jamtrack_due_amount_in_cents.to_f / 100.to_f) end
column 'JamTracks' do |oo| oo.jamtracks_sold end
column 'Subscriptions' do |oo| oo.subscriptions_count end
column "Due (\u00A2)" do |oo| oo.due_amount_in_cents end
column 'JamTracks Sold' do |oo| oo.jamtracks_sold end
column 'Paid' do |oo| oo.paid end
column 'Closed' do |oo| oo.paid end

View File

@ -23,9 +23,7 @@ ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Sta
# default_actions # use this for all view/edit/delete links
column 'Day' do |oo| oo.day end
column 'Partner ID' do |oo| oo.affiliate_partner.id end
column 'Partner Name' do |oo| oo.affiliate_partner.display_name end
column 'Partner User' do |oo| link_to(oo.affiliate_partner.partner_user.name, admin_user_path(oo.affiliate_partner.partner_user.id), { :title => oo.affiliate_partner.partner_user.name }) end
column 'Partner' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end
column 'Signups' do |oo| oo.signups end
column 'Visits' do |oo| oo.visits end
@ -33,16 +31,6 @@ ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Sta
controller do
def scoped_collection
rel = end_of_association_chain
.includes([:affiliate_partner])
.order('day DESC')
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
qq = ['affiliate_partner_id = ?', ref_id]
else
qq = ['affiliate_partner_id IS NOT NULL']
end
@users ||= rel.where(qq)
end
end
end

View File

@ -8,32 +8,27 @@ ActiveAdmin.register JamRuby::User, :as => 'Referrals' do
config.filters = true
filter :affiliate_referral
filter :email
## scope("Has Signups", default: true) { |scope| scope.where('visits != 0 or signups != 0').order('day desc') }
index do
column 'User' do |oo| link_to(oo.name, oo.admin_url, {:title => oo.name}) end
column 'User Email' do |oo| oo.email end
column 'Email' do |oo| oo.email end
column 'Created' do |oo| oo.created_at end
column 'Partner ID' do |oo| oo.affiliate_referral.id end
column 'Partner Name' do |oo| oo.affiliate_referral.display_name end
column 'Partner User' do |oo| link_to(oo.affiliate_referral.partner_user.name, admin_user_path(oo.affiliate_referral.partner_user.id), { :title => oo.affiliate_referral.partner_user.name }) end
column 'Partner' do |oo| oo.affiliate_referral.display_name end
end
controller do
def scoped_collection
rel = end_of_association_chain
.includes([:affiliate_referral])
.order('created_at DESC')
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
qq = ['affiliate_referral_id = ?', ref_id]
else
qq = ['affiliate_referral_id IS NOT NULL']
def scoped_collection
rel = end_of_association_chain
.includes([:affiliate_referral])
.order('created_at DESC')
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
qq = ['affiliate_referral_id = ?', ref_id]
else
qq = ['affiliate_referral_id IS NOT NULL']
end
@users ||= rel.where(qq)
end
@users ||= rel.where(qq)
end
end
end

View File

@ -5,42 +5,21 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
config.sort_order = 'referral_user_count DESC'
config.batch_actions = false
# config.clear_action_items!
config.filters = true
config.per_page = 100
config.filters = false
config.per_page = 50
config.paginate = true
#form :partial => 'form'
#filter :partner_user
filter :partner_name
filter :id
filter :current_quarter_in_cents
filter :cumulative_earnings_in_cents
filter :jamtracks_sold
filter :subscriptions_count
filter :referral_user_count
scope("Sorted By Current Quarter", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('current_quarter_in_cents desc') }
scope("Sorted By Jamtracks Sold", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('jamtracks_sold desc') }
scope("Sorted By Subs", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('subscriptions_count desc') }
scope("Sorted By Signups", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
scope("Sorted By Newest First") { |scope| scope.where('partner_user_id IS NOT NULL').order('id desc') }
scope("Any") { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
scope("Unpaid") { |partner| partner.unpaid }
controller do
helper 'active_admin/subscription'
end
form do |f|
f.inputs 'Fields' do
f.input(:partner_name, :input_html => { :maxlength => 128 })
f.input(:partner_user, as: :searchable_select, hint: 'This person is the owner of the affiliate. Has access to reporting info in account section of www.jamkazam.com')
f.input(:entity_type, :as => :select, :collection => AffiliatePartner::ENTITY_TYPES)
f.input(:rate)
f.input(:paypal_id)
end
f.actions
end
@ -61,47 +40,14 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
column 'Code' do |oo|
oo.id
end
column 'Signups' do |oo|
column 'Referral Count' do |oo|
oo.referral_user_count
end
column 'JamTracks' do |oo|
oo.jamtracks_sold
end
column 'Subs' do |oo|
oo.subscriptions_count
end
column 'Cum Earnings' do |oo|
div do
sprintf("Tot $%.2f", oo.cumulative_earnings_in_dollars)
end
div do
sprintf("Jam $%.2f", oo.jamtrack_cumulative_earnings_in_dollars)
end
div do
sprintf("Sub $%.2f", oo.subscriptions_cumulative_earnings_in_dollars)
end
end
column 'Current Quarter' do |oo|
div do
sprintf("Tot $%.2f", oo.current_quarter_in_dollars)
end
div do
sprintf("Jam $%.2f", oo.jamtrack_current_quarter_in_dollars)
end
div do
sprintf("Sub $%.2f", oo.subscriptions_current_quarter_in_dollars)
end
column 'Earnings' do |oo|
sprintf("$%.2f", oo.cumulative_earnings_in_dollars)
end
column 'Amount Owed' do |oo|
div do
sprintf("Tot $%.2f", oo.due_amount_in_cents.to_f / 100.to_f)
end
div do
sprintf("Jam $%.2f", oo.jamtrack_due_amount_in_cents.to_f / 100.to_f)
end
div do
sprintf("Sub $%.2f", oo.subscription_due_amount_in_cents.to_f / 100.to_f)
end
sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f)
end
column 'Pay Actions' do |oo|
link_to('Mark Paid', mark_paid_admin_affiliate_path(oo.id), :confirm => "Mark this affiliate as PAID?") if oo.unpaid
@ -110,32 +56,6 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
actions
end
show do |affiliate_partner|
attributes_table do
row :id
row :partner_name
row :entity_type
row :rate
row :address
row :tax_identifier
row :paypal_id
row :venmo_user_id
row :jamtracks_sold
row :subscriptions_count
row :cumulative_earnings_in_dollars
row :jamtrack_cumulative_earnings_in_dollars
row :subscriptions_cumulative_earnings_in_dollars
row :current_quarter_in_dollars
row :jamtrack_current_quarter_in_dollars
row :subscriptions_current_quarter_in_dollars
end
render 'earnings', { affiliate_partner: affiliate_partner }
end
action_item :only => [:show] do
link_to("Mark Paid",
mark_paid_admin_affiliate_path(resource.id),

View File

@ -1,21 +0,0 @@
ActiveAdmin.register JamRuby::AppFeature, as: 'App Features' do
menu parent: 'Misc', label: 'App Features'
config.sort_order = 'created_at ASC'
config.batch_actions = false
config.filters = false
config.per_page = 50
config.paginate = true
form do |f|
f.inputs 'Fields' do
f.input(:feature_type, as: :select, collection: JamRuby::AppFeature::FEATURE_TYPES)
f.input(:handle, :input_html => { :maxlength => 1025 })
f.input(:is_enabled, as: :boolean)
f.input(:env, as: :select, collection: %w(production staging development))
end
f.actions
end
end

View File

@ -3,7 +3,7 @@ ActiveAdmin.register JamRuby::GenericState, :as => 'GenericState' do
config.clear_action_items!
filter :env
permit_params :top_message, :event_page_top_logo_url, :customer_ltv, :connection_policy
permit_params :top_message, :event_page_top_logo_url, :connection_policy
actions :all, :except => [:destroy]

View File

@ -23,116 +23,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
form :partial => "form"
action_item :school_user, only: :index do
link_to "Quick Add School User", add_school_user_admin_users_path
end
action_item :latency, only: :show do
link_to 'Show Latency', latency_admin_user_path(resource)
end
collection_action :add_school_user, method: [:get, :post] do
if request.post?
@client = RecurlyClient.new
errors = []
user_params = params[:jam_ruby_user]
user_type = user_params[:user_type].strip
school_id = user_params[:school_id].strip
return (render text: "Please select school") if school_id.blank?
first_name = user_params[:first_name].strip
last_name = user_params[:last_name].strip
email = user_params[:email].strip.downcase
license_start = user_params[:license_start].strip
license_end = user_params[:license_end].strip
import_source = user_params[:import_source].strip
password = SecureRandom.uuid
options = {
first_name: first_name,
last_name: last_name,
email: email,
license_start: license_start,
license_end: license_end,
import_source: import_source,
terms_of_service: true,
musician: true,
skip_recaptcha: true,
password: password,
password_confirmation: password
}
options.merge!({ school_id: school_id }) if school_id.present?
instrument = Instrument.find('electric guitar')
instruments = [{instrument_id: instrument.id, proficiency_level: 3, priority: 1}]
options[:instruments] = instruments
parse_user_type(user_type, options)
@user = User.find_by_email(options[:email])
unless @user.nil?
@user.user_type = user_type
#if @user.import_source.nil?
#TODO: do we need to have following 3 lines for an existing user?
@user.import_source = options[:import_source]
@user.license_start = options[:license_start]
@user.license_end = options[:license_end]
if options[:student]
@user.school_id = options[:school_id]
@user.is_a_student = true
elsif options[:teacher]
@user.school = school
if @user.teacher.nil?
@user.teacher = Teacher.build_teacher(@user, validate_introduction: true, biography: "Empty biography", school_id: school.id)
end
elsif options[:platform_instructor]
@user.is_platform_instructor = true
end
if @user.save
@client.sync_subscription(@user)
if options[:student]
UserMailer.school_welcome_message(@user, nil).deliver_now
elsif options[:teacher]
UserMailer.school_welcome_message(@user, nil).deliver_now
elsif options[:platform_instructor]
end
end
if @user.errors.any?
flash[:error] = "Error updating User #{@user.name} #{@user.email}"
render :add_school_user
else
flash[:notice] = "User #{@user.name} #{@user.email} updated successfully"
redirect_to admin_users_path
end
#end
else
@user = User.signup(options)
if @user.errors.none?
@client.sync_subscription(@user)
puts "User #{@user.email} created"
flash[:notice] = "User #{@user.name} #{@user.email} added successfully"
redirect_to admin_users_path
else
flash[:error] = "Error adding school user"
render :add_school_user
end
end
else
@user = User.new
@user.import_source = 'Manual'
end
end
member_action :update_school_user, method: [:get, :put] do
end
member_action :delete_forever, :method => :get do
resource.permanently_delete
redirect_to :back, {notice: 'User email and login credentials have been permanently changed'}
@ -230,28 +120,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
end
member_action :latency, method: :get do
@user = resource
latency_url = "#{Rails.application.config.latency_data_host}/user/#{@user.id}"
uri = URI(latency_url)
begin
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://")
req = Net::HTTP::Get.new(uri.request_uri)
req["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}"
req["Content-Type"] = "application/json"
response = http.request(req)
render text: "404 Not Found", status: 404 if response.is_a?(Net::HTTPNotFound)
#render json: response.body, status: 200 if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess)
@user_latency = response.body
rescue => e
render text: e.message
end
end
show do |user|
panel "Common" do
@ -475,55 +344,35 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
end
end
panel "School" do
div do
attributes_table do
row "School" do |user|
span do
if user.school
link_to user.school.name, admin_school_path(user.school), target: '_blank'
end
end
end
if user.school
row :license_start
row :license_end
row :is_platform_instructor
panel "Onboarding" do
attributes_table do
row :onboarding_status
row "Support Consultant" do |user|
if user.onboarder
link_to "#{user.onboarder.name} (#{user.onboarder.onboarding_users.count})", user.onboarder.admin_url
else
end
end
row "Signup" do
user.created_at.to_date
end
row "Assigned", :onboarder_assigned_at
row "Email 1", :onboarding_email_1_sent_at
row "Email 2", :onboarding_email_2_sent_at
row "Email 3", :onboarding_email_3_sent_at
row "Email 4", :onboarding_email_4_sent_at
row "Email 5", :onboarding_email_5_sent_at
row "Test Session Scheduled Time", :onboarding_test_session_at
row "When Test Session Was Requested", :onboarding_test_session_at
row "Test Session Outcome", :onboarding_test_session_outcome
row "Notes", :onboarding_onboarder_notes
row "Lost Reason", :onboarding_lost_reason
row "Lost At", :onboarding_lost_at
row "Escalated Reason", :onboarding_escalation_reason
row "Escalated At", :onboarding_escalated_at
end
end
# panel "Onboarding" do
# attributes_table do
# row :onboarding_status
# row "Support Consultant" do |user|
# if user.onboarder
# link_to "#{user.onboarder.name} (#{user.onboarder.onboarding_users.count})", user.onboarder.admin_url
# else
# end
# end
# row "Signup" do
# user.created_at.to_date
# end
# row "Assigned", :onboarder_assigned_at
# row "Email 1", :onboarding_email_1_sent_at
# row "Email 2", :onboarding_email_2_sent_at
# row "Email 3", :onboarding_email_3_sent_at
# row "Email 4", :onboarding_email_4_sent_at
# row "Email 5", :onboarding_email_5_sent_at
# row "Test Session Scheduled Time", :onboarding_test_session_at
# row "When Test Session Was Requested", :onboarding_test_session_at
# row "Test Session Outcome", :onboarding_test_session_outcome
# row "Notes", :onboarding_onboarder_notes
# row "Lost Reason", :onboarding_lost_reason
# row "Lost At", :onboarding_lost_at
# row "Escalated Reason", :onboarding_escalation_reason
# row "Escalated At", :onboarding_escalated_at
# end
# end
=begin
panel "Teacher Setting" do
@ -637,15 +486,10 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
controller do
# this actually searches on first name, last name, and email, because of get_autocomplete_items defined below
autocomplete :user, :email, :full => true, :display_value => :autocomplete_display_name, extra_data: [:last_jam_addr]
autocomplete :user, :email, :full => true, :display_value => :autocomplete_display_name
def get_autocomplete_items(parameters)
term = parameters[:term]
if term.include?('@')
User.select("email, first_name, last_name, id, last_jam_addr").where(["email = ?", term]).limit(5)
else
User.select("email, first_name, last_name, id, last_jam_addr").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{term}%", "%#{term}%", "%#{term}%"]).limit(40)
end
User.select("email, first_name, last_name, id").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
end
@ -667,6 +511,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
@user = resource
@user.email = params[:jam_ruby_user][:email]
@user.admin = params[:jam_ruby_user][:admin]
@user.is_onboarder = params[:jam_ruby_user][:is_onboarder]
@user.subscribe_email = params[:jam_ruby_user][:subscribe_email]
@user.musician = params[:jam_ruby_user][:musician]
@user.first_name = params[:jam_ruby_user][:first_name]
@ -703,97 +548,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
redirect_to edit_admin_user_path(@user)
end
def user_latency_recommendation
recommendation_params = params[:latency_recommendation]
offset = recommendation_params[:offset].blank?? 0 : recommendation_params[:offset]
limit = recommendation_params[:limit].blank?? 20 : recommendation_params[:limit]
is_valid = [
recommendation_params[:my_user_id],
recommendation_params[:my_public_ip]
].all?{ |param|
param.present?
}
if is_valid
instruments = []
instruments << recommendation_params[:instruments_beginner].reject(&:blank?).map{|i| { id: i, proficiency: 1} }
instruments << recommendation_params[:instruments_advanced].reject(&:blank?).map{|i| { id: i, proficiency: 2} }
instruments << recommendation_params[:instruments_expert].reject(&:blank?).map{|i| { id: i, proficiency: 3} }
instruments.flatten!
genres = recommendation_params[:genres].reject(&:blank?)
wifi = recommendation_params[:wifi]
max_latency = recommendation_params[:max_latency]
data = {
my_user_id: recommendation_params[:my_user_id],
my_public_ip: recommendation_params[:my_public_ip],
offset: offset,
limit: limit
}
data.merge!({query: recommendation_params[:query]}) unless recommendation_params[:query].blank?
data.merge!({instruments: instruments}) unless instruments.empty?
data.merge!({genres: genres}) unless genres.empty?
data.merge!({wifi: wifi}) if %w(true false).include?(wifi)
data.merge!({max_latency: max_latency}) unless max_latency.blank?
latency_url = "#{Rails.application.config.latency_data_host}/recommendations"
uri = URI(latency_url)
begin
http = Net::HTTP.new(uri.host, uri.port)
http.read_timeout = 5
http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://")
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
request.body = data.to_json
request["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}"
response = http.request(request)
@latency_recommendation = response.body
rescue => exception
render text: exception.message
end
else
flash[:error] = 'Please provide all required fields'
redirect_to admin_latency_recommendation_path
end
end
def user_latencies
latency_params = params[:latencies]
latency_url = "#{Rails.application.config.latency_data_host}/user_latencies"
uri = URI(latency_url)
if not ([latency_params[:my_user_id], latency_params[:my_public_ip], latency_params[:user_1_id]]).any?{|param| param.blank? }
user_ids = [latency_params[:user_1_id]]
user_ids.push(latency_params[:user_2_id]) unless latency_params[:user_2_id].blank?
user_ids.push(latency_params[:user_3_id]) unless latency_params[:user_3_id].blank?
user_ids.push(latency_params[:user_4_id]) unless latency_params[:user_4_id].blank?
user_ids.push(latency_params[:user_5_id]) unless latency_params[:user_5_id].blank?
begin
http = Net::HTTP.new(uri.host, uri.port)
http.read_timeout = 5
http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://")
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
request.body = {
my_user_id: latency_params[:my_user_id],
my_public_ip: latency_params[:my_public_ip],
users: user_ids
}.to_json
request["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}"
response = http.request(request)
@user_latencies = response.body
#render text: JSON.pretty_generate(@user_latencies)
rescue => e
render text: e.message
end
else
flash[:error] = 'Please provide all required fields'
redirect_to admin_latency_between_users_path
end
end
end
end

View File

@ -17,14 +17,6 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
filter :jam_track
controller do
def create
jt_params = params[:jam_ruby_jam_track_right]
jt_params[:jam_track] =JamRuby::JamTrack.where("id=?", jt_params[:jam_track_id_val]).first # jt_params[:jam_track_id_val]
jt_params[:user] = JamRuby::User.where("id=?", jt_params[:user_id_val]).first # jt_params[:user_id_val]
create!
end
end
index do
actions
@ -55,14 +47,9 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
form do |f|
f.inputs 'New Jam Track Right' do
#f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
f.input :jam_track_id_val, :required=>true, :as => :hidden
f.input :jam_track, :required=>true, :as => :autocomplete, :url => autocomplete_jam_track_name_admin_jam_tracks_path, hint: 'Select a jamtrack to give to this user'
#f.input :user, :required=>true, collection: User.all, include_blank: false
f.input :user_id_val, :required=>true, :as => :hidden
f.input :user, :required=>true, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, hint: 'Give a free jamtrack to this user'
f.input :can_download, :required => true, as: :boolean, :input_html => { :checked => 'checked' }
f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
f.input :user, :required=>true, collection: User.all, include_blank: false
f.input :can_download, :required => true, as: :boolean
end
f.actions
end

View File

@ -1,7 +1,5 @@
ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
collection_action :autocomplete_jam_track_name, :method => :get
menu :label => 'JamTracks', :parent => 'JamTracks'
config.sort_order = 'name_asc'
@ -21,19 +19,6 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
form :partial => 'form'
controller do
# this actually searches on first name, last name, and email, because of get_autocomplete_items defined below
autocomplete :jam_track, :name, :full => true, :display_value => :autocomplete_display_name
def get_autocomplete_items(parameters)
JamTrack.select("name, original_artist, id").where(["name ILIKE ? OR original_artist ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
end
end
index do
# actions # use this for all view/edit/delete links

View File

@ -1,185 +0,0 @@
class Spacer
def self.spacer(val, row)
percentage = ((val * 100) / row.total.to_f).round(1).to_s
('%-5.5s' % percentage).gsub(' ', '&nbsp;') + '%&nbsp;-&nbsp;' + val.to_s
end
end
=begin
select
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
select first_name, last_name, email
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
AND first_music_session_at is NULL;
=end
ActiveAdmin.register_page "Jammers Subscription Cohorts" do
menu :parent => 'Reports'
content :title => "Jammers Subscription Cohorts" do
filter_type = params[:filter_type] || 'All'
filter_campaign = params[:filter_campaign]
filter_campaign_id = params[:filter_campaign_id]
filter_ad_set = params[:filter_ad_set]
filter_ad_name = params[:filter_ad_name]
campaigns = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_utm_campaign).compact.sort
campaign_ids = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_id).compact.sort
ad_sets = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_term).compact.sort
ad_names = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_content).compact.sort
div style: "margin-bottom: 20px; padding: 10px; background-color: #f4f4f4; border-radius: 4px;" do
form action: admin_jammers_subscription_cohorts_path, method: :get do
span "Source: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_type', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: 'All', selected: filter_type == 'All'
option "Organic", value: 'Organic', selected: filter_type == 'Organic'
option "Advertising", value: 'Advertising', selected: filter_type == 'Advertising'
end
if filter_type == 'Advertising'
div style: "margin-top: 10px;" do
span "Campaign Name: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_campaign', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: ''
option "Null", value: 'NULL', selected: filter_campaign == 'NULL'
campaigns.each do |c|
option c, value: c, selected: filter_campaign == c
end
end
end
div style: "margin-top: 10px;" do
span "Campaign ID: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_campaign_id', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: ''
option "Null", value: 'NULL', selected: filter_campaign_id == 'NULL'
campaign_ids.each do |c|
option c, value: c, selected: filter_campaign_id == c
end
end
end
div style: "margin-top: 10px;" do
span "Ad Set: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_ad_set', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: ''
option "Null", value: 'NULL', selected: filter_ad_set == 'NULL'
ad_sets.each do |c|
option c, value: c, selected: filter_ad_set == c
end
end
div style: "margin-top: 10px;" do
span "Ad Name: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_ad_name', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: ''
option "Null", value: 'NULL', selected: filter_ad_name == 'NULL'
ad_names.each do |c|
option c, value: c, selected: filter_ad_name == c
end
end
end
end
end
noscript { input type: :submit, value: "Filter" }
end
end
h2 "Users Grouped By Month as Paying Subscribers"
query = User.select(%Q{date_trunc('month', users.created_at) as month,
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed,
count(first_played_jamtrack_at) as played_jamtrack
})
.joins(%Q{LEFT JOIN LATERAL (
SELECT
j.created_at
FROM
jam_track_rights as j
WHERE
j.user_id = users.id
ORDER BY
j.created_at
LIMIT 1 -- Select only that single row
) j ON TRUE })
if filter_type == 'Organic'
query = query.where("users.origin_utm_source = 'organic'")
elsif filter_type == 'Advertising'
query = query.where("origin_utm_medium = 'cpc'")
if filter_campaign.present?
if filter_campaign == 'NULL'
query = query.where("users.origin_utm_campaign IS NULL")
elsif filter_campaign != 'All'
query = query.where("users.origin_utm_campaign = ?", filter_campaign)
end
end
if filter_campaign_id.present?
if filter_campaign_id == 'NULL'
query = query.where("users.origin_id IS NULL")
elsif filter_campaign_id != 'All'
query = query.where("users.origin_id = ?", filter_campaign_id)
end
end
if filter_ad_set.present?
if filter_ad_set == 'NULL'
query = query.where("users.origin_term IS NULL")
elsif filter_ad_set != 'All'
query = query.where("users.origin_term = ?", filter_ad_set)
end
end
if filter_ad_name.present?
if filter_ad_name == 'NULL'
query = query.where("users.origin_content IS NULL")
elsif filter_ad_name != 'All'
query = query.where("users.origin_content = ?", filter_ad_name)
end
end
end
table_for query.group("date_trunc('month', users.created_at)")
.where("j.created_at IS NULL OR (j.created_at - users.created_at) >= INTERVAL '2 hours'")
.order("date_trunc('month', users.created_at) DESC") do |row|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
column "Total", :total
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
end
end
end

View File

@ -1,73 +0,0 @@
class Spacer
def self.spacer(val, row)
percentage = ((val * 100) / row.total.to_f).round(1).to_s
('%-5.5s' % percentage).gsub(' ', '&nbsp;') + '%&nbsp;-&nbsp;' + val.to_s
end
end
=begin
select
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
select first_name, last_name, email
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
AND first_music_session_at is NULL;
=end
ActiveAdmin.register_page "JamTrack Subscription Cohorts" do
menu :parent => 'Reports'
content :title => "JamTrack Subscription Cohorts" do
h2 "Users Grouped By Month as Paying Subscribers"
table_for User.select(%Q{date_trunc('month', users.created_at) as month,
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed,
count(first_played_jamtrack_at) as played_jamtrack
})
.joins(%Q{INNER JOIN LATERAL (
SELECT
j.created_at
FROM
jam_track_rights as j
WHERE
j.user_id = users.id
ORDER BY
j.created_at
LIMIT 1 -- Select only that single row
) j ON (j.created_at - users.created_at) < INTERVAL '2 hours' })
.group("date_trunc('month', users.created_at)").order("date_trunc('month', users.created_at) DESC") do |row|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
column "Total", :total
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
end
end
end

View File

@ -0,0 +1,41 @@
class Spacer
def self.spacer(val, row)
percentage = ((val * 100) / row.total.to_f).round(1).to_s
('%-5.5s' % percentage).gsub(' ', '&nbsp;') + '%&nbsp;-&nbsp;' + val.to_s
end
end
ActiveAdmin.register_page "Subscription Cohorts" do
menu :parent => 'Reports'
content :title => "Subscription Cohorts" do
h2 "Users Grouped By Month as Paying Subscribers"
table_for User.select(%Q{date_trunc('month', created_at) as month,
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(recurly_subscription_id) as subscribed
}).group("date_trunc('month', created_at)").order("date_trunc('month', created_at) DESC") do |row|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
column "Total", :total
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
end
end
end

View File

@ -1,7 +0,0 @@
ActiveAdmin.register_page "Latency Between Users" do
menu parent: 'Users'
content :title => "Latency Between Users" do
render 'admin/users/users_latency_form'
end
end

View File

@ -1,7 +0,0 @@
ActiveAdmin.register_page "Latency Recommendation" do
menu parent: 'Users'
content :title => "Latency Recommentation" do
render 'admin/users/latency_recommendation_form'
end
end

View File

@ -6,18 +6,15 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
config.batch_actions = false
config.clear_action_items!
config.filters = false
config.per_page = 250
scope("Paid", default: true) { |scope| scope.unscoped.where(:origin_utm_medium => 'cpc').order('created_at desc') }
scope("Inorganic Source") { |scope| scope.unscoped.where("origin_utm_source != 'organic' OR origin_utm_source IS NULL").order('created_at desc') }
scope("Include Organic") { |scope| scope.unscoped.order('created_at desc') }
scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
index do
column "Email" do |user|
user.email
end
column "Signup (CST)" do |user|
user.created_at.in_time_zone("Central Time (US & Canada)")
column "Bought TestDrive" do |user|
!user.most_recent_test_drive_purchase.nil? ? "Yes" : "No"
end
column "UTM Source" do |user|
user.origin_utm_source
@ -28,23 +25,8 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
column "UTM Campaign" do |user|
user.origin_utm_campaign
end
column "UTM ID" do |user|
user.origin_id
end
column "UTM Term" do |user|
user.origin_term
end
column "UTM Content" do |user|
user.origin_content
end
column "Referrer" do |user|
user.origin_referrer
end
column "FB Click ID" do |user|
user.facebook_click_id
end
column "FB Browser ID" do |user|
user.facebook_browser_id
end
end
end

View File

@ -13,11 +13,8 @@
// //= require autocomplete-rails
//= require base
//= require_tree .
//= require best_in_place.jquery-ui
$(document).ready(function() {
jQuery(".best_in_place").best_in_place();
$.datepicker.setDefaults({
dateFormat: 'yy-mm-dd',
});
jQuery(".best_in_place").best_in_place()
})

View File

@ -1,3 +0,0 @@
$(document).ready(function() {
jQuery(".ac_bip").bind("ajax:success", function(){ window.location.reload(); });
})

View File

@ -1,110 +0,0 @@
function intToIP(int) {
var part1 = int & 255;
var part2 = ((int >> 8) & 255);
var part3 = ((int >> 16) & 255);
var part4 = ((int >> 24) & 255);
return part4 + "." + part3 + "." + part2 + "." + part1;
}
function handleJamTrackRightsForm() {
var $jamTrackRights = $('form#new_jam_ruby_jam_track_right');
var $jamTrack = $jamTrackRights.find('#jam_ruby_jam_track_right_jam_track_id');
var $jamTrackVal = $jamTrackRights.find('#jam_ruby_jam_track_right_jam_track_id_val')
var $user = $jamTrackRights.find('#jam_ruby_jam_track_right_user_id');
var $userVal = $jamTrackRights.find('#jam_ruby_jam_track_right_user_id_val');
$jamTrack.on('change', function(){
console.log("change jam track");
});
/**
$user.on('change', function(){
console.log("change user");
});
$jamTrack.on('focus', function(){
$userVal.val('')
});*/
$jamTrack.bind('railsAutocomplete.select', function(event, data){
$jamTrackVal.val('');
$jamTrackVal.val(data.item.id);
console.log("jam track selected with id " + data.item.id);
});
$user.bind('railsAutocomplete.select', function(event, data){
$userVal.val('');
$userVal.val(data.item.id);
console.log("user selected with id " + data.item.id);
});
}
function handleUserLatencyForm(){
var $userLatenciesForm = $('form#user_latencies_form');
var $latenciesMyUser = $userLatenciesForm.find('#latencies_my_user');
var $latenciesMyUserId = $userLatenciesForm.find('#latencies_my_user_id');
var $latenciesMyPublicIp = $userLatenciesForm.find('#latencies_my_public_ip');
$latenciesMyUser.on('focus', function(){
$latenciesMyUserId.val('')
});
$latenciesMyUser.bind('railsAutocomplete.select', function(event, data){
$latenciesMyUserId.val('');
$latenciesMyPublicIp.val('');
if(data.item.last_jam_addr){
var ipAddr = intToIP(data.item.last_jam_addr);
$latenciesMyPublicIp.val(ipAddr);
}
$latenciesMyUserId.val(data.item.id);
});
$userLatenciesForm.find('#latencies_user_1').bind('railsAutocomplete.select', function(event, data){
$userLatenciesForm.find('#latencies_user_1_id').val(data.item.id);
});
$userLatenciesForm.find('#latencies_user_2').bind('railsAutocomplete.select', function(event, data){
$userLatenciesForm.find('#latencies_user_2_id').val(data.item.id);
});
$userLatenciesForm.find('#latencies_user_3').bind('railsAutocomplete.select', function(event, data){
$userLatenciesForm.find('#latencies_user_3_id').val(data.item.id);
});
$userLatenciesForm.find('#latencies_user_4').bind('railsAutocomplete.select', function(event, data){
$userLatenciesForm.find('#latencies_user_4_id').val(data.item.id);
});
$userLatenciesForm.find('#latencies_user_5').bind('railsAutocomplete.select', function(event, data){
$userLatenciesForm.find('#latencies_user_5_id').val(data.item.id);
});
}
function handleLatencyRecommendationForm(){
var $latencyRecommendationForm = $('form#latency_recommendation_form');
var $latenciesMyUser = $latencyRecommendationForm.find('#latency_recommendation_my_user');
var $latenciesMyUserId = $latencyRecommendationForm.find('#latency_recommendation_my_user_id');
var $latenciesMyPublicIp = $latencyRecommendationForm.find('#latency_recommendation_my_public_ip');
$latenciesMyUser.on('focus', function(){
$latenciesMyUserId.val('')
});
$latenciesMyUser.bind('railsAutocomplete.select', function(event, data){
$latenciesMyUserId.val('');
$latenciesMyPublicIp.val('');
if(data.item.last_jam_addr){
var ipAddr = intToIP(data.item.last_jam_addr);
$latenciesMyPublicIp.val(ipAddr);
}
$latenciesMyUserId.val(data.item.id);
});
}
$(document).ready(function() {
handleUserLatencyForm();
handleLatencyRecommendationForm();
handleJamTrackRightsForm();
});

View File

@ -2,97 +2,49 @@ class ArsesController < ApplicationController
respond_to :json
def index
if params[:code] != Rails.application.config.data_dump_code
render :json => {error: "Unauthorized"}, :status => 401
return
end
@arses = JamRuby::Ars.all
render :json => @arses
end
def update
if params[:code] != Rails.application.config.data_dump_code
render :json => {error: "Unauthorized"}, :status => 401
return
end
begin
# Primary ID lookup
@ars = JamRuby::Ars.find_by_id(params[:id])
# Explicit secondary lookups if primary ID fails
@ars ||= JamRuby::Ars.find_by_id_int(params[:id_int]) if params[:id_int]
@ars ||= JamRuby::Ars.find_by_name(params[:name]) if params[:name]
if @ars.nil?
render :json => {error: "Not Found"}, :status => 404
return
end
allowed = [:password, :username, :active, :beta, :name, :provider, :id_int, :ip, :port, :continent, :country, :city, :subdivision, :latitude, :longitude]
update_hash = {}
allowed.each do |attr|
update_hash[attr] = params[attr] if params.has_key?(attr)
end
if @ars.update_attributes(update_hash, as: :admin)
render :json => @ars, :status => :ok
else
render :json => @ars.errors, :status => :unprocessable_entity
end
rescue => e
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500
end
end
# create or update a client_artifact row
def get_or_create
begin
name = params[:name]
provider = params[:provider]
active = params[:active]
beta = params.has_key?(:beta) ? params[:beta] : true
ip = params[:ip]
username = params[:username]
password = params[:password]
topology = params[:topology]
ars_id = params[:ars_id]
name = params[:name]
provider = params[:provider]
active = params[:active]
ip = params[:ip]
username = params[:username]
password = params[:password]
topology = params[:topology]
ars_id = params[:ars_id]
puts "TOPOLOGY #{topology}"
# Explicit field-based lookups
ars = nil
ars = JamRuby::Ars.find_by_id_int(ars_id) if ars_id
ars ||= JamRuby::Ars.find_by_name(name) if name
if ars.nil?
ars = JamRuby::Ars.new
ars.name = name
end
ars.id_int = ars_id if !ars_id.nil?
ars.provider = provider
ars.active = active
ars.beta = params[:beta]
ars.beta = beta
ars.ip = ip
ars.password = password
ars.username = username
if topology
ars.city = topology['city']
ars.country = topology['country']
ars.continent = topology['continent']
ars.latitude = topology['latitude']
ars.longitude = topology['longitude']
ars.subdivision = topology['subdivision']
end
ars.save!
@ars = ars
render :json => {id_int: @ars.id_int, id: @ars.id, name: @ars.name, provider: @ars.provider, active: @ars.active, beta: @ars.beta, ip: @ars.ip}, :status => :ok
rescue => e
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500
if ars_id
ars = Ars.find_by_id_int(ars_id)
end
if ars.nil?
ars = Ars.new
ars.name = name
end
ars.provider = provider
ars.active = active
ars.ip = ip
ars.password = password
ars.username = username
if topology
ars.city = topology['city']
ars.country = topology['country']
ars.continent = topology['continent']
ars.latitude = topology['latitude']
ars.longitude = topology['longitude']
ars.subdivision = topology['subdivision']
end
ars.save
@ars = ars
unless @ars.errors.any?
@ars = Ars.find_by_name(name)
render :json => {id_int: @ars.id_int, id: @ars.id, name: @ars.name, provider: @ars.provider, active: @ars.active, ip: @ars.ip}, :status => :ok
else
response.status = :unprocessable_entity
respond_with @ars
end
end
end

View File

@ -1,20 +0,0 @@
module ActiveAdmin
module SubscriptionHelper
def subscription_plan_name(code)
case code
when 'jamrubysilver'
'Silver'
when 'jamrubygold'
'Gold'
when 'jamrubyplatinum'
'Platinum'
when 'jamsubgoldyearly'
'Gold Yearly'
when 'jamsubsilveryearly'
'Silver Yearly'
when 'jamsubplatinumyearly'
'Platinum Yearly'
end
end
end
end

View File

@ -39,4 +39,5 @@ module ApplicationHelper
end
end

View File

@ -112,19 +112,6 @@ class Cohort < ActiveRecord::Base
def self.cohort_users(cohort)
User.where(created_at: cohort.group_start..cohort.group_end)
end
=begin
SELECT played.user_id FROM
(SELECT user_id, COUNT(*) cnt FROM music_sessions_user_history msuh1
WHERE
msuh1.created_at >= '2024-11-01' AND
msuh1.created_at <= '202' 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
=end
def _played_online_subquery(constraint)
where = if constraint.is_a?(Range)

View File

@ -1,4 +0,0 @@
<%= label_tag :customer_ltv, 'Customer LTV : $' %>
<%= best_in_place GenericState.singleton, :customer_ltv, :as => :input, url: "#{ENV['RAILS_RELATIVE_URL_ROOT']}/admin/generic_states/#{GenericState.singleton.id}", place_holder: "---", :ok_button => 'Save', :cancel_button => 'Cancel', classes: 'ac_bip' %>
<br />
<br />

View File

@ -1,42 +0,0 @@
<h3>Earnings by Month</h3>
<table class="index_table index">
<thead>
<tr>
<th>Month</th>
<th>JamTracks</th>
<th>Subscriptions</th>
<th>Affiliate Earnings</th>
</tr>
</thead>
<tbody>
<% AffiliateMonthlyPayment.index(affiliate_partner.partner_user, {})[0].each do |payment| %>
<tr>
<td>
<%= Date::MONTHNAMES[payment.month] if payment.month %>
<%= payment.year %>
</td>
<td>
<% if payment.jamtracks_sold > 0 %>
JamTracks: <%= pluralize payment.jamtracks_sold, 'unit' %>
<% end %>
</td>
<td>
<%
month_start = Date.new(payment.year, payment.month, 1)
month_end = Date.new(payment.year, payment.month, 1).end_of_month
AffiliateDistribution.subscription_plans_count(affiliate_partner.id, month_start, month_end).each do |plan_count|
%>
<div>
<%= subscription_plan_name(plan_count[:plan]) -%>: <%= pluralize(plan_count.count, 'unit') -%> sold
</div>
<%
end
%>
</td>
<td>
<%= number_to_currency(payment.due_amount_in_cents.to_f/100.0) %>
</td>
</tr>
<% end %>
</tbody>
</table>

View File

@ -2,6 +2,7 @@
= f.inputs "Details" do
= f.input :email, label: 'Email'
= f.input :admin
= f.input :is_onboarder, label: 'Is Support Consultant'
= f.input :subscribe_email, label: 'Subscribed to Emails?'
= f.input :is_platform_instructor, label: 'Is Platform Instructor?'
= f.input :gifted_jamtracks, label: 'JamTrack Credits'
@ -10,8 +11,6 @@
= f.input :city
= f.input :state
= f.input :musician
= f.input :beta
= f.input :use_video_conferencing_server
= f.inputs "Gear Mods" do
= f.input :show_frame_options, as: :boolean
= f.inputs "Do Not Shows" do

View File

@ -1,22 +0,0 @@
<%= semantic_form_for 'latency_recommendation', url: user_latency_recommendation_admin_users_path, html: {id: 'latency_recommendation_form'} do |f| %>
<%= f.inputs :name => 'Select user', :class => 'inputs' do %>
<%= f.input :my_user, :as => :autocomplete, url: autocomplete_user_email_admin_users_path, label: 'My user name/email' %>
<%= f.input :my_user_id, as: :hidden %>
<%= f.input :my_public_ip, label: 'My user public IP' %>
<% end %>
<%= f.inputs :name => 'Query parameters', :class => 'inputs' do %>
<%= f.input :query, required: false %>
<%= f.input :instruments_beginner, collection: JamRuby::Instrument.all, input_html: { multiple: true }, required: false %>
<%= f.input :instruments_advanced, collection: JamRuby::Instrument.all, input_html: { multiple: true }, required: false %>
<%= f.input :instruments_expert, collection: JamRuby::Instrument.all, input_html: { multiple: true }, required: false %>
<%= f.input :genres, as: :select, collection: JamRuby::Genre.all, input_html: { multiple: true }, required: false %>
<%= f.input :wifi, as: :select, collection: {'Any': 'any', 'Yes': true, 'No': false }, required: false %>
<%= f.input :max_latency, required: false %>
<%= f.input :offset, input_html: { value: 0 } %>
<%= f.input :limit, input_html: { value: 20 } %>
<% end %>
<%= f.actions do %>
<%= f.action :submit, :as => :button, label: 'Submit' %>
<%= f.action :cancel, :as => :link, label: 'Cancel' %>
<% end %>
<% end %>

View File

@ -1,23 +0,0 @@
<%= semantic_form_for 'latencies', url: user_latencies_admin_users_path, html: {id: 'user_latencies_form'} do |f| %>
<%= f.inputs :name => 'Select user', :class => 'inputs' do %>
<%= f.input :my_user, :as => :autocomplete, url: autocomplete_user_email_admin_users_path, label: 'My user name/email' %>
<%= f.input :my_user_id, as: :hidden %>
<%= f.input :my_public_ip, label: 'My user public IP' %>
<% end %>
<%= f.inputs :name => 'Select other users', :class => 'inputs' do %>
<%= f.input :user_1, :as => :autocomplete, url: autocomplete_user_email_admin_users_path, label: 'Other user 1' %>
<%= f.input :user_1_id, as: :hidden %>
<%= f.input :user_2, :as => :autocomplete, url: autocomplete_user_email_admin_users_path, label: 'Other user 2', required: false %>
<%= f.input :user_2_id, as: :hidden %>
<%= f.input :user_3, :as => :autocomplete, url: autocomplete_user_email_admin_users_path, label: 'Other user 3', required: false %>
<%= f.input :user_3_id, as: :hidden %>
<%= f.input :user_4, :as => :autocomplete, url: autocomplete_user_email_admin_users_path, label: 'Other user 4', required: false %>
<%= f.input :user_4_id, as: :hidden %>
<%= f.input :user_5, :as => :autocomplete, url: autocomplete_user_email_admin_users_path, label: 'Other user 5', required: false %>
<%= f.input :user_5_id, as: :hidden %>
<% end %>
<%= f.actions do %>
<%= f.action :submit, :as => :button, label: 'Submit' %>
<%= f.action :cancel, :as => :link, label: 'Cancel' %>
<% end %>
<% end %>

View File

@ -1,12 +0,0 @@
= semantic_form_for([:admin_users, @user], builder: ActiveAdmin::FormBuilder, :url => @user.new_record? ? add_school_user_admin_users_path : "/admin/users/#{@user.id}/add_school_user") do |f|
= f.inputs "Details" do
= f.input :email, label: 'Email', required: true
= f.input :first_name, required: true
= f.input :last_name, required: true
= f.input :license_start, as: :datepicker, required: true
= f.input :license_end, as: :datepicker, required: true
= f.input :import_source, required: true
= f.input :school_id, as: :select, collection: School.all, required: true
= f.input :user_type, as: :select, collection: ['Student', 'Student Instructor', 'Platform Instructor'], include_blank: false, required: true
= f.actions

View File

@ -1,2 +0,0 @@
<h2><%= @user.name %></h2>
<pre><%= debug @user_latency -%></pre>

View File

@ -1 +0,0 @@
<pre><%= JSON.pretty_generate(JSON.parse(@user_latencies)) -%></pre>

View File

@ -1 +0,0 @@
<pre><%= JSON.pretty_generate(JSON.parse(@latency_recommendation)) -%></pre>

View File

@ -1 +0,0 @@
BUNDLE_GEMFILE=Gemfile.alt RAILS_ENV=development LOCAL_DEV=1 MODERN_OS=1 JAM_RUBY_VERSION=2.4.1 bundle _1.17.3_ exec rails server -b 0.0.0.0 -p 3333

View File

@ -131,7 +131,7 @@ module JamAdmin
config.email_smtp_starttls_auto = true
config.verify_email_enabled = false
config.musician_count = '300,000+'
config.musician_count = '200,000+'
config.facebook_app_id = ENV['FACEBOOK_APP_ID'] || '468555793186398'
config.facebook_app_secret = ENV['FACEBOOK_APP_SECRET'] || '546a5b253972f3e2e8b36d9a3dd5a06e'
@ -169,8 +169,5 @@ module JamAdmin
config.download_tracker_day_range = 30
config.max_user_ip_address = 10
config.max_multiple_users_same_ip = 2
config.latency_data_host = "https://latency-production.naas.jamkazam.com"
config.latency_data_host_auth_code = "amFtQXBpOm5pNWY0eFRjRlpNc0lXNEd4IUR5cQ=="
end
end

View File

@ -46,7 +46,4 @@ JamAdmin::Application.configure do
config.email_generic_from = 'nobody-dev@jamkazam.com'
config.email_alerts_alias = 'alerts-dev@jamkazam.com'
config.email_social_alias = 'social-dev@jamkazam.com'
config.latency_data_host = "http://localhost:4001/local"
config.latency_data_host_auth_code = "c2VydmVyOnBhc3N3b3Jk"
end

View File

@ -1,18 +0,0 @@
class JamRuby::JamTrackRight
attr_accessible :jam_track, :user, :jam_track_id_val, :user_id_val, as: :admin
def jam_track_id_val
end
def jam_track_id_val=(val)
end
def user_id_val
end
def user_id_val=(val)
end
end

View File

@ -3,8 +3,4 @@ class JamRuby::JamTrack
# add a custom validation
def autocomplete_display_name
"#{original_artist} - #{name}"
end
end

View File

@ -14,15 +14,6 @@ JamAdmin::Application.routes.draw do
namespace :admin do
resources :users do
get :autocomplete_user_email, :on => :collection
get :add_school_user, on: :collection
post :add_school_user, on: :collection
patch :add_school_user, on: :member
post :user_latencies, on: :collection
post :user_latency_recommendation, on: :collection
end
resources :jam_tracks do
get :autocomplete_jam_track_name, :on => :collection
end
end
@ -44,8 +35,6 @@ JamAdmin::Application.routes.draw do
match '/api/jam_tracks/released' => 'jam_track#dump_released', :via => :get, as: 'released_jamtracks_csv'
match '/api/arses/register' => 'arses#get_or_create', :via => :post
match '/api/arses' => 'arses#index', :via => :get
match '/api/arses/:id' => 'arses#update', :via => :post
mount Resque::Server.new, :at => "/resque"

View File

@ -1 +0,0 @@
# trigger build

View File

@ -1,98 +0,0 @@
image: node:14.21.3
pipelines:
branches:
develop:
- step:
name: Build Staging
script:
- pushd jam-ui
- npm install
- popd
- pushd jam-ui/cicd
- npm install
- NODE_ENV=production PUBLIC_URL=https://staging.jamkazam.com REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_BASE_URL=https://staging.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT ENVIRONMENT=staging ./generate.sh
- popd
- cd jam-ui
- NODE_ENV=production CI=false PUBLIC_URL=https://staging.jamkazam.com REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com REACT_APP_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT npm run build
artifacts:
- jam-ui/build/**
- step:
name: Deploy to staging - SPA
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/stg"
LOCAL_PATH: "jam-ui/build"
EXTRA_ARGS: "--exclude=*backing-tracks/*"
- step:
name: Deploy to staging - backing-tracks
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/stg/backing-tracks"
LOCAL_PATH: "jam-ui/build/backing-tracks"
EXTRA_ARGS: "--exclude=*.js --content-type text/html"
- step:
name: Deploy to staging - backing-tracks js
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/stg/js"
LOCAL_PATH: "jam-ui/build/js"
EXTRA_ARGS: "--content-type text/javascript"
# - step:
# name: Deploy to staging - invalidate cloudfront distribution
# deployment: staging
# script:
# - pipe: atlassian/aws-cloudfront-invalidate:0.10.1
# variables:
# DISTRIBUTION_ID: "E2AQIC9RSON94Q" # ESQDIABYLT0RV
custom:
build-and-deploy-to-production:
- step:
name: Build Production
script:
- pushd jam-ui
- npm install
- popd
- pushd jam-ui/cicd
- npm install
- NODE_ENV=production ENVIRONMENT=production PUBLIC_URL=https://www.jamkazam.com REACT_APP_ORIGIN=jamkazam.com REACT_APP_BASE_URL=https://www.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB ./generate.sh
- popd
- cd jam-ui
- NODE_ENV=production CI=false PUBLIC_URL=https://www.jamkazam.com REACT_APP_ORIGIN=jamkazam.com REACT_APP_BASE_URL=https://www.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB npm run build
artifacts:
- jam-ui/build/**
- step:
name: Deploy to production - SPA
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/prd"
LOCAL_PATH: "jam-ui/build"
EXTRA_ARGS: "--exclude=*backing-tracks/*"
- step:
name: Deploy to production - backing-tracks
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/prd/backing-tracks"
LOCAL_PATH: "jam-ui/build/backing-tracks"
EXTRA_ARGS: "--exclude=*.js --content-type text/html"
- step:
name: Deploy to production - backing-tracks js
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/prd/js"
LOCAL_PATH: "jam-ui/build/js"
EXTRA_ARGS: "--content-type text/javascript"
#- step:
# name: Deploy to production - invalidate cloudfront distribution
# deployment: production
# script:
# - pipe: atlassian/aws-cloudfront-invalidate:0.10.1
# variables:
# DISTRIBUTION_ID: "ESQDIABYLT0RV"

1
build
View File

@ -1,6 +1,5 @@
#!/bin/bash
# RUN_SLOW_TESTS, RUN_AWS_TESTS, SKIP_KARMA=1 SHOW_JS_ERRORS=1 PACKAGE=1
# WORKSPACE=/var/lib/jenkins/jobs/jam-web/workspace

View File

@ -1,6 +0,0 @@
# Browsers that we support
last 1 version
> 0.2%
not op_mini all
not dead

View File

@ -1,16 +0,0 @@
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_ORIGIN=jamkazam.local
REACT_APP_BASE_URL=http://beta.jamkazam.local:4000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
REACT_APP_BITBUCKET_BUILD_NUMBER=dev
REACT_APP_BITBUCKET_COMMIT=dev
REACT_APP_ENV=development
REACT_APP_RECAPTCHA_ENABLED=false
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
PUBLIC_URL=
REACT_APP_COOKIE_DOMAIN=.jamkazam.local
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms

View File

@ -1,13 +0,0 @@
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_ORIGIN=jamkazam.local
REACT_APP_BASE_URL=http://beta.jamkazam.local:4000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
REACT_APP_BITBUCKET_BUILD_NUMBER=dev
REACT_APP_BITBUCKET_COMMIT=dev
REACT_APP_ENV=development
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
REACT_APP_RECURLY_PUBLIC_API_KEY=
REACT_APP_BRAINTREE_TOKEN=

View File

@ -1,13 +0,0 @@
HOST=beta.jamkazam.com
PORT=4000
REACT_APP_ORIGIN=jamkazam.com
REACT_APP_BASE_URL=https://www.jamkazam.com
REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com
REACT_APP_API_BASE_URL=https://www.jamkazam.com/api
REACT_APP_ENV=production
REACT_APP_RECAPTCHA_ENABLED=true
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
REACT_APP_BRAINTREE_TOKEN=production_hc7z69yq_pwwc6zm3d478kfrh

View File

@ -1,13 +0,0 @@
HOST=beta.staging.jamkazam.com
PORT=4000
REACT_APP_ORIGIN=staging.jamkazam.com
REACT_APP_BASE_URL=https://staging.jamkazam.com
REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com
REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api
REACT_APP_ENV=staging
REACT_APP_RECAPTCHA_ENABLED=false
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_COOKIE_DOMAIN=.staging.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-AjUHUfcLtIsPdtetD4mj2x
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms

View File

@ -1,8 +0,0 @@
{
"extends": ["react-app", "prettier", "plugin:react/recommended"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
"react/no-unescaped-entities": 0
}
}

37
jam-ui/.gitignore vendored
View File

@ -1,37 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.cypress.env.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.idea
/.vscode
/test-results
/cypress/videos/
/cypress/screenshots/
/public/backing-tracks
/public/js

View File

@ -1,31 +0,0 @@
# Using the node alpine image to build the React app
image: node:alpine
# Announce the URL as per CRA docs
# https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration
variables:
PUBLIC_URL: /react-falcon
# Cache node modules - speeds up future builds
cache:
paths:
- node_modules
# Name the stages involved in the pipeline
stages:
- deploy
# Job name for gitlab to recognise this results in assets for Gitlab Pages
# https://docs.gitlab.com/ee/user/project/pages/introduction.html#gitlab-pages-requirements
pages:
stage: deploy
script:
- npm install # Install all dependencies
- npm run build --prod # Build for prod
- mv public _public # CRA and gitlab pages both use the public folder. Only do this in a build pipeline.
- mv build public # Move build files to public dir for Gitlab Pages
- cp public/index.html public/404.html # Required for react router browser history, but helps with https://blog.pshrmn.com/how-single-page-applications-work/
artifacts:
paths:
- public # The built files for Gitlab Pages to serve
only:
- master # Only run on master branch

View File

@ -1 +0,0 @@
v14.21.3

View File

@ -1,4 +0,0 @@
{
"singleQuote": true,
"printWidth": 120
}

View File

@ -1 +0,0 @@
2.7.18

View File

@ -1,56 +0,0 @@
# JamKazam new react frontend UI/UX
`jam-ui` is a react app created using `create-react-app` utility. to run the app on your development environment you need to `cd jam-ui` and run `npm run start`
The changes to the source files are auto-loaded but sometimes you might need to force close it by crtl+c and then start manually.
The application files goes under `src` and the react components are placed under `src/components` directory. We have a convention of naming the files using JK prefix in the filename. For example `JKMusicSessions`.
The routes are defined in `jam-ui/src/components/dashboard/JKDashboardMain.js`
## Running react app
In production this React app is supposed to run on beta.jamkazam.com subdomain which is same origin domain to the production Rails app (www.jamkazam.com). This way we utilize same session based user authentication of Rails web app for authenticating users. (It looks for remember_token session cookie in headers and if it is not availale redirect the user to Rails web app sign in page)
The DOMAIN and PORT running this app is defined in env.production file. This file also has env variables for connecting with Rails app. (When setting up in development you can copy the content of env.development.example in to env.development.local and change them according to your host setup)
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
## Subdomains setup (development)
You need 2 host records created for React and and Rails app. For example
127.0.0.1 www.jamkazam.local #for Rails app
127.0.0.1 beta.jamkazam.local #for React app
## Installing npm dependencies
cd jam-ui
npm install
## Running the app
cd jam-ui
npm run start
This will open it in a borwser window at http://beta.jamkazam.local:3000. Of course for it to work you also need Rails (web) app and websocket app (websocket-gateway) running.
## Working with JamTracks
if you have the latest from develop, you can go:
```
cd cicd
npm install
./export_personal_jamtracks.sh
./generate.js
open http://beta.jamkazam.local:4000/backing-tracks/ac-dc/back-in-black.html
```
You can also do none of the above, and go straight to:
http://beta.jamkazam.local:4000/public/backing-tracks/ac-dc/back-in-black
I tried to make it so the SPA has 'secret routes' to these pages, which is convenient for us dev & testing
but also tried to make it convenient to run the cicd approach of actually generating separate pages for each landing page (which is what those 5 steps cover)

View File

@ -1,6 +0,0 @@
build
output
node_modules
public
jam_track_tracks_for_jam_ui*

View File

@ -1 +0,0 @@
22

View File

@ -1,7 +0,0 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"ignore": [
"../src/components/e-commerce/*.js"
]
}

View File

@ -1,29 +0,0 @@
import 'react-app-polyfill/ie9';
import 'react-app-polyfill/stable';
import React from "react";
import ReactDOM from "react-dom";
import Main from "../src/Main.js"
import TemplatePage from "../src/components/jamtracks/JKJamTracksLandingTemplatePage.js"
import ArtistTemplatePage from "../src/components/jamtracks/JKJamTracksArtistLandingTemplatePage.js"
import '../src/helpers/initFA';
import '../src/i18n/config';
const rootElement = document.getElementById("root");
// Ensure props are passed correctly (or fetch from the server)
const props = window.jamtrack_data;
console.log('init', props, rootElement);
// Hydrate the server-rendered React component
//ReactDOM.hydrate(React.createElement(TemplatePage, props), rootElement);
ReactDOM.render(
<Main>
{props.song ? <TemplatePage {...props} /> : <ArtistTemplatePage {...props} /> }
</Main>, rootElement
);

View File

@ -1,25 +0,0 @@
#!/bin/bash
# check if 1st argument spceified; if it is, then set that to SAVE_TO
if [ -n "$1" ]; then
SAVE_TO="$1"
else
SAVE_TO=/tmp
fi
echo "Saving to $SAVE_TO"
psql jam -c "COPY( select id, original_artist, name , original_artist_slug, name_slug, plan_code, slug, allow_free, ('https://www.jamkazam.com/backing-tracks/' || original_artist_slug || '/' || name_slug) as \"URL\", (select name from jam_track_licensors l where l.id = licensor_id) as \"Licensor\", vendor_id as \"Vendor ID\" FROM jam_tracks) TO '$SAVE_TO/jam_tracks_for_jam_ui.$USER.csv' with CSV HEADER;"
//https://jamkazam-public.s3.amazonaws.com
# dump all artists
psql jam -c "COPY( select original_artist, original_artist_slug, ('https://www.jamkazam.com/backing-tracks/' || original_artist_slug ) as \"URL\" FROM jam_tracks group by original_artist, original_artist_slug) TO '$SAVE_TO/jam_tracks_for_jam_ui_artists.$USER.csv' with CSV HEADER;"
psql jam -c "COPY( select id, part, instrument_id, (select description from instruments where id = instrument_id) as instrument_description, track_type, position, preview_mp3_url, preview_url as preview_ogg_url, preview_aac_url from jam_track_tracks) TO '$SAVE_TO/jam_track_tracks_for_jam_ui.$USER.csv' with CSV HEADER;"
echo "Moving personal files to jamtracks-for-env"
mkdir -p jamtracks-for-env
sudo mv $SAVE_TO/jam_tracks_for_jam_ui.$USER.csv jamtracks-for-env
sudo mv $SAVE_TO/jam_tracks_for_jam_ui_artists.$USER.csv jamtracks-for-env
sudo mv $SAVE_TO/jam_track_tracks_for_jam_ui.$USER.csv jamtracks-for-env

View File

@ -1,39 +0,0 @@
#!/bin/bash
# Ensure the correct number of arguments
if [ "$#" -lt 2 ]; then
echo "Usage: $0 <save_to_path> <server_env>"
exit 1
fi
SAVE_TO="$1"
server_env="$2"
# Validate server_env
if [ "$server_env" != "staging" ] && [ "$server_env" != "production" ]; then
echo "Error: server_env must be either 'staging' or 'production'"
exit 1
fi
# Determine SSH target
if [ "$server_env" == "staging" ]; then
SSH_TARGET="jam@int.jamkazam.com"
else
SSH_TARGET="jam@db.jamkazam.com"
fi
echo "Saving to $SAVE_TO on $server_env"
# Run psql commands remotely
ssh $SSH_TARGET "psql jam -c \"COPY( select id, original_artist, name, original_artist_slug, name_slug, plan_code, slug, allow_free, ('https://www.jamkazam.com/backing-tracks/' || original_artist_slug || '/' || name_slug) as \"URL\", (select name from jam_track_licensors l where l.id = licensor_id) as \"Licensor\" FROM jam_tracks order by id::int) TO '$SAVE_TO/jam_tracks_for_jam_ui.$server_env.csv' with CSV HEADER;\""
ssh $SSH_TARGET "psql jam -c \"COPY( select original_artist, original_artist_slug, ('https://www.jamkazam.com/backing-tracks/' || original_artist_slug ) as \"URL\" FROM jam_tracks group by original_artist, original_artist_slug order by original_artist) TO '$SAVE_TO/jam_tracks_for_jam_ui_artists.$server_env.csv' with CSV HEADER;\""
ssh $SSH_TARGET "psql jam -c \"COPY( select id, part, instrument_id, (select description from instruments where id = instrument_id) as instrument_description, track_type, position, preview_mp3_url, preview_url as preview_ogg_url, preview_aac_url from jam_track_tracks order by jam_track_id::int) TO '$SAVE_TO/jam_track_tracks_for_jam_ui.$server_env.csv' with CSV HEADER;\""
# Move files locally from the remote server
scp $SSH_TARGET:"$SAVE_TO/jam_tracks_for_jam_ui.$server_env.csv" jamtracks-for-env
scp $SSH_TARGET:"$SAVE_TO/jam_tracks_for_jam_ui_artists.$server_env.csv" jamtracks-for-env
scp $SSH_TARGET:"$SAVE_TO/jam_track_tracks_for_jam_ui.$server_env.csv" jamtracks-for-env
echo "Files moved successfully to local machine"

View File

@ -1,337 +0,0 @@
const fs = require("fs");
const path = require("path");
const csv = require("csv-parser");
const React = require("react");
const dotenv = require("dotenv");
const ReactDOMServer = require("react-dom/server");
const TemplatePageModule = require("./build/components/jamtracks/JKJamTracksLandingTemplatePage");
const ArtistTemplatePageModule = require("./build/components/jamtracks/JKJamTracksArtistLandingTemplatePage");
var csvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui.${process.env.USER}.csv`
var artistCsvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui_artists.${process.env.USER}.csv`
var sitemapPath = path.join(__dirname, "..", "public", "sitemap.xml");
const clear_sitemap = () => {
fs.writeFileSync(sitemapPath, "");
// Add the root element
fs.writeFileSync(sitemapPath, `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`, { flag: 'a' });
// Add the root url
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}</loc></url>\n`, { flag: 'a' });
// Add standard URLs specific to this site, such as:
// All prefix with /public
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/</loc></url>\n`, { flag: 'a' });
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/privacy</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/help</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/knowledge-base</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/help-desk</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/forum</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/unsubscribe</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/downloads</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/downloads-legacy</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/login</loc></url>\n`, { flag: 'a' });
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/signup</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/forget-password</loc></url>\n`, { flag: 'a' } );
// Add the closing root element
}
const add_song_to_sitemap = (artistSlug, songSlug) => {
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/backing-tracks/${artistSlug}/${songSlug}</loc></url>\n`, { flag: 'a' });
}
const add_artist_to_sitemap = (artistSlug) => {
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/backing-tracks/${artistSlug}</loc></url>\n`, { flag: 'a' });
}
const close_sitemap = () => {
fs.writeFileSync(sitemapPath, "</urlset>", { flag: 'a' } );
}
/**
* Loads a CSV file into an array of objects.
* @param {string} csvPath - The path to the CSV file.
* @returns {Promise<Array<Object>>} - A promise that resolves with the parsed CSV data.
*/
const load_csv = (csvPath) => {
return new Promise((resolve, reject) => {
const results = [];
fs.createReadStream(csvPath)
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', () => resolve(results))
.on('error', (error) => reject(error));
});
};
/**
* Finds all songs for a given artist based on the original_artist_slug
* and sorts them alphabetically by the `slug` field.
* @param {string} artistSlug - The original_artist_slug to match.
* @param {Array<Object>} songsCsv - The songs CSV data.
* @returns {Array<Object>} - A sorted array of matching song objects.
*/
const collect_songs_for_artist = (artistSlug, songsCsv) => {
return songsCsv
.filter((song) => song.original_artist_slug === artistSlug)
.sort((a, b) => a.slug.localeCompare(b.slug)); // Sort alphabetically by slug
};
const init = () => {
const node_env = process.env.NODE_ENV || 'development';
const environment = process.env.ENVIRONMENT || 'development';
console.log(`environment=${environment} node_env=${node_env}`);
console.log(dotenv.config({ path: `../.env.${environment}` }));
if (environment === "production" || environment === "staging") {
csvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui.${environment}.csv`;
artistCsvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui_artists.${environment}.csv`;
}
console.log("Song csv file", csvFilePath);
console.log("Artist csv file", artistCsvFilePath);
if (!process.env.PUBLIC_URL) {
console.log("setting public url", process.env.REACT_APP_BASE_URL);
process.env.PUBLIC_URL = process.env.REACT_APP_BASE_URL;
}
clear_sitemap();
//const __dirname = path.resolve(path.dirname(''));
console.log("init done successfully")
}
const generateSongPages = async (render) => {
const rows = [];
const OUTPUT_DIR = path.join(__dirname, "..", "public", "backing-tracks");
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
console.log("generatPages starting")
const TemplatePage = TemplatePageModule.default;
fs.createReadStream(csvFilePath)
.pipe(csv())
.on("data", (row) => rows.push(row))
.on("end", async () => {
console.log(`Processing ${rows.length} rows...`);
for (const row of rows) {
// id, original_artist, name, original_artist_slug, name_slug, plan_code, slug, URL, licensor, vendor_id
const { id, original_artist, name, original_artist_slug, name_slug, plan_code, slug, allow_free } = row;
const artist = original_artist;
const song = name;
const location = `/backing-tracks/${original_artist_slug}/${name_slug}`;
const fullPath = process.env.REACT_APP_BASE_URL + location;
const logoPath = process.env.REACT_APP_BASE_URL + "/favicon.svg";
add_song_to_sitemap(original_artist_slug, name_slug);
console.log(`Generating ${artist} - ${song}`);
const html = render
? ReactDOMServer.renderToStaticMarkup(
React.createElement(TemplatePage, { id, plan_code, slug, artist, song, location })
)
: "";
const fullHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="/favicon.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${artist} - ${song} - Free Backing Track</title>
<link rel="stylesheet" href="${process.env.REACT_APP_BASE_URL}/css/theme.css">
<meta name="description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta name="keywords" content="Backing Track, ${artist}, ${song}, Instrumental">
<meta name="author" content="JamKazam">
<!-- Open Graph (Facebook, LinkedIn, etc.) -->
<meta property="og:title" content="${artist} - ${song} | Free Backing Track">
<meta property="og:description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta property="og:image" content="${logoPath}">
<meta property="og:url" content="${fullPath}">
<meta property="og:type" content="music.song">
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${artist} - ${song} | Free Backing Track">
<meta name="twitter:description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta name="twitter:image" content="${logoPath}">
<!-- Canonical URL -->
<link rel="canonical" href="${fullPath}" />
<!-- Structured Data (Schema.org) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "MusicRecording",
"name": "${song}",
"byArtist": {
"@type": "MusicGroup",
"name": "${artist}"
},
"url": "${fullPath}",
"image": "${logoPath}"
}
</script>
<script>
window.jamtrack_data = {
id: "${id}",
plan_code: "${plan_code}",
slug: "${slug}",
artist: "${artist}",
song: "${song}",
location: "${location}"
}
</script>
</head>
<body>
<div id="root">${html}</div>
<script src="/js/client-hydrate.bundle.js"></script>
</body>
</html>`;
const ARTIST_DIR = path.join(OUTPUT_DIR, original_artist_slug);
if (!fs.existsSync(ARTIST_DIR)) {
fs.mkdirSync(ARTIST_DIR, { recursive: false });
}
const finalOutputPath = process.env.NODE_ENV === "development" ? `${name_slug}.html` : `${name_slug}.html`;
const outputFilePath = path.join(ARTIST_DIR, finalOutputPath);
fs.writeFileSync(outputFilePath, fullHtml);
console.log(`Generated: ${outputFilePath}`);
}
console.log("All pages generated!");
});
};
const generateArtistPages = async (render) => {
const rows = [];
const OUTPUT_DIR = path.join(__dirname, "..", "public", "backing-tracks");
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
console.log("generatPages starting")
const TemplatePage = TemplatePageModule.default;
const songs_csv = await load_csv(csvFilePath);
fs.createReadStream(artistCsvFilePath)
.pipe(csv())
.on("data", (row) => rows.push(row))
.on("end", async () => {
console.log(`Processing ${rows.length} rows...`);
for (const row of rows) {
const { original_artist, original_artist_slug, url } = row;
const artist = original_artist;
const matchingSongs = collect_songs_for_artist(original_artist_slug, songs_csv);
console.log(`Found ${matchingSongs.length} songs for ${artist}`);
const location = `/backing-tracks/${original_artist_slug}`;
const fullPath = process.env.REACT_APP_BASE_URL + location;
const logoPath = process.env.REACT_APP_BASE_URL + "/favicon.svg";
add_artist_to_sitemap(original_artist_slug);
console.log(`Generating ${artist}`);
const songs = matchingSongs.map((song) => {
return {
name: song.name,
plan_code: song.plan_code,
url: process.env.REACT_APP_BASE_URL + "/backing-tracks/" + song.original_artist_slug + "/" + song.name_slug
}
});
const html = render
? ReactDOMServer.renderToStaticMarkup(
React.createElement(ArtistTemplatePage, { artist, original_artist_slug, location })
)
: "";
const fullHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="/favicon.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${artist} - Free Backing Track</title>
<link rel="stylesheet" href="${process.env.REACT_APP_BASE_URL}/css/theme.css">
<meta name="description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta name="keywords" content="Backing Track, ${artist}, Instrumental">
<meta name="author" content="JamKazam">
<!-- Open Graph (Facebook, LinkedIn, etc.) -->
<meta property="og:title" content="${artist} | Free Backing Track">
<meta property="og:description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta property="og:image" content="${logoPath}">
<meta property="og:url" content="${fullPath}">
<meta property="og:type" content="music.song">
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${artist} | Free Backing Track">
<meta name="twitter:description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta name="twitter:image" content="${logoPath}">
<!-- Canonical URL -->
<link rel="canonical" href="${fullPath}" />
<script>
window.jamtrack_data = {
artist: "${artist}",
original_artist_slug: "${original_artist_slug}",
location: "${location}",
songs: ${JSON.stringify(songs)}
}
</script>
</head>
<body>
<div id="root">${html}</div>
<script src="/js/client-hydrate.bundle.js"></script>
</body>
</html>`;
const finalOutputPath = process.env.NODE_ENV === "development" ? `${original_artist_slug}.html` : `${original_artist_slug}.html`;
const outputFilePath = path.join(OUTPUT_DIR, finalOutputPath);
fs.writeFileSync(outputFilePath, fullHtml);
console.log(`Generated: ${outputFilePath}`);
}
close_sitemap();
console.log("All pages generated!");
});
};
let render = false;
if (process.argv.length > 2) {
render = process.argv[2] === "true" || process.argv[2] === "yes" || process.argv[2] === "1";
}
init()
generateSongPages(render);
generateArtistPages(render);

View File

@ -1,40 +0,0 @@
#!/bin/bash
# requires node 22 at least
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# default NODE_ENV to development if not set
if [ -z "$NODE_ENV" ]; then
NODE_ENV=development
fi
# default ENVIRONMENT to development if not set
if [ -z "$ENVIRONMENT" ]; then
ENVIRONMENT=development
fi
set -eo pipefail
export NODE_ENV
export ENVIRONMENT
echo "cleaning"
rm -rf $SCRIPT_DIR/build
echo "creating build dir - this is for correct image resolution; ideally this wouldn't be needed"
#mkdir -p $SCRIPT_DIR/build/assets/img
#cp -r $SCRIPT_DIR/../src/assets/img/* $SCRIPT_DIR/build/assets/img/
echo "creating client-hydrate.bundle.js for jamtrack landing pages"
# PUBLIC_URL=? for server builds
# NODE_ENV=production for server builds
npx webpack
#cp -r public/* output/
npm run build
echo "run generate.js for all jamtracks defined in the CSV"
NODE_ENV=$NODE_ENV ENVIRONMENT=$ENVIRONMENT npm run generate-song-landing-pages

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
{
"name": "react-static-site-generator",
"version": "1.0.0",
"description": "A React-based static site generator from CSV",
"main": "generate.js",
"dependencies": {
"csv-parser": "^3.0.0",
"react": "^16.4.1",
"react-dom": "^16.4.1"
},
"scripts": {
"generate-song-landing-pages": "node generate.js",
"build": "babel ../src/ --out-dir build/",
"webpack": "webpack"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/register": "^7.25.9",
"@svgr/webpack": "^8.1.0",
"babel-loader": "^9.2.1",
"dotenv-webpack": "^8.1.0",
"image-minimizer-webpack-plugin": "^4.1.3",
"imagemin": "^9.0.0",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1"
}
}

View File

@ -1,55 +0,0 @@
const path = require("path");
const webpack = require('webpack');
const Dotenv = require("dotenv-webpack");
const environment = process.env.ENVIRONMENT || 'development';
const node_env = process.env.NODE_ENV || 'development';
const debug = node_env == 'development';
module.exports = {
entry: "./client-hydrate.js",
output: {
path: path.resolve(__dirname, "..", "public", "js"),
filename: "client-hydrate.bundle.js",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
},
},
{
test: /\.svg/,
type: 'asset/inline'
},
{
test: /\.(png|jpe?g|gif)$/i,
type: 'asset',
}
],
},
resolve: {
extensions: [".js", ".jsx"],
alias: {
react: path.resolve('./node_modules/react'),
}
},
devtool: debug ? 'eval-source-map' : 'source-map',
mode: node_env,
plugins: [
new Dotenv({
path: `../.env.${environment}`
})
],
optimization: {
minimize: node_env === 'production', //only minimize in production
},
};

View File

@ -1,22 +0,0 @@
module.exports = {
env: {
legacyBaseUrl: "http://www.jamkazam.local:3000",
apiBaseUrl: "http://www.jamkazam.local:3000/api",
},
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require("./cypress/plugins/index.js")(on, config);
},
baseUrl: "http://beta.jamkazam.local:4000",
},
component: {
devServer: {
framework: "react",
bundler: "webpack",
},
},
};

View File

@ -1,6 +0,0 @@
{
"baseUrl": "http://beta.jamkazam.local:4000",
"legacyBaseUrl": "http://www.jamkazam.local:3000",
"apiBaseUrl": "http://www.jamkazam.local:3000/api"
}

View File

@ -1,166 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Change Current Email/Password Feature', () => {
beforeEach(() => {
// Log in to the application or navigate to the account page
// where the change email feature is available
const currentUser = makeFakeUser({
email: 'sam@example.com'
});
cy.stubAuthenticate({ ...currentUser });
cy.visit('/account/identity');
});
describe('Email update', () => {
it('should display the current email address', () => {
// Assert that the current email address is displayed
cy.get('[data-testid=edit_email_form]').within($form => {
cy.get('input#new_email[placeholder="sam@example.com"]');
});
});
it('shows validation error if no data is entered', () => {
// Assert that an error message is displayed
cy.get('[data-testid=edit_email_form]').within($form => {
cy.get('[data-testid=email_submit]').click();
cy.contains('Current password is required');
cy.contains('New Email is required');
});
});
it('shows validation error if new email is invalid', () => {
cy.get('[data-testid=edit_email_form]').within($form => {
// Enter the current password
cy.get('input#current_password').type('password');
// Enter an invalid email address in the input field
cy.get('input#new_email').type('invalidemail');
cy.get('[data-testid=email_submit]').click();
// Assert that an error message is displayed
cy.contains('Email is invalid');
});
});
it('reveal password', () => {
cy.get('[data-testid=edit_email_form]').within($form => {
// Click on the reveal password button
cy.get('.input-group-append').click();
// Assert that the password is revealed
cy.get('input#current_password').should('have.attr', 'type', 'text');
});
});
describe('when the user submits the form', () => {
beforeEach(() => {
cy.intercept('POST', /\S+\/users\S+/, {
statusCode: 200
}).as('updateEmail');
});
it('should update the email address', () => {
cy.get('[data-testid=edit_email_form]').within($form => {
// Enter the current password
cy.get('input#current_password').type('password');
// Enter the new email address
cy.get('input#new_email').type('samsam@example.com');
// Submit the form
cy.get('[data-testid=email_submit]').click();
});
// Assert that the email address is updated
cy.contains('A confirmation email has been sent to your email address');
});
});
});
describe('Password update', () => {
it('shows validation error if no data is entered', () => {
// Assert that an error message is displayed
cy.get('[data-testid=edit_password_form]').within($form => {
cy.get('[data-testid=password_submit]').click();
cy.contains('Current password is required');
cy.contains('New password is required');
});
});
it('reveal password', () => {
cy.get('[data-testid=edit_password_form]').within($form => {
// Click on the reveal password button
cy.get('.current-password-reveal').click();
// Assert that the password is revealed
cy.get('input#current_password').should('have.attr', 'type', 'text');
// Click on the reveal password button
cy.get('.new-password-reveal').click();
// Assert that the password is revealed
cy.get('input#new_password').should('have.attr', 'type', 'text');
});
});
describe('Submit form with errors', () => {
beforeEach(() => {
cy.intercept('POST', /\S+\/users\S+\/set_password/, {
statusCode: 422,
body: {
errors: {
password: ['is too short']
}
}
}).as('updatePassword');
});
it('shows validation error if new password is invalid', () => {
cy.get('[data-testid=edit_password_form]').within($form => {
// Enter the current password
cy.get('input#current_password').type('password');
// Enter an invalid new password
cy.get('input#new_password').type('short');
cy.get('[data-testid=password_submit]').click();
// Assert that an error message is displayed
cy.contains('New Password is too short');
});
});
});
describe('Submit with valid data', () => {
beforeEach(() => {
cy.intercept('POST', /\S+\/users\S+\/set_password/, {
statusCode: 200
}).as('updatePassword');
});
it('should update the password', () => {
cy.get('[data-testid=edit_password_form]').within($form => {
// Enter the current password
cy.get('input#current_password').type('password');
// Enter the new password
cy.get('input#new_password').type('newpassword');
// Submit the form
cy.get('[data-testid=password_submit]').click();
});
// Assert that the password is updated
cy.contains('password has been successfully updated');
});
})
describe('Forgot password', () => {
beforeEach(() => {
cy.intercept('POST', /\S+\/users\S+\/request_reset_password/, {
statusCode: 200
}).as('forgotPassword');
});
it('sends forgot password email', () => {
cy.get('[data-testid=forgot_password]').click();
cy.contains('A password reset email has been sent to your email');
});
})
});
});

View File

@ -1,34 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Account Preferences Feature', () => {
beforeEach(() => {
// Log in to the application or navigate to the account page
// where the change email feature is available
const currentUser = makeFakeUser({
email: 'sam@example.com',
recording_pref: 0
});
cy.stubAuthenticate({ ...currentUser });
cy.intercept('POST', /\S+\/users\S+/, {
statusCode: 200
}).as('updateUser');
cy.visit('/account/preferences');
});
it('should display the current recording preference', () => {
// Assert that the current recording preference is displayed
cy.get('[data-testid=recording_pref_none]').should('be.checked');
});
it('should save the new recording preference', () => {
// Select a new recording preference
cy.get('[data-testid=recording_pref_my_audio]').check('1');
cy.wait('@updateUser');
// Assert that the new recording preference is saved
cy.contains('Recording preference saved successfully.');
});
});

View File

@ -1,117 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('User subscribe to a plan', () => {
beforeEach(() => {
// Log in to the application or navigate to the account page
// where the change email feature is available
const currentUser = makeFakeUser({
email: 'sam@example.com'
});
cy.stubAuthenticate({ ...currentUser });
cy.stubGonSubscriptionCodes();
cy.intercept('GET', /\S+\/get_subscription/, {
statusCode: 200,
body: {
past_due: false,
has_billing_info: true,
plan_code: 'jamsubsilver',
desired_plan_code: 'jamsubgold',
admin_overide_plan_code: null,
admin_overide_ends_at: null,
in_trial: false,
trial_ends_at: null,
subscription: {
},
subscription_rules: {
remaining_monthly_play_time: null
}
}
}).as('getSubscription');
cy.intercept('GET', /\S+\/get_subscription/, {
statusCode: 200,
body: {
past_due: false,
has_billing_info: true,
plan_code: '',
desired_plan_code: '',
admin_overide_plan_code: null,
admin_overide_ends_at: null,
in_trial: false,
trial_ends_at: null,
subscription: {
},
subscription_rules: {
remaining_monthly_play_time: null
}
}
}).as('getSubscriptionWithNoPlan');
});
it('should display the current plan', () => {
cy.visit('/account/subscription');
cy.wait('@getSubscription')
// Assert that the current plan is displayed
cy.get('[data-testid=changeSubscriptionForm]').within($form => {
cy.get('label').contains('Subscription Plan');
cy.get('.select-plan__single-value').should('contain', 'Gold ($9.99/month)');
cy.get('input[type=submit]').should('have.value', 'Save Plan').should('be.disabled');
});
cy.get('[data-testid=playtime]').within($playtime => {
cy.get('label').contains('You have unlimited play time');
});
cy.get('[data-testid=subscription-explanation]').within($expl => {
cy.get('.alert').contains('You are currently on the Silver (monthly) level, thank you!');
});
});
it.only('should display the current plan when no plan', () => {
cy.visit('/account/subscription');
cy.wait('@getSubscriptionWithNoPlan')
// Assert that the current plan is displayed
cy.get('[data-testid=changeSubscriptionForm]').within($form => {
cy.get('label').contains('Subscription Plan');
cy.get('.select-plan__single-value').should('contain', 'Free ($0.00/month)');
cy.get('input[type=submit]').should('have.value', 'Save Plan').should('be.disabled');
});
cy.get('[data-testid=playtime]').within($playtime => {
cy.get('label').contains('You have unlimited play time.');
});
cy.get('[data-testid=subscription-explanation]').within($expl => {
cy.get('.alert').contains('You are currently on the Free (monthly) plan.');
});
});
it('change plan', () => {
cy.visit('/account/subscription');
cy.wait('@getSubscription')
cy.get('[data-testid=changeSubscriptionForm]').within($form => {
cy.get('.select-plan__control').click();
cy.get('.select-plan__option').contains('Platinum ($19.99/month)').click();
cy.get('input[type=submit]').should('be.enabled').click();
});
const text = 'You have selected the PLATINUM MONTHLY PLAN and will be charged US$19.99'
cy.contains('.modal-body', text).should('be.visible');
cy.get('.modal-content').find('.modal-footer').contains('OK').click();
cy.get('.modal-body').should('not.be.visible');
// cy.get('[data-testid=subscription-explanation]').within($expl => {
// cy.get('.alert').contains('You are currently on the Silver (monthly) level, thank you! And your plan and billing will switch to the Platinum (monthly) level on the next billing cycle.');
// });
cy.get('input[type=submit]').should('be.disabled')
});
});

View File

@ -1,29 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Change Email Confirm Page', () => {
beforeEach(() => {
// Log in to the application or navigate to the account page
// where the change email feature is available
const currentUser = makeFakeUser({
email: 'sam@example.com'
});
cy.stubAuthenticate({ ...currentUser });
cy.intercept('POST', /\S+\/update_email/, { statusCode: 200, body: { ok: true } });
});
it('should display the confirm page when visiting the confirm URL', () => {
// Replace with a realistic token for your app if needed
const token = 'dummy-confirm-token';
// Visit the confirm URL
cy.visit(`/confirm-email-change?token=${token}`);
// Assert that the JKChangeEmailConfirm page is rendered
// Adjust selectors/texts as per your actual component
cy.contains('Change Email Confirmation').should('be.visible');
cy.contains('Loading...').should('be.visible');
// Optionally, check for the success message after the email update
cy.wait(1000); // Wait for the email update to complete
cy.contains('Your email has been successfully updated.').should('be.visible');
});
});

View File

@ -1,126 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Profile update', () => {
beforeEach(() => {
// Log in to the application or navigate to the account page
// where the change email feature is available
const currentUser = makeFakeUser();
cy.stubAuthenticate({ ...currentUser });
cy.intercept('GET', /\S+\/users\/\S+\/profile/, {
statusCode: 200,
body: {
first_name: 'David',
last_name: 'Wilson',
name: 'David Wilson',
city: 'Barstow',
state: 'CA',
country: 'US',
location: 'Barstow, CA',
photo_url: null,
biography: 'This is the musician biography. It is a long form text.',
virtual_band: true,
virtual_band_commitment: 2,
traditional_band: false,
traditional_band_commitment: null,
traditional_band_touring: false,
paid_sessions: true,
paid_sessions_hourly_rate: 19900,
paid_sessions_daily_rate: 200000,
free_sessions: true,
cowriting: true,
cowriting_purpose: 2,
subscribe_email: true,
genres: [
{
genre_id: 'asian',
player_type: 'JamRuby::User',
genre_type: 'profile'
},
{
genre_id: 'blues',
player_type: 'JamRuby::User',
genre_type: 'profile'
},
{
genre_id: 'dance',
player_type: 'JamRuby::User',
genre_type: 'profile'
}
],
instruments: [
{
description: 'Acoustic Guitar',
proficiency_level: 3,
priority: 0,
instrument_id: 'acoustic guitar'
},
{
description: 'Bass Guitar',
proficiency_level: 3,
priority: 2,
instrument_id: 'bass guitar'
},
{
description: 'Violin',
proficiency_level: 1,
priority: 0,
instrument_id: 'violin'
},
{
description: 'Banjo',
proficiency_level: 1,
priority: 1,
instrument_id: 'banjo'
}
]
}
}).as('getProfile');
cy.intercept('PUT', /\S+\/users\/\S+/, {
statusCode: 200
}).as('updateProfile');
cy.visit('/profile');
});
it('should render the profile form with persisted data', () => {
// Assert that the profile form is rendered
cy.wait('@getProfile');
cy.get('[data-testid=edit_profile_form]').within($form => {
cy.get('[data-testid=firstName]').should('have.value', 'David');
cy.get('[data-testid=lastName]').should('have.value', 'Wilson');
cy.get('[data-testid=biography]').should('contain', 'This is the musician biography. It is a long form text');
cy.get('[data-testid=subscribeEmail]').should('be.checked');
cy.get('[data-testid=virtualBand]').should('be.checked');
cy.get('[data-testid=traditionalBand]').should('not.be.checked');
cy.get('[data-testid=cowriting]').should('be.checked');
cy.get('[data-testid=instruments] input:checked').should('have.length', 4);
cy.get('[data-testid=genres] input:checked').should('have.length', 3);
});
});
it.only('should update the profile', () => {
// Update the profile form
cy.get('[data-testid=edit_profile_form]').within($form => {
// Update the first name
cy.get('[data-testid=firstName]')
.clear()
.type('Seth');
// Update the last name
cy.get('[data-testid=lastName]')
.clear()
.type('Call');
cy.wait(2000);
});
cy.reload();
cy.get('[data-testid=firstName]').should('have.value', 'Seth');
});
});

View File

@ -1,89 +0,0 @@
import makeFakeUser from '../../factories/user';
describe('Payments Page', () => {
beforeEach(() => {
// Place any setup code here, such as logging in or navigating to the payments page
const currentUser = makeFakeUser();
cy.stubAuthenticate({ ...currentUser });
cy.intercept('GET', /\S+\/invoice_history\?limit=10&cursor=0/, { fixture: 'payments_page1' }).as('getPayments1');
cy.intercept('GET', /\S+\/invoice_history\?limit=10&cursor=10/, { fixture: 'payments_page2' }).as('getPayments2');
cy.intercept('GET', /\S+\/invoice_history\?limit=10&cursor=20/, { fixture: 'payments_page3' }).as('getPayments3');
});
describe('in desktop', () => {
beforeEach(() => {
cy.viewport('macbook-13');
});
it('should list user\'s payments', () => {
// Call the external API that fetches the payment records
cy.visit('/account/payments')
cy.wait('@getPayments1')
cy.get('[data-testid="paymentsListTable"]').should('be.visible').find('tbody tr td').first().within(() => {
cy.contains('12/01/2022')
})
})
it('paginate through the payments', () => {
// Call the external API that fetches the payment records
cy.visit('/account/payments')
cy.wait('@getPayments1')
cy.get('[data-testid="paymentsListTable"]').should('be.visible').find('tbody tr').first().within(() => {
cy.contains('12/01/2022')
})
// Click the next page button
cy.get('[data-testid="nextPageButton"]').click()
cy.wait('@getPayments2')
cy.get('[data-testid="paymentsListTable"]').should('be.visible').find('tbody tr:nth-child(11)').first().within(() => {
cy.contains('02/01/2022')
})
// Click the next page button
cy.get('[data-testid="nextPageButton"]').click()
cy.wait('@getPayments3')
cy.get('[data-testid="paymentsListTable"]').should('be.visible').find('tbody tr:nth-child(22)').first().within(() => {
cy.contains('03/01/2021')
})
});
});
describe('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('should list user\'s payments', () => {
// Call the external API that fetches the payment records
cy.visit('/account/payments')
cy.wait('@getPayments1')
cy.get('[data-testid=paymentSwiper]').should('be.visible')
for (let i = 0; i < 9; i++) {
cy.get('.swiper-button-next').click();
cy.wait(500);
}
cy.wait('@getPayments2')
for (let i = 0; i < 10; i++) {
cy.get('.swiper-button-next').click();
cy.wait(500);
}
cy.get('.swiper-button-next').click();
cy.wait('@getPayments3')
for (let i = 0; i < 2; i++) {
cy.get('.swiper-button-next').click();
cy.wait(500);
}
cy.get('.swiper-button-next').should('have.class', 'swiper-button-disabled');
});
})
})

View File

@ -1,137 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Affiliate Earnings', () => {
beforeEach(() => {
const currentUser = makeFakeUser();
cy.stubAuthenticate({ id: currentUser.id });
cy.stubGonSubscriptionCodes();
});
describe('When user has no earnings', () => {
it('should show no earnings message', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/payments/, {
body: [
{
payments: [],
next: null
}
]
}).as('fetchAllEarnings');
cy.visit('/affiliate/earnings');
cy.wait('@fetchAllEarnings');
cy.contains('There is no earnings data yet ');
});
});
describe('When user has earnings', () => {
it('should show earnings', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/payments/, {
body: {
payments: [
{
payment_type: 'monthly',
year: 2021,
month: 9,
subscriptions: [
{
plan: 'jamsubsilver',
count: 2
}
],
jamtracks_sold: 3,
due_amount_in_cents: 5000
},
{
payment_type: 'monthly',
year: 2021,
month: 8,
subscriptions: [
{
plan: 'jamsubgold',
count: 1
},
{
plan: 'jamsubplatinum',
count: 2
}
],
jamtracks_sold: 2,
due_amount_in_cents: 3000
}
],
next: null
}
}).as('fetchAllEarnings');
cy.visit('/affiliate/earnings');
cy.wait('@fetchAllEarnings');
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
.eq(0)
.should('have.text', 'September 2021');
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
.eq(1)
.should('have.text', 'Silver - 2');
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
.eq(2)
.should('have.text', '3');
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
.eq(3)
.should('have.text', '$50.00');
});
});
describe('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('should show earnings', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/payments/, {
body: {
payments: [
{
payment_type: 'monthly',
year: 2021,
month: 9,
subscriptions: [
{
plan: 'jamsubsilver',
count: 2
}
],
jamtracks_sold: 3,
due_amount_in_cents: 5000
},
{
payment_type: 'monthly',
year: 2021,
month: 8,
subscriptions: [
{
plan: 'jamsubgold',
count: 1
},
{
plan: 'jamsubplatinum',
count: 2
}
],
jamtracks_sold: 2,
due_amount_in_cents: 3000
}
],
next: null
}
}).as('fetchAllEarningsOnMobile');
cy.visit('/affiliate/earnings');
cy.wait('@fetchAllEarningsOnMobile');
cy.contains('September 2021');
cy.contains('Silver - 2');
cy.contains('JamTracks Sold: 3');
cy.contains('Earnings: $50.00');
});
});
});

View File

@ -1,90 +0,0 @@
/// <reference types="cypress" />
import { day } from 'is_js';
import makeFakeUser from '../../factories/user';
describe('affiliate signups', () => {
beforeEach(() => {
const currentUser = makeFakeUser();
cy.stubAuthenticate({ id: currentUser.id });
//cy.stubGonSubscriptionCodes();
});
describe('when user has no signups', () => {
it('should show no signups message', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/signups?\S+/, {
body: [
{
traffics: [],
next: null
}
]
}).as('fetchAllSignups');
cy.visit('/affiliate/signups');
cy.wait('@fetchAllSignups');
cy.contains('There is no signup data yet ');
});
});
describe('when user has signups', () => {
it('should show signups', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/signups?\S+/, {
body: {
traffics: [
{
day: '2021-09-01',
visits: 100,
signups: 10,
},
{
day: '2021-09-02',
visits: 200,
signups: 20,
}
]
}
}).as('fetchAllSignups');
cy.visit('/affiliate/signups');
cy.wait('@fetchAllSignups');
cy.contains('Signups');
cy.get('[data-testid=affiliateSignupList] tbody tr:first-child > td').eq(0).should('have.text', 'September 2021');
cy.get('[data-testid=affiliateSignupList] tbody tr:first-child > td').eq(1).should('have.text', '300');
cy.get('[data-testid=affiliateSignupList] tbody tr:first-child > td').eq(2).should('have.text', '30');
});
});
describe('in mobile', () => {
it('should show signups in mobile', () => {
cy.viewport('iphone-6');
cy.intercept('GET', /\S+\/api\/affiliate_partners\/signups?\S+/, {
body: {
traffics: [
{
day: '2021-09-01',
visits: 100,
signups: 25,
},
{
day: '2021-10-01',
visits: 50,
signups: 10,
},
{
day: '2021-10-02',
visits: 10,
signups: 5,
}
]
}
}).as('fetchAllSignups');
cy.visit('/affiliate/signups');
cy.wait('@fetchAllSignups');
cy.contains('Signups');
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-slide').should('have.length', 2);
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-button-next').click();
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-slide').contains('October 2021');
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-slide').contains('Visits: 60');
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-slide').contains('Signups: 15');
});
})
})

View File

@ -1,54 +0,0 @@
///<reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('forgot password', () => {
beforeEach(() => {
const currentUser = makeFakeUser({
email: 'sam@example.com'
});
cy.clearCookie('remeber_token');
});
it('redirects to forgot password page', () => {
cy.visit('/');
cy.url().should('include', '/auth/login');
cy.get('a')
.contains('Forgot password?')
.click();
cy.url().should('include', '/auth/forget-password');
cy.get('h5').contains('Forgot Your Password');
});
describe('validate forgot password form', () => {
beforeEach(() => {
cy.visit('/auth/forget-password');
cy.get('[data-testid=email]').clear();
cy.get('[data-testid=submit]').should('be.disabled');
});
//invalid email format
it('invalid email format', () => {
cy.get('[data-testid=email]').type('invalid-email-format@example');
cy.get('[data-testid=submit]').click();
cy.url().should('not.include', /\/auth\/confirm-mail?\S+/);
});
//valid email format but non-existing
it('valid email format but non-existing', () => {
cy.get('[data-testid=email]').type('valid-email-format@example.com');
cy.get('[data-testid=submit]').click();
cy.url().should('not.include', /\/auth\/confirm-mail?\S+/);
});
//valid and existing email
it('valid and existing email', () => {
cy.get('[data-testid=email]').type('nuwan@jamkazam.com');
cy.get('[data-testid=submit]').click();
cy.wait(3000);
cy.contains('Please check your email!');
cy.url().should('match', /\S+auth\/confirm-mail?\S+/);
cy.contains('An email has been sent to nuwan@jamkazam.com.')
});
});
});

View File

@ -1,93 +0,0 @@
///<reference types="cypress" />
function submitLogin(){
const userAttrs = {
id: '1',
first_name: 'Peter',
last_name: 'Pan',
name: 'Peter Pan',
photo_url: '',
email: 'peter@example.com'
}
cy.intercept('POST', `${Cypress.env('apiBaseUrl')}/auths/login`, {
statusCode: 200,
body: userAttrs
}).as('createSession')
cy.intercept('GET', `${Cypress.env('apiBaseUrl')}/me`, {
statusCode: 200,
body: userAttrs
}).as('getCurrentUser')
cy.reload()
cy.get('[data-testid=email]').type('peter@example.com')
cy.get('[data-testid=password]').type('jam123')
cy.get('[data-testid=submit]').click()
}
describe('Unauthenticated users redirect to login page', () => {
it('redirects to login page', () => {
cy.clearCookie('remeber_token')
cy.visit('/')
cy.url().should('include', '/auth/login')
cy.contains('Sign In')
})
})
describe('Login page', () => {
before(() => {
cy.clearCookie('remeber_token')
})
it('validate login form', () => {
cy.visit('/auth/login')
cy.reload()
cy.get('[data-testid=submit]').should('be.disabled')
cy.get('[data-testid=email]').type('invalid-email-format@example')
cy.get('[data-testid=submit]').should('be.disabled')
cy.get('[data-testid=email]').type('valid-email-format@example.com')
cy.get('[data-testid=submit]').should('be.disabled')
cy.get('[data-testid=password]').type('password')
cy.get('[data-testid=submit]').should('not.be.disabled')
})
it('submit login form with invalid credentials', () => {
cy.visit('/auth/login')
cy.reload()
cy.get('[data-testid=email]').type('peter@example.com')
cy.get('[data-testid=password]').type('wrong')
cy.get('[data-testid=submit]').click()
cy.contains('Incorrect email or password')
})
it('submits login form', () => {
cy.visit('/auth/login')
submitLogin()
cy.url().should('eq', Cypress.config().baseUrl + '/') // tests won't fail in case the port changes
//cy.contains('Signed in as peter@example.com')
//cy.contains('Peter Pan')
//cy.getCookie('remember_token').should('exist')
})
it('redirect to requested page', () => {
cy.visit('/friends')
cy.url().should('include', '/auth/login')
cy.reload()
submitLogin()
cy.url().should('eq', Cypress.config().baseUrl + '/friends')
})
})
describe('Forget password page', () => {
it('submit forget password form', () => {
cy.visit('/auth/forget-password')
cy.get('[data-testid=email]').type('peter@example.com')
cy.get('[data-testid=submit]').click()
cy.contains('An email is sent')
})
})

View File

@ -1,611 +0,0 @@
/// <reference types="cypress" />
const showSidePanelContent = () => {
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
cy.get('[data-testid=profileSidePanel] .modal-body').first().within(() => {
cy.contains('Location: Denver, CO, US')
.and('contain', 'Skill Level: Professional')
.and('contain', 'Joined JamKazam: 08-26-2021')
.and('contain', 'Last Active:')
.and('contain', 'Latency To Me:');
cy.get('.latency-badge').contains('UNKNOWN');
cy.get('[data-testid=biography]').contains('Biography of Test User1');
//instruments
cy.get('[data-testid=instruments]').contains('Acoustic Guitar: Expert');
cy.get('[data-testid=instruments]').contains('Keyboard: Expert');
//genres
cy.get('[data-testid=genres]').contains('classical, blues');
//bands
cy.get('[data-testid=bands]').contains('The Band');
//performance_samples
cy.get('[data-testid=performance_samples]').contains('Test Recording1')//.should('have.attr', 'href').and('eq', 'https://www.jamkazam.com/test-recording1');
//online presence
cy.get('[data-testid=online_presences]').contains('Youtube').should('have.attr', 'href').and('eq', 'https://www.youtube.com/testuser');
cy.get('[data-testid=online_presences]').contains('Facebook').should('have.attr', 'href').and('eq', 'https://www.facebook.com/testuser');
cy.get('[data-testid=online_presences]').contains('Twitter').should('have.attr', 'href').and('eq', 'https://www.twitter.com/testuser');
});
};
const closeSidePanel = () => {
cy.get('[data-testid=profileSidePanel] .modal-header button.close').click();
};
describe('Friends page without data', () => {
beforeEach(() => {
cy.stubAuthenticate();
cy.intercept('POST', /\S+\/filter/, {
musicians: []
}).as("getPeople_empty");
});
//from_location checkbox is hidden for now. so we skip this test for now
it.skip('from_location is unchecked', () => {
cy.visit('/friends');
//default api call with from_location parameter turned off
cy.wait('@getPeople_empty')
.then(interception => {
assert.isNotNull(interception.response.body, '1st API call');
})
.its('request.body')
.should('deep.contain', {
from_location: false
});
//now it automatically turns on from_location parameter and fetches again
cy.wait('@getPeople_empty')
.then(interception => {
assert.isNotNull(interception.response.body, '2nd API call - (prefetched)');
})
.its('request.body')
.should('deep.contain', {
from_location: true
});
cy.contains('No Records!');
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=from_location]').check();
cy.wait(1000);
cy.get('[data-testid=btnSubmitSearch]').click();
//default api call with from_location parameter turned on
cy.wait('@getPeople_empty')
.then(interception => {
assert.isNotNull(interception.response.body, '3th API call');
})
.its('request.body')
.should('deep.contain', {
from_location: true
});
cy.contains('No Records!');
});
});
describe('Friends page with data', () => {
beforeEach(() => {
cy.stubAuthenticate({ id: '2' }); //currentUser id is 2 - people.yaml fixture
cy.intercept('POST', /\S+\/filter\?offset=0/, { fixture: 'people_page1' }).as('getPeople_page1');
cy.intercept('POST', /\S+\/filter\?offset=10/, { fixture: 'people_page2' }).as('getPeople_page2');
cy.intercept('POST', /\S+\/filter\?offset=20/, { fixture: 'people_page3' }).as('getPeople_page3');
cy.intercept('GET', /\S+\/profile/, { fixture: 'person' });
cy.intercept('GET', /\S+\/my_notifications\S+/, {
statusCode: 200,
body: []
});
});
describe('listing users', () => {
beforeEach(() => {
cy.visit('/friends');
});
describe('in desktop', () => {
beforeEach(() => {
cy.viewport('macbook-13');
});
it('paginate', () => {
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 10);
cy.wait('@getPeople_page2')
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 20);
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 30);
cy.get('[data-testid=paginate-next-page]').should('not.exist');
});
it('show profiles', () => {
cy.contains('Find New Friends').should('exist');
cy.contains('Update Search').should('exist');
cy.contains('Reset Filters').should('exist');
cy.get('[data-testid=peopleListTable] > tbody tr')
.should('have.length', 10)
.first()
.contains('Test User1');
});
it('click profile name', () => {
//open side panel by clicking name
cy.get('[data-testid=peopleListTable]').find('.person-link').first().within(() => {
cy.contains("Test User1").click()
})
//cy.get('[data-testid=peopleListTable]').find('.person-link').first().click()
showSidePanelContent();
closeSidePanel();
});
it('click more button', () => {
//open side panel by clicking more button
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
it('click more link', () => {
//open side panel by clicking more link
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=linkMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
it('click description more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=biography-col]')
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click instruments more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=instrumentList]')
.within(() => {
cy.get('[data-testid=instrument]').should('have.length', 4); //show only 4 instruments plus more link
cy.contains('Acoustic Guitar: Expert');
cy.contains('Keyboard: Expert');
cy.contains('Ukulele: Expert');
cy.contains('Voice: Expert');
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click genres more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=genres-col]')
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
});
describe('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('show profile', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').should('have.length', 10);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(0)
.contains('Test User1');
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(0)
.should('be.visible');
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(2)
.should('not.be.visible');
});
it('show all profile description', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=mobBiography]')
.should('not.contain', 'More');
});
it('show all instruments', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=instrumentList] div')
.its('length')
.should('be.gte', 1);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=instrumentList]')
.should('not.contain', 'More');
});
it('show all genres', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=genreList] div')
.its('length')
.should('be.gte', 1);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=genreList]')
.should('not.contain', 'More');
});
//it.skip('click connect button', () => {});
//it.skip('click message button', () => {});
it.skip('paginate', () => {
cy.get('[data-testid=peopleSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled');
for (let i = 0; i < 19; i++) {
cy.get('[data-testid=peopleSwiper] .swiper-button-next').click();
cy.wait(500);
}
cy.wait(500);
cy.get('[data-testid=peopleSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled');
});
it('click more button', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
});
});
describe('making friendship', () => {
it('add friend', () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
cy.visit('/friends');
cy.contains('Test User2').click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('not.be.disabled')
.click();
// cy.get('[data-testid=confirmFriendRequestModal]').within(() => {
// cy.contains('Send a friend request to Test User2');
// cy.contains('Yes').click();
// });
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Success! Your friend request has been sent to Test User2.');
});
it('remove friend', () => {
cy.intercept('GET', /\S+\/profile/, { fixture: 'friend' });
cy.intercept('DELETE', /\S+\/friends\S+/, { statusCode: 204, body: { ok: true } });
cy.visit('/friends');
cy.contains('Test User1').click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=disconnect]')
.should('exist')
.should('not.be.disabled')
.click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=disconnect]')
.should('not.exist');
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.exist')
.should('not.be.disabled');
});
});
describe('chat window', () => {
beforeEach(() => {
cy.visit('/friends');
});
it('is not disabled for friends', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.should('not.be.disabled')
.trigger('mouseover');
cy.contains('Send a message').should('exist');
});
it('is not disabled for non friends', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(1)
.find('[data-testid=message]')
.should('not.be.disabled');
//cy.contains('You can message this user once you are friends').should('exist')
});
it('lists text messages', () => {
//initially show the most recent messages on openning chat window modal
let numberOfMessages = 10;
cy.fixture('text_messages_page1').then(json => {
cy.intercept('GET', /\S+\/text_messages\S+/, json).as('getTextMessages');
});
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.wait('@getTextMessages');
cy.get('[data-testid=textMessageModal]')
.should('be.visible')
.within(() => {
cy.get('.text-message-row').should('have.length', numberOfMessages);
//display previous messages by scrolling up
const messageFixtures = ['text_messages_page2', 'text_messages_page3'];
messageFixtures.forEach(fixture => {
cy.fixture(fixture).then(json => {
cy.intercept('GET', /\S+\/text_messages\S+/, json);
cy.get('.modal-body .ScrollbarsCustom-Scroller')
.trigger('mouseover')
.scrollTo('bottom');
cy.get('.modal-body .ScrollbarsCustom-Scroller')
.trigger('mouseover')
.scrollTo('top');
numberOfMessages = numberOfMessages + 10;
cy.get('.text-message-row').should('have.length', numberOfMessages);
});
});
cy.get('button')
.contains('Close')
.should('not.be.disabled')
.click();
});
cy.get('[data-testid=textMessageModal]').should('not.be.visible');
});
it('sends message by clicking send button', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Send')
.should('be.disabled');
cy.get('textarea').type('Hello');
cy.get('button')
.contains('Send')
.should('not.be.disabled')
.click();
cy.get('textarea').should('have.value', '');
cy.get('button')
.contains('Send')
.should('be.disabled');
});
});
it('sends message by pressing enter key', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Send')
.should('be.disabled');
cy.get('textarea').type('Hello{enter}');
cy.get('textarea').should('have.value', '');
cy.get('button')
.contains('Send')
.should('be.disabled');
});
});
it('goes away by clicking close button', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Close')
.should('not.be.disabled')
.click();
});
cy.get('[data-testid=textMessageModal]').should('not.be.visible');
});
it.skip('shows received message by other user', () => {
//TODO: this should be test in e2e test
});
});
describe('coming from email links', () => {
it("opens details sidebar", () => {
cy.visit('/friends?open=details&id=1');
showSidePanelContent();
});
it("opens chat window", () => {
cy.visit('/friends?open=message&id=1');
cy.get('[data-testid=textMessageModal]')
.should('be.visible')
cy.contains('Send Message to Test User1').should('exist');
});
it("sends friend request", () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
cy.visit('/friends?open=connect&id=1');
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Success! Your friend request has been sent to Test User1.');
});
})
describe('filter', () => {
const fillFilterForm = () => {
//cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_fair]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_high]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_beginner]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_intermediate]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_expert]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_expert]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=from_location]').uncheck();
cy.get('#selInstruments')
.type('Drums{enter}')
.click();
cy.get('#selGenres')
.type('Pop{enter}')
.click();
cy.get('#selLastActive').type('Within last 1 Day{enter}');
cy.get('#selJoinedWithin').type('Within last 7 Day{enter}');
};
beforeEach(() => {
cy.visit('/friends');
});
it('open and close filter modal', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch]').should('be.visible');
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=modalUpdateSearch]').should('not.be.visible');
});
it('render filter form', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch]').within(() => {
cy.get('input[name=latency_good]')
.should('be.checked')
.next()
.contains('Good (less than 40ms)');
cy.get('input[name=latency_fair]')
.should('be.checked')
.next()
.contains('Fair (40-60ms)');
cy.get('input[name=latency_high]')
.should('not.be.checked')
.next()
.contains('Poor (more than 60ms)');
cy.get('input[name=from_location]')
.should('not.be.checked')
});
});
it('reset filters', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm();
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('not.be.checked');
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=btnResetSearch]').click(); //click reset button
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('be.checked');
});
it('submit filter form with params', () => {
//wait for stubbed request sent to fetch data for initial page load
cy.wait('@getPeople_page1').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data');
});
//the subsequent request sent to perfetch data and store in redux prefetched buffer
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data - (prefethed)');
});
cy.get('[data-testid=btnUpdateSearch]').click();
cy.wait(1000);
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form without filling any form field
cy.wait('@getPeople_page1')
.then(interception => {
assert.isNotNull(interception.response.body, '3rd API call has data');
})
.its('request.body')
.should('deep.contain', {
latency_good: true,
latency_fair: true,
latency_high: false,
proficiency_beginner: true,
proficiency_intermediate: true,
proficiency_expert: true,
instruments: [],
genres: [],
from_location: false
});
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '4th API call has data - (prefethed)');
});
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm(); // change filter options
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form again. but this time fill form fields
cy.wait('@getPeople_page1')
.then(interception => {
assert.isNotNull(interception.response.body, '5th API call has data');
})
.its('request.body')
.should('deep.contain', {
latency_good: false,
latency_fair: false,
latency_high: false,
proficiency_beginner: false,
proficiency_intermediate: false,
proficiency_expert: false,
instruments: [{ value: 'drums', label: 'Drums' }],
genres: ['pop'],
from_location: false
});
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '6th API call has data - (prefethed)');
});
});
});
});

View File

@ -1,607 +0,0 @@
/// <reference types="cypress" />
const showSidePanelContent = () => {
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
cy.get('[data-testid=profileSidePanel] .modal-body').first().within(() => {
cy.contains('Location: Denver, CO, US')
.and('contain', 'Skill Level: Professional')
.and('contain', 'Joined JamKazam: 08-26-2021')
.and('contain', 'Last Active:')
.and('contain', 'Latency To Me:');
cy.get('.latency-badge').contains('UNKNOWN');
cy.get('[data-testid=biography]').contains('Biography of Test User1');
//instruments
cy.get('[data-testid=instruments]').contains('Acoustic Guitar: Expert');
cy.get('[data-testid=instruments]').contains('Keyboard: Expert');
//genres
cy.get('[data-testid=genres]').contains('classical, blues');
//bands
cy.get('[data-testid=bands]').contains('The Band');
//performance_samples
cy.get('[data-testid=performance_samples]').contains('Test Recording1')//.should('have.attr', 'href').and('eq', 'https://www.jamkazam.com/test-recording1');
//online presence
cy.get('[data-testid=online_presences]').contains('Youtube').should('have.attr', 'href').and('eq', 'https://www.youtube.com/testuser');
cy.get('[data-testid=online_presences]').contains('Facebook').should('have.attr', 'href').and('eq', 'https://www.facebook.com/testuser');
cy.get('[data-testid=online_presences]').contains('Twitter').should('have.attr', 'href').and('eq', 'https://www.twitter.com/testuser');
});
};
const closeSidePanel = () => {
cy.get('[data-testid=profileSidePanel] .modal-header button.close').click();
};
describe('Friends page without data', () => {
beforeEach(() => {
cy.stubAuthenticate();
// cy.intercept('POST', /\S+\/filter/, {
// musicians: []
// }).as("getPeople_empty");
});
it('shows no records found alert', () => {
cy.visit('/friends/my');
//default api call with from_location parameter turned off
// cy.wait('@getPeople_empty')
// .then(interception => {
// assert.isNotNull(interception.response.body, '1st API call');
// })
// .its('request.body')
// .should('deep.contain', {
// from_location: false
// });
// //now it automatically turns on from_location parameter and fetches again
// cy.wait('@getPeople_empty')
// .then(interception => {
// assert.isNotNull(interception.response.body, '2nd API call - (prefetched)');
// })
// .its('request.body')
// .should('deep.contain', {
// from_location: true
// });
// cy.contains('No Records!');
// cy.get('[data-testid=btnUpdateSearch]').click();
// cy.get('[data-testid=modalUpdateSearch] input[name=from_location]').check();
// cy.wait(1000);
// cy.get('[data-testid=btnSubmitSearch]').click();
// //default api call with from_location parameter turned on
// cy.wait('@getPeople_empty')
// .then(interception => {
// assert.isNotNull(interception.response.body, '3th API call');
// })
// .its('request.body')
// .should('deep.contain', {
// from_location: true
// });
// cy.contains('No Records!');
});
});
describe('Friends page with data', () => {
beforeEach(() => {
cy.stubAuthenticate({ id: '2' }); //currentUser id is 2 - people.yaml fixture
cy.intercept('POST', /\S+\/filter\?offset=0/, { fixture: 'people_page1' }).as('getPeople_page1');
cy.intercept('POST', /\S+\/filter\?offset=10/, { fixture: 'people_page2' }).as('getPeople_page2');
cy.intercept('POST', /\S+\/filter\?offset=20/, { fixture: 'people_page3' }).as('getPeople_page3');
cy.intercept('GET', /\S+\/profile/, { fixture: 'person' });
});
describe('listing users', () => {
beforeEach(() => {
cy.visit('/friends');
});
describe('in desktop', () => {
beforeEach(() => {
cy.viewport('macbook-13');
});
it('paginate', () => {
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 10);
cy.wait('@getPeople_page2')
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 20);
//cy.get('[data-testid=paginate-next-page]').should('not.exist');
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 30);
cy.get('[data-testid=paginate-next-page]').should('not.exist');
});
it('show profiles', () => {
cy.contains('Find New Friends').should('exist');
cy.contains('Update Search').should('exist');
cy.contains('Reset Filters').should('exist');
cy.get('[data-testid=peopleListTable] > tbody tr')
.should('have.length', 10)
.first()
.contains('Test User1');
});
it('click profile name', () => {
//open side panel by clicking name
cy.get('[data-testid=peopleListTable]').find('.person-link').first().within(() => {
cy.contains("Test User1").click()
})
//cy.get('[data-testid=peopleListTable]').find('.person-link').first().click()
showSidePanelContent();
closeSidePanel();
});
it('click more button', () => {
//open side panel by clicking more button
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
it('click more link', () => {
//open side panel by clicking more link
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=linkMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
it('click description more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=biography-col]')
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click instruments more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=instrumentList]')
.within(() => {
cy.get('[data-testid=instrument]').should('have.length', 4); //show only 4 instruments plus more link
cy.contains('Acoustic Guitar: Expert');
cy.contains('Keyboard: Expert');
cy.contains('Ukulele: Expert');
cy.contains('Voice: Expert');
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click genres more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=genres-col]')
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
});
describe('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('show profile', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').should('have.length', 10);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(0)
.contains('Test User1');
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(0)
.should('be.visible');
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(2)
.should('not.be.visible');
});
it('show all profile description', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=mobBiography]')
.should('not.contain', 'More');
});
it('show all instruments', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=instrumentList] div')
.its('length')
.should('be.gte', 1);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=instrumentList]')
.should('not.contain', 'More');
});
it('show all genres', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=genreList] div')
.its('length')
.should('be.gte', 1);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=genreList]')
.should('not.contain', 'More');
});
//it.skip('click connect button', () => {});
//it.skip('click message button', () => {});
it.skip('paginate', () => {
cy.get('[data-testid=peopleSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled');
for (let i = 0; i < 19; i++) {
cy.get('[data-testid=peopleSwiper] .swiper-button-next').click();
cy.wait(500);
}
cy.wait(500);
cy.get('[data-testid=peopleSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled');
});
it('click more button', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
});
});
describe('making friendship', () => {
it('add friend', () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
cy.visit('/friends');
cy.contains('Test User2').click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('not.be.disabled')
.click();
// cy.get('[data-testid=confirmFriendRequestModal]').within(() => {
// cy.contains('Send a friend request to Test User2');
// cy.contains('Yes').click();
// });
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Success! Your friend request has been sent to Test User2.');
});
it('remove friend', () => {
cy.intercept('GET', /\S+\/profile/, { fixture: 'friend' });
cy.intercept('DELETE', /\S+\/friends\S+/, { statusCode: 204, body: { ok: true } });
cy.visit('/friends');
cy.contains('Test User1').click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=disconnect]')
.should('exist')
.should('not.be.disabled')
.click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=disconnect]')
.should('not.exist');
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.exist')
.should('not.be.disabled');
});
});
describe('chat window', () => {
beforeEach(() => {
cy.visit('/friends');
});
it('is not disabled for friends', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.should('not.be.disabled')
.trigger('mouseover');
cy.contains('Send a message').should('exist');
});
it('is not disabled for non friends', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(1)
.find('[data-testid=message]')
.should('not.be.disabled');
//cy.contains('You can message this user once you are friends').should('exist')
});
it('lists text messages', () => {
//initially show the most recent messages on openning chat window modal
let numberOfMessages = 10;
cy.fixture('text_messages_page1').then(json => {
cy.intercept('GET', /\S+\/text_messages\S+/, json).as('getTextMessages');
});
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.wait('@getTextMessages');
cy.get('[data-testid=textMessageModal]')
.should('be.visible')
.within(() => {
cy.get('.text-message-row').should('have.length', numberOfMessages);
//display previous messages by scrolling up
const messageFixtures = ['text_messages_page2', 'text_messages_page3'];
messageFixtures.forEach(fixture => {
cy.fixture(fixture).then(json => {
cy.intercept('GET', /\S+\/text_messages\S+/, json);
cy.get('.modal-body .ScrollbarsCustom-Scroller')
.trigger('mouseover')
.scrollTo('bottom');
cy.get('.modal-body .ScrollbarsCustom-Scroller')
.trigger('mouseover')
.scrollTo('top');
numberOfMessages = numberOfMessages + 10;
cy.get('.text-message-row').should('have.length', numberOfMessages);
});
});
cy.get('button')
.contains('Close')
.should('not.be.disabled')
.click();
});
cy.get('[data-testid=textMessageModal]').should('not.be.visible');
});
it('sends message by clicking send button', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Send')
.should('be.disabled');
cy.get('textarea').type('Hello');
cy.get('button')
.contains('Send')
.should('not.be.disabled')
.click();
cy.get('textarea').should('have.value', '');
cy.get('button')
.contains('Send')
.should('be.disabled');
});
});
it('sends message by pressing enter key', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Send')
.should('be.disabled');
cy.get('textarea').type('Hello{enter}');
cy.get('textarea').should('have.value', '');
cy.get('button')
.contains('Send')
.should('be.disabled');
});
});
it('goes away by clicking close button', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Close')
.should('not.be.disabled')
.click();
});
cy.get('[data-testid=textMessageModal]').should('not.be.visible');
});
it.skip('shows received message by other user', () => {
//TODO: this should be test in e2e test
});
});
describe('coming from email links', () => {
it("opens details sidebar", () => {
cy.visit('/friends?open=details&id=1');
showSidePanelContent();
});
it("opens chat window", () => {
cy.visit('/friends?open=message&id=1');
cy.get('[data-testid=textMessageModal]')
.should('be.visible')
cy.contains('Send Message to Test User1').should('exist');
});
it("sends friend request", () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
cy.visit('/friends?open=connect&id=1');
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Success! Your friend request has been sent to Test User1.');
});
})
describe('filter', () => {
const fillFilterForm = () => {
//cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_fair]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_high]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_beginner]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_intermediate]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_expert]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_expert]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=from_location]').uncheck();
cy.get('#selInstruments')
.type('Drums{enter}')
.click();
cy.get('#selGenres')
.type('Pop{enter}')
.click();
cy.get('#selLastActive').type('Within last 1 Day{enter}');
cy.get('#selJoinedWithin').type('Within last 7 Day{enter}');
};
beforeEach(() => {
cy.visit('/friends');
});
it('open and close filter modal', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch]').should('be.visible');
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=modalUpdateSearch]').should('not.be.visible');
});
it('render filter form', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch]').within(() => {
cy.get('input[name=latency_good]')
.should('be.checked')
.next()
.contains('Good (less than 40ms)');
cy.get('input[name=latency_fair]')
.should('be.checked')
.next()
.contains('Fair (40-60ms)');
cy.get('input[name=latency_high]')
.should('not.be.checked')
.next()
.contains('Poor (more than 60ms)');
cy.get('input[name=from_location]')
.should('not.be.checked')
});
});
it('reset filters', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm();
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('not.be.checked');
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=btnResetSearch]').click(); //click reset button
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('be.checked');
});
it('submit filter form with params', () => {
//wait for stubbed request sent to fetch data for initial page load
cy.wait('@getPeople_page1').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data');
});
//the subsequent request sent to perfetch data and store in redux prefetched buffer
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data - (prefethed)');
});
cy.get('[data-testid=btnUpdateSearch]').click();
cy.wait(1000);
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form without filling any form field
cy.wait('@getPeople_page1')
.then(interception => {
assert.isNotNull(interception.response.body, '3rd API call has data');
})
.its('request.body')
.should('deep.contain', {
latency_good: true,
latency_fair: true,
latency_high: false,
proficiency_beginner: true,
proficiency_intermediate: true,
proficiency_expert: true,
instruments: [],
genres: [],
from_location: false
});
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '4th API call has data - (prefethed)');
});
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm(); // change filter options
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form again. but this time fill form fields
cy.wait('@getPeople_page1')
.then(interception => {
assert.isNotNull(interception.response.body, '5th API call has data');
})
.its('request.body')
.should('deep.contain', {
latency_good: false,
latency_fair: false,
latency_high: false,
proficiency_beginner: false,
proficiency_intermediate: false,
proficiency_expert: false,
instruments: [{ value: 'drums', label: 'Drums' }],
genres: ['pop'],
from_location: false
});
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '6th API call has data - (prefethed)');
});
});
});
});

View File

@ -1,31 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Unsubscribe from email link', () => {
beforeEach(() => {
// cy.intercept('POST', /\S+\/unsubscribe_user_match\/\S+/, { statusCode: 200, body: { ok: true } });
const currentUser = makeFakeUser({
email: 'sam@example.com'
});
cy.stubAuthenticate({ ...currentUser });
cy.intercept('POST', /\S+\/unsubscribe\/\S+/, { statusCode: 200, body: { ok: true } });
})
it("redirect to home page if tok is not provided", () => {
cy.visit('/unsubscribe');
cy.location('pathname').should('eq', '/errors/404');
});
it("show unsubscribed message", () => {
cy.visit('/unsubscribe/123');
// cy.location('search')
// .should('equal', '?tok=123')
// .then((s) => new URLSearchParams(s))
// .invoke('get', 'tok')
// .should('equal', '123')
cy.contains("You have successfully unsubscribed from JamKazam emails.").should('be.visible');
cy.contains("Loading...").should('not.exist');
});
})

Some files were not shown because too many files have changed in this diff Show More