Merged in feature/user_recommendations_email (pull request #41)
WIP: Feature/user recommendations email
This commit is contained in:
commit
84e3609602
|
|
@ -0,0 +1 @@
|
|||
2.4.1
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const showSidePanelContent = () => {
|
||||
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
|
||||
cy.get('[data-testid=profileSidePanel] .modal-body p').within(() => {
|
||||
cy.get('[data-testid=profileSidePanel] .modal-body p').first().within(() => {
|
||||
cy.contains('Location: Denver, CO, US')
|
||||
.and('contain', 'Skill Level: Professional')
|
||||
.and('contain', 'Joined JamKazam: 08-26-2021')
|
||||
|
|
@ -11,7 +11,7 @@ const showSidePanelContent = () => {
|
|||
cy.get('.latency-badge').contains('UNKNOWN');
|
||||
});
|
||||
|
||||
cy.get('[data-testid=profileSidePanel] .modal-body').within(() => {
|
||||
cy.get('[data-testid=profileSidePanel] .modal-body').first().within(() => {
|
||||
cy.get('[data-testid=biography]').contains('Biography of Test User1');
|
||||
|
||||
//instruments
|
||||
|
|
@ -116,7 +116,7 @@ describe('Friends page with data', () => {
|
|||
cy.viewport('macbook-13');
|
||||
});
|
||||
|
||||
it.only('paginate', () => {
|
||||
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();
|
||||
|
|
@ -139,9 +139,10 @@ describe('Friends page with data', () => {
|
|||
|
||||
it('click profile name', () => {
|
||||
//open side panel by clicking name
|
||||
cy.get('[data-testid=peopleListTable]').within(() => {
|
||||
cy.contains('Test User1').click();
|
||||
});
|
||||
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();
|
||||
});
|
||||
|
|
@ -260,12 +261,13 @@ describe('Friends page with data', () => {
|
|||
|
||||
//it.skip('click message button', () => {});
|
||||
|
||||
it('paginate', () => {
|
||||
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');
|
||||
});
|
||||
|
||||
|
|
@ -341,11 +343,11 @@ describe('Friends page with data', () => {
|
|||
cy.contains('Send a message').should('exist');
|
||||
});
|
||||
|
||||
it('is disabled for non friends', () => {
|
||||
it('is not disabled for non friends', () => {
|
||||
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||
.eq(1)
|
||||
.find('[data-testid=message]')
|
||||
.should('be.disabled');
|
||||
.should('not.be.disabled');
|
||||
//cy.contains('You can message this user once you are friends').should('exist')
|
||||
});
|
||||
|
||||
|
|
@ -446,6 +448,31 @@ describe('Friends page with data', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('coming from email links', () => {
|
||||
it.only("opens details sidebar", () => {
|
||||
cy.visit('/friends?open=details&id=1');
|
||||
showSidePanelContent();
|
||||
});
|
||||
|
||||
it.only("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.only("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();
|
||||
|
|
@ -529,7 +556,7 @@ describe('Friends page with data', () => {
|
|||
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 => {
|
||||
|
|
@ -580,4 +607,6 @@ describe('Friends page with data', () => {
|
|||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
/// <reference types="cypress" />
|
||||
|
||||
describe('Unsubscribe from email link', () => {
|
||||
beforeEach(() =>{
|
||||
cy.intercept('POST', /\S+\/unsubscribe_user_match\/\S+/, { statusCode: 200, body: { ok: true } });
|
||||
})
|
||||
|
||||
it("redirect to home page if tok is not provided", () => {
|
||||
cy.visit('/unsubscribe');
|
||||
cy.location('pathname').should('eq', '/');
|
||||
});
|
||||
|
||||
it.only("show unsubscribed message", () => {
|
||||
cy.visit('/unsubscribe?tok=123');
|
||||
cy.location('search')
|
||||
.should('equal', '?tok=123')
|
||||
.then((s) => new URLSearchParams(s))
|
||||
.invoke('get', 'tok')
|
||||
.should('equal', '123')
|
||||
cy.contains("successfully unsubscribed")
|
||||
});
|
||||
|
||||
})
|
||||
|
|
@ -26,6 +26,7 @@ import JKPrivacy from '../page/JKPrivacy';
|
|||
import JKPeopleFilter from '../page/JKPeopleFilter';
|
||||
import JKNotifications from '../page/JKNotifications';
|
||||
import JKMessageModal from '../profile/JKMessageModal';
|
||||
import JKUnsubscribe from '../page/JKUnsubscribe';
|
||||
|
||||
//import loadable from '@loadable/component';
|
||||
//const DashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes'));
|
||||
|
|
@ -174,6 +175,7 @@ function JKDashboardMain() {
|
|||
<Route path="/" exact component={HomePage} />
|
||||
<Route path="/privacy" component={JKPrivacy} />
|
||||
<Route path="/help" component={JKHelp} />
|
||||
<Route path="/unsubscribe" exact component={JKUnsubscribe} />
|
||||
<PrivateRoute path="/friends" component={JKPeopleFilter} />
|
||||
<PrivateRoute path="/notifications" component={JKNotifications} />
|
||||
{/*Redirect*/}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ const JKPeople = ({ className, onPageChange }) => {
|
|||
|
||||
useEffect(() => {
|
||||
try {
|
||||
console.log("DEBUG======", page, hasNext);
|
||||
onPageChange(page, hasNext)
|
||||
} catch (error) {
|
||||
console.log('Error fetching people', error);
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@ const JKPeopleList = ({ people, goNextPage, hasNext, isLoading }) => {
|
|||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
|
||||
|
||||
{hasNext && (
|
||||
<Button color="primary" outline={true} onClick={() => goNextPage()} disabled={isLoading} data-testid="paginate-next-page">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Row, Col } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
|
@ -8,6 +8,7 @@ import { fetchPerson } from '../../store/features/peopleSlice';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import { useBrowserQuery } from '../../context/BrowserQuery';
|
||||
|
||||
import JKProfileSidePanel from '../profile/JKProfileSidePanel';
|
||||
import JKProfileAvatar from '../profile/JKProfileAvatar';
|
||||
|
|
@ -28,22 +29,36 @@ const JKPerson = props => {
|
|||
const { greaterThan } = useResponsive()
|
||||
|
||||
const toggleMoreDetails = async e => {
|
||||
e.preventDefault();
|
||||
if(e)
|
||||
e.preventDefault();
|
||||
try {
|
||||
await dispatch(fetchPerson({ userId: id })).unwrap();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
setShowSidePanel(prev => !prev);
|
||||
};
|
||||
|
||||
const queryString = useBrowserQuery();
|
||||
|
||||
useEffect(() => {
|
||||
const openWin = queryString.get('open');
|
||||
const userId = queryString.get('id')
|
||||
//showing user more details if directly reqested to do so
|
||||
//by query string params (coming from weekly new user match email link)
|
||||
if(openWin && userId && userId === id){
|
||||
if(openWin === 'details' || openWin === 'connect'){
|
||||
toggleMoreDetails()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{greaterThan.sm ? (
|
||||
<tr className="align-middle" key={`people-list-item-${id}`}>
|
||||
<td className="text-nowrap">
|
||||
<a href="/#" onClick={toggleMoreDetails} className="d-flex align-items-center mb-1 fs-0">
|
||||
<a href="/#" onClick={toggleMoreDetails} className="d-flex align-items-center mb-1 fs-0 person-link">
|
||||
<div className="avatar avatar-xl">
|
||||
<JKProfileAvatar url={photo_url} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Card, CardBody, CardText, CardTitle } from 'reactstrap';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useBrowserQuery } from '../../context/BrowserQuery';
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
const unsubscribeFromNewUsersWeeklyEmail = (token) => {
|
||||
|
||||
const baseUrl = process.env.REACT_APP_LEGACY_BASE_URL
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(`${baseUrl}/unsubscribe_user_match/${token}`,
|
||||
{ method: 'POST' }
|
||||
).then(response => {
|
||||
if (response.ok) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function JKUnsubscribe() {
|
||||
const {t} = useTranslation()
|
||||
const queryObj = useBrowserQuery();
|
||||
const history = useHistory()
|
||||
const [success, setSuccess] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const token = queryObj.get('tok')
|
||||
if(token){
|
||||
unsubscribeFromNewUsersWeeklyEmail(token)
|
||||
.then((resp) => {
|
||||
if(resp.ok){
|
||||
setSuccess(true)
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(error))
|
||||
}else{
|
||||
history.push('/')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<Card color={ success ? 'success' : 'light' } style={{ width: '25rem', margin: '2rem auto' }}>
|
||||
<CardBody>
|
||||
<CardTitle className="mb-2">Unsubscribe From Weekly Email</CardTitle>
|
||||
<CardText>
|
||||
{
|
||||
success?
|
||||
'You have successfully unsubscribed from weekly emails on newly joined musicians having low internet latency to you.' :
|
||||
'Unsubscribing...'
|
||||
}
|
||||
</CardText>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default JKUnsubscribe
|
||||
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { addFriend as connect, removeFriend as disconnect } from '../../helpers/rest';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Modal, ModalBody, ModalHeader, ModalFooter, Button } from 'reactstrap';
|
||||
import { useBrowserQuery } from '../../context/BrowserQuery';
|
||||
|
||||
const JKConnectButton = props => {
|
||||
const { user, currentUser, addContent, removeContent, cssClasses } = props;
|
||||
|
|
@ -15,6 +16,21 @@ const JKConnectButton = props => {
|
|||
setPendingFriendRequest(user.pending_friend_request);
|
||||
}, [user]);
|
||||
|
||||
const queryString = useBrowserQuery();
|
||||
|
||||
useEffect(() => {
|
||||
const openWin = queryString.get('open');
|
||||
const userId = queryString.get('id')
|
||||
//sending friend request if directly reqested to do so
|
||||
//by query string params (coming from weekly new user match email link)
|
||||
if(openWin && userId && userId === user.id && !user.isFriend && !user.pending_friend_request){
|
||||
if(openWin === 'connect'){
|
||||
addFriend();
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
const addFriend = () => {
|
||||
setShowConfirmModal(!showConfirmModal);
|
||||
setPendingFriendRequest(true);
|
||||
|
|
|
|||
|
|
@ -1,21 +1,38 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Button, Tooltip } from "reactstrap";
|
||||
import JKMessageModal from './JKMessageModal';
|
||||
import { useBrowserQuery } from '../../context/BrowserQuery';
|
||||
|
||||
const JKMessageButton = props => {
|
||||
const { currentUser, user, cssClasses, children, size, color, outline } = props;
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [isFriend, setIsFriend] = useState(false);
|
||||
//const [isFriend, setIsFriend] = useState(false);
|
||||
const [pendingFriendRequest, setPendingFriendRequest] = useState(false);
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false);
|
||||
|
||||
const toggleTooltip = () => setTooltipOpen(!tooltipOpen);
|
||||
|
||||
useEffect(() => {
|
||||
setIsFriend(user.is_friend);
|
||||
//setIsFriend(user.is_friend);
|
||||
setPendingFriendRequest(user.pending_friend_request);
|
||||
}, [user]);
|
||||
|
||||
|
||||
const queryString = useBrowserQuery();
|
||||
|
||||
useEffect(() => {
|
||||
const openWin = queryString.get('open');
|
||||
const userId = queryString.get('id')
|
||||
//openning chat window if directly reqested to do so
|
||||
//by query string params (coming from weekly new user match email link)
|
||||
if(openWin && userId && userId === user.id){
|
||||
if(openWin === 'message'){
|
||||
setShowModal(!showModal)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<JKMessageModal show={showModal} setShow={setShowModal} user={user} currentUser={currentUser} />
|
||||
|
|
@ -27,7 +44,7 @@ const JKMessageButton = props => {
|
|||
outline={outline}
|
||||
className={cssClasses}
|
||||
data-testid="message"
|
||||
disabled={!isFriend}
|
||||
//disabled={!isFriend}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
|
|
@ -37,9 +54,10 @@ const JKMessageButton = props => {
|
|||
target={"text-message-user-" + user.id}
|
||||
toggle={toggleTooltip}
|
||||
>
|
||||
{
|
||||
{/* {
|
||||
isFriend ? 'Send a message' : 'You can message this user once you are friends'
|
||||
}
|
||||
} */}
|
||||
Send a message
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react'
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const BrowserQueryContext = React.createContext(null)
|
||||
|
||||
export const BrowserQueryProvider = ({children}) => {
|
||||
|
||||
function useQuery() {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
}
|
||||
|
||||
const queryObj = useQuery();
|
||||
|
||||
return(
|
||||
<BrowserQueryContext.Provider value={ queryObj }>
|
||||
{ children }
|
||||
</BrowserQueryContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useBrowserQuery = () => React.useContext(BrowserQueryContext)
|
||||
|
|
@ -128,4 +128,4 @@ export const deleteNotification = (userId, notificationId) => {
|
|||
.then(response => resolve(response))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import commonTranslationsEN from './locales/en/common.json'
|
|||
import homeTranslationsEN from './locales/en/home.json'
|
||||
import peopleTranslationsEN from './locales/en/people.json'
|
||||
import authTranslationsEN from './locales/en/auth.json'
|
||||
import unsubscribeTranslationsEN from './locales/en/unsubscribe.json'
|
||||
|
||||
import commonTranslationsES from './locales/es/common.json'
|
||||
import homeTranslationsES from './locales/es/home.json'
|
||||
import peopleTranslationsES from './locales/es/people.json'
|
||||
import authTranslationsES from './locales/es/auth.json'
|
||||
import unsubscribeTranslationsES from './locales/es/unsubscribe.json'
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
fallbackLng: 'en',
|
||||
|
|
@ -20,14 +22,16 @@ i18n.use(initReactI18next).init({
|
|||
common: commonTranslationsEN,
|
||||
home: homeTranslationsEN,
|
||||
people: peopleTranslationsEN,
|
||||
auth: authTranslationsEN
|
||||
auth: authTranslationsEN,
|
||||
unsubscribe: unsubscribeTranslationsEN,
|
||||
},
|
||||
es: {
|
||||
//translations: require('./locales/es/translations.json')
|
||||
common: commonTranslationsES,
|
||||
home: homeTranslationsES,
|
||||
people: peopleTranslationsES,
|
||||
auth: authTranslationsES
|
||||
auth: authTranslationsES,
|
||||
unsubscribe: unsubscribeTranslationsES,
|
||||
}
|
||||
},
|
||||
//ns: ['translations'],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"page_title": "Unsubscribe"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"page_title": "Unsubscribe"
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, createContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import DashboardMain from '../components/dashboard/JKDashboardMain';
|
||||
import UserAuth from '../context/UserAuth';
|
||||
import { BrowserQueryProvider } from '../context/BrowserQuery';
|
||||
|
||||
const DashboardLayout = ({ location }) => {
|
||||
useEffect(() => {
|
||||
|
|
@ -11,7 +12,9 @@ const DashboardLayout = ({ location }) => {
|
|||
|
||||
return (
|
||||
<UserAuth path={location.pathname}>
|
||||
<DashboardMain />
|
||||
<BrowserQueryProvider>
|
||||
<DashboardMain />
|
||||
</BrowserQueryProvider>
|
||||
</UserAuth>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ end
|
|||
gem 'activerecord', '= 4.2.8'
|
||||
gem 'railties', '= 4.2.8'
|
||||
gem 'actionmailer', '= 4.2.8'
|
||||
gem 'actionview', '= 4.2.8'
|
||||
gem 'rails-observers', '0.1.2'
|
||||
gem 'protected_attributes' # needed to support attr_accessible
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ gem 'sendgrid_toolkit', '>= 1.1.1'
|
|||
gem 'stripe'
|
||||
gem 'zip-codes'
|
||||
|
||||
|
||||
gem 'elasticsearch'
|
||||
|
||||
gem 'logging', '1.7.2'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
class UserMatchEmailSendingsDefaultSentUserIds < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute("ALTER TABLE public.user_match_email_sendings
|
||||
ALTER COLUMN sent_user_ids SET DEFAULT array[]::varchar[];")
|
||||
end
|
||||
|
||||
def self.down
|
||||
execute("ALTER TABLE public.user_match_email_sendings
|
||||
ALTER COLUMN sent_user_ids DROP DEFAULT;")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class AddSubscribeEmailForUserMatch < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute("ALTER TABLE users ADD COLUMN subscribe_email_for_user_match BOOLEAN; UPDATE users SET subscribe_email_for_user_match = TRUE;")
|
||||
|
||||
end
|
||||
def self.down
|
||||
execute("ALTER TABLE users DROP COLUMN subscribe_email_for_user_match;")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
class CreateUserMatchEmailSendings < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute(<<-SQL
|
||||
CREATE TABLE public.user_match_email_sendings (
|
||||
id character varying(64) DEFAULT public.uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||
sent_user_ids text,
|
||||
total_recipients integer,
|
||||
created_at timestamp without time zone DEFAULT now() NOT NULL,
|
||||
completed_at timestamp without time zone
|
||||
);
|
||||
SQL
|
||||
)
|
||||
end
|
||||
|
||||
def self.down
|
||||
execute("DROP TABLE public.user_match_email_sendings")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class AddUserMatchEmailSentAt < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute("ALTER TABLE users ADD COLUMN user_match_email_sent_at timestamp without time zone;")
|
||||
end
|
||||
|
||||
def self.down
|
||||
execute("ALTER TABLE users DROP COLUMN user_match_email_sent_at;")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
class AddFailCountAndExceptionDetailToUserMatchEmailSendings < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute("ALTER TABLE public.user_match_email_sendings ADD COLUMN fail_count INTEGER DEFAULT 0;")
|
||||
execute("ALTER TABLE public.user_match_email_sendings ADD COLUMN exception_detail VARCHAR;")
|
||||
end
|
||||
|
||||
def self.down
|
||||
execute("ALTER TABLE public.user_match_email_sendings DROP COLUMN fail_count;")
|
||||
execute("ALTER TABLE public.user_match_email_sendings DROP COLUMN exception_detail;")
|
||||
end
|
||||
end
|
||||
|
|
@ -30,6 +30,7 @@ require 'tzinfo'
|
|||
require 'stripe'
|
||||
require 'zip-codes'
|
||||
require 'email_validator'
|
||||
require 'action_view'
|
||||
|
||||
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
||||
require "jam_ruby/lib/timezone"
|
||||
|
|
@ -73,6 +74,7 @@ require "jam_ruby/resque/scheduled/hourly_job"
|
|||
require "jam_ruby/resque/scheduled/minutely_job"
|
||||
require "jam_ruby/resque/scheduled/daily_session_emailer"
|
||||
require "jam_ruby/resque/scheduled/new_musician_emailer"
|
||||
require "jam_ruby/resque/scheduled/new_musician_match_emailer"
|
||||
require "jam_ruby/resque/scheduled/music_session_reminder"
|
||||
require "jam_ruby/resque/scheduled/music_session_scheduler"
|
||||
require "jam_ruby/resque/scheduled/active_music_session_cleaner"
|
||||
|
|
@ -116,6 +118,8 @@ require "jam_ruby/lib/desk_multipass"
|
|||
require "jam_ruby/lib/ip"
|
||||
require "jam_ruby/lib/subscription_message"
|
||||
require "jam_ruby/lib/stats.rb"
|
||||
require "jam_ruby/lib/email_new_musician_match"
|
||||
require "jam_ruby/lib/musician_filter"
|
||||
require "jam_ruby/amqp/amqp_connection_manager"
|
||||
require "jam_ruby/database"
|
||||
require "jam_ruby/message_factory"
|
||||
|
|
@ -339,6 +343,7 @@ require "jam_ruby/models/mobile_recording_upload"
|
|||
require "jam_ruby/models/temp_token"
|
||||
require "jam_ruby/models/ad_campaign"
|
||||
require "jam_ruby/models/user_asset"
|
||||
require "jam_ruby/models/user_match_email_sending"
|
||||
|
||||
|
||||
include Jampb
|
||||
|
|
|
|||
|
|
@ -3,4 +3,30 @@ module MailerHelper
|
|||
@vars = Hash.new unless @vars
|
||||
@vars[arg1] = arg2[0]
|
||||
end
|
||||
|
||||
def latency_info(latency_data)
|
||||
latency_scores = {
|
||||
good: { label: 'GOOD', min: 0, max: 40 },
|
||||
fair: { label: 'FAIR', min: 40, max: 60 },
|
||||
high: { label: 'HIGH', min: 60, max: 10000000 },
|
||||
me: { label: 'ME', min: -1, max: -1 },
|
||||
unknown: { label: 'UNKNOWN', min: -2, max: -2 }
|
||||
}
|
||||
|
||||
total_latency = latency_data[:ars_internet_latency].round + latency_data[:audio_latency].round;
|
||||
|
||||
lbl = if (total_latency >= latency_scores[:good][:min] && total_latency <= latency_scores[:good][:max])
|
||||
latency_scores[:good][:label]
|
||||
elsif (total_latency > latency_scores[:fair][:min] && total_latency <= latency_scores[:fair][:max])
|
||||
latency_scores[:fair][:label]
|
||||
elsif (total_latency > latency_scores[:fair][:min] && total_latency <= latency_scores[:fair][:max])
|
||||
latency_scores[:fair][:label]
|
||||
elsif (total_latency > latency_scores[:high][:min])
|
||||
latency_scores[:high][:label]
|
||||
else
|
||||
latency_scores[:unknown][:label]
|
||||
end
|
||||
|
||||
lbl
|
||||
end
|
||||
end
|
||||
|
|
@ -10,6 +10,8 @@ module JamRuby
|
|||
include SendGrid
|
||||
include MailerHelper
|
||||
|
||||
helper MailerHelper
|
||||
|
||||
layout "user_mailer"
|
||||
|
||||
DEFAULT_SENDER = "JamKazam <noreply@jamkazam.com>"
|
||||
|
|
@ -399,6 +401,23 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def new_musicians_match(user, musicians_data)
|
||||
@user, @musicians_data = user, musicians_data
|
||||
@instrument_proficiencies = {
|
||||
'1': 'Beginner',
|
||||
'2': 'Intermediate',
|
||||
'3': 'Expert'
|
||||
}
|
||||
sendgrid_recipients([user.email])
|
||||
sendgrid_substitute('@USERID', [user.id])
|
||||
sendgrid_unique_args :type => "new_musicians_match"
|
||||
|
||||
mail(:to => user.email, :subject => EmailNewMusicianMatch.subject) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
#################################### NOTIFICATION EMAILS ####################################
|
||||
def friend_request(user, msg, friend_request_id)
|
||||
return if !user.subscribe_email
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
<style>
|
||||
.container{
|
||||
width: 65%;
|
||||
margin: 0 auto;
|
||||
padding: 2em;
|
||||
background-color: #fff;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.logo{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.search-btn{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.search-btn a{
|
||||
color: #fff;
|
||||
background-color: #2c7be5;
|
||||
border-color: #2c7be5;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.3125rem 1rem;
|
||||
line-height: 2.5;
|
||||
font-size: 1em;
|
||||
border-radius: 0.25rem;
|
||||
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
row-gap: 1em;
|
||||
column-gap: 1em;
|
||||
margin-top: 2em;
|
||||
}
|
||||
.row > div{
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.row .photo{
|
||||
flex-grow: 1;
|
||||
}
|
||||
.row .details{
|
||||
flex-grow: 2;
|
||||
}
|
||||
.row .instruments{
|
||||
flex-grow: 2;
|
||||
}
|
||||
.row .links{
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
<section class="container">
|
||||
<div class="logo">
|
||||
<img src="<%= image_url("JK_Logo_blue-2021.png", host: APP_CONFIG.action_mailer.assets_host ) -%>" alt="JamKazam Logo" />
|
||||
</div>
|
||||
<p>
|
||||
Hi <%= @user.first_name -%>,
|
||||
</p>
|
||||
<p>
|
||||
The following musicians have joined JamKazam within the last week and have low internet
|
||||
latency to you that will support enjoyable sessions. If you'd like to make more musical connections,
|
||||
we encourage you to use the links below to send these new users a welcome message and
|
||||
perhaps arrange a session to play together.
|
||||
</p>
|
||||
<%
|
||||
@musicians_data.each do | data | -%>
|
||||
<%
|
||||
musicians = data[:musicians]
|
||||
latencies = data[:latencies]
|
||||
musicians.each do |musician|
|
||||
latency = latencies.find{|l| l[:user_id] == musician.id }
|
||||
-%>
|
||||
<div class="row">
|
||||
<div class="photo">
|
||||
<%= image_tag musician.photo_url.blank?? "avatar.png" : musician.photo_url, height: '32', width: '32', host: APP_CONFIG.action_mailer.assets_host -%>
|
||||
</div>
|
||||
<div class="details">
|
||||
<div><strong><%= musician.first_name %> <%= musician.last_name %></strong></div>
|
||||
<div>Latency To You: <%= latency_info(latency) %></div>
|
||||
<% if musician.last_active_timestamp -%>
|
||||
<div>Last Active On: <%= time_ago_in_words(Time.at(musician.last_active_timestamp)) %> ago</div>
|
||||
<% end -%>
|
||||
</div>
|
||||
<div class="instruments">
|
||||
<% musician.musician_instruments.each do |mi| -%>
|
||||
<div>
|
||||
<%= mi.description %>: <%= @instrument_proficiencies[mi.proficiency_level.to_s.to_sym] %>
|
||||
</div>
|
||||
<% end -%>
|
||||
</div>
|
||||
<div class="links">
|
||||
<div><a href="<%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=details" target="_blank">View Profile</a></div>
|
||||
<div><a href="<%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=message" target="_blank">Send Message</a></div>
|
||||
<div><a href="<%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=connect" target="_blank">Send Friend Request</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
<br />
|
||||
<p>
|
||||
To find great musical matches across the entire JamKazam commiunity and make new connections, use the button below to access our musician search feature.
|
||||
This let you filter JamKazammers by latency, instruments, skill level, genre interests, last active day and more.
|
||||
</p>
|
||||
<br />
|
||||
<div class="search-btn">
|
||||
<a class="button" href="<%= APP_CONFIG.spa_origin -%>/friends" target="_blank">Search JamKazam Musicians</a>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<% if !@user.anonymous? %>
|
||||
Hi <%= @user.first_name %>,
|
||||
<% end %>
|
||||
|
||||
The following musicians have joined JamKazam within the last week and have low internet latency to you that will support enjoyable sessions. If you'd like to make more musical connections, we encourage you to use the links below to send these new users a welcome message and perhaps arrange a session to play together.
|
||||
|
||||
<%
|
||||
@musicians_data.each do | data | -%>
|
||||
<%
|
||||
musicians = data[:musicians]
|
||||
latencies = data[:latencies]
|
||||
musicians.each do |musician|
|
||||
latency = latencies.find{|l| l[:user_id] == musician.id }
|
||||
-%>
|
||||
<%= musician.first_name %> <%= musician.last_name %>
|
||||
Latency To You: <%= latency_info(latency) %>
|
||||
<% if musician.last_active_timestamp -%>
|
||||
Last Active On: <%= time_ago_in_words(Time.at(musician.last_active_timestamp)) %> ago
|
||||
<% end -%>
|
||||
<% musician.musician_instruments.each do |mi| -%>
|
||||
<%= mi.description %> (<%= @instrument_proficiencies[mi.proficiency_level.to_s.to_sym] %>)
|
||||
<% end -%>
|
||||
View Profile: <%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=details
|
||||
Send Message: <%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=message
|
||||
Send Friend Request: <%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=connect
|
||||
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
|
||||
To find great musical matches across the entire JamKazam commiunity and make new connections, use the link below to access our musician search feature. This let you filter JamKazammers by latency, instruments, skill level, genre interests, last active day and more.
|
||||
|
||||
Search JamKazam Musicians: <%= APP_CONFIG.spa_origin -%>/friends
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>JamKazam</title>
|
||||
|
||||
<style>
|
||||
body{
|
||||
margin-top:10px;
|
||||
font-family:Arial, Helvetica, sans-serif;
|
||||
}
|
||||
p {
|
||||
margin-bottom:0px;
|
||||
line-height:140%;
|
||||
}
|
||||
|
||||
footer{
|
||||
text-align: center;
|
||||
padding-top: 1em;
|
||||
color: #5e6e82;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body bgcolor="#eee" >
|
||||
<%= yield -%>
|
||||
<body>
|
||||
<footer>
|
||||
<p>
|
||||
Copyright © <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||
</p>
|
||||
<p style="text-decoration:none;">
|
||||
You are receiving this email because you created a JamKazam account with the email address <%= @user.email -%>
|
||||
</p>
|
||||
<p><a href="<%= APP_CONFIG.spa_origin -%>/unsubscribe?tok=<%= @user.unsubscribe_token %>">Unsubscribe from this weekly new musician notification</a></p>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<% if @batch_body %>
|
||||
<%= Nokogiri::HTML(@batch_body).text %>
|
||||
<% else %>
|
||||
<%= yield %>
|
||||
<% end %>
|
||||
|
||||
<% unless @user.nil? || @suppress_user_has_account_footer == true %>
|
||||
This email was sent to you because you have an account at JamKazam / https://www.jamkazam.com. To unsubscribe: https://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>.
|
||||
<% end %>
|
||||
|
||||
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
module JamRuby
|
||||
class EmailNewMusicianMatch
|
||||
|
||||
PER_PAGE = 150
|
||||
LIMIT = 20
|
||||
JOINED_WITHIN_DAYS = 7
|
||||
ACTIVE_WITHIN_DAYS = 30
|
||||
|
||||
PRIORITY_RECIPIENTS = %w(seth@jamkazam.com david@jamkazam.com peter@jamkazam.com nuwan@jamkazam.com).freeze
|
||||
|
||||
def self.subject
|
||||
"New musicians with good Internet connections to you have joined JamKazam!"
|
||||
end
|
||||
|
||||
def self.send_new_musicians
|
||||
|
||||
params = {
|
||||
latency_good: true,
|
||||
latency_fair: true,
|
||||
latency_high: false,
|
||||
proficiency_beginner: true,
|
||||
proficiency_intermediate: true,
|
||||
proficiency_expert: true,
|
||||
from_location: false,
|
||||
joined_within_days: JOINED_WITHIN_DAYS,
|
||||
active_within_days: ACTIVE_WITHIN_DAYS,
|
||||
limit: PER_PAGE
|
||||
}
|
||||
|
||||
email_sending = UserMatchEmailSending.most_recent
|
||||
if email_sending.nil? || email_sending.completed?
|
||||
email_sending = UserMatchEmailSending.create
|
||||
end
|
||||
|
||||
begin
|
||||
|
||||
recipients = User.where("users.subscribe_email = ? AND
|
||||
users.subscribe_email_for_user_match = ?
|
||||
AND NOT COALESCE(users.user_match_email_sent_at, ?) > ?",
|
||||
true, true, 7.days.ago, 6.days.ago).where.not(id: email_sending.sent_user_ids).order("
|
||||
CASE WHEN users.email IN ('#{PRIORITY_RECIPIENTS.map {|str| "\"#{str}\""}.join(',')}')
|
||||
THEN 0 ELSE 1 END, last_active_at DESC").select("users.*,
|
||||
GREATEST(updated_at, last_jam_updated_at) AS last_active_at").limit(LIMIT)
|
||||
|
||||
AdminMailer.ugly({to: APP_CONFIG.user_match_monitoring_email,
|
||||
subject:"Weekly user match email sending job started.",
|
||||
body: "#{email_sending.sent_user_ids.any?? "This job is resuming. It was originally started at #{email_sending.created_at} and has been sent to #{email_sending.sent_user_ids.size} user(s) so far." : "This job was started at #{email_sending.created_at}" }. It will send to total of #{ recipients.size } users."}).deliver_now
|
||||
|
||||
recipients.find_each do |user|
|
||||
|
||||
ip_address = user.last_jam_addr.blank?? '127.0.0.1' : IPAddr.new(user.last_jam_addr, Socket::AF_INET).to_s
|
||||
matched_musician_data = []
|
||||
nextOffset = 0
|
||||
|
||||
while !nextOffset.nil? && nextOffset >= 0 do
|
||||
|
||||
params.merge!({ offset: nextOffset })
|
||||
|
||||
results, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, ip_address, params)
|
||||
|
||||
matched_musician_data << [{ musicians: results, latencies: latency_data }] if results && results.size > 0
|
||||
end
|
||||
|
||||
if matched_musician_data.size > 0
|
||||
|
||||
UserMailer.new_musicians_match(user, matched_musician_data).deliver_now
|
||||
|
||||
user.update_column(:user_match_email_sent_at, Time.now)
|
||||
email_sending.sent_user_ids.push(user.id)
|
||||
email_sending.save!
|
||||
end
|
||||
end
|
||||
|
||||
email_sending.total_recipients = email_sending.sent_user_ids.size
|
||||
email_sending.completed_at = Time.now
|
||||
email_sending.save!
|
||||
|
||||
AdminMailer.ugly({
|
||||
to: APP_CONFIG.user_match_monitoring_email,
|
||||
subject:"Weekly user match email sending completed.",
|
||||
body: "Weekly email sending job was completed at #{Time.now}. It was sent to #{email_sending.sent_user_ids.size} user(s)."
|
||||
}).deliver_now
|
||||
|
||||
rescue => exception
|
||||
begin
|
||||
fail_count = email_sending.fail_count
|
||||
email_sending.update_attributes(fail_count: fail_count + 1, exception_detail: exception.message)
|
||||
|
||||
AdminMailer.ugly({to: APP_CONFIG.user_match_monitoring_email,
|
||||
subject:"Error occured when sending weekly user match email.",
|
||||
body: "An error was encountered at #{Time.now} while sending weekly user match email - #{exception.message}."}).deliver_now
|
||||
rescue
|
||||
Bugsnag.notify(exception)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
module JamRuby
|
||||
class MusicianFilter
|
||||
|
||||
LATENCY_SCORES = {
|
||||
good: { label: 'GOOD', min: 0, max: 40 },
|
||||
fair: { label: 'FAIR', min: 40, max: 60 },
|
||||
high: { label: 'HIGH', min: 60, max: 10000000 },
|
||||
me: { label: 'ME', min: -1, max: -1 },
|
||||
unknown: { label: 'UNKNOWN', min: -2, max: -2 }
|
||||
};
|
||||
|
||||
def self.filter(user, remote_ip, params)
|
||||
#debugger
|
||||
latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good])
|
||||
latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair])
|
||||
latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high])
|
||||
offset = [params[:offset].to_i, 0].max
|
||||
limit = [params[:limit].to_i, 20].max
|
||||
filter_params = {}
|
||||
|
||||
filter_params.merge!(from_location: params[:from_location] ? '1' : '0')
|
||||
|
||||
genres = params[:genres]
|
||||
filter_params.merge!(genres: genres) if genres
|
||||
|
||||
beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner])
|
||||
intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate])
|
||||
expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert])
|
||||
|
||||
proficiency_levels = []
|
||||
proficiency_levels.push(1) if beginner
|
||||
proficiency_levels.push(2) if intermediate
|
||||
proficiency_levels.push(3) if expert
|
||||
|
||||
instruments = params[:instruments]
|
||||
|
||||
if instruments && instruments.any? && proficiency_levels.any?
|
||||
inst = []
|
||||
instruments.each do |ii|
|
||||
proficiency_levels.each do |pl|
|
||||
inst << { id: ii[:value], proficiency: pl}
|
||||
end
|
||||
end
|
||||
filter_params.merge!(instruments: inst)
|
||||
end
|
||||
|
||||
filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank?
|
||||
filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank?
|
||||
|
||||
begin
|
||||
|
||||
#bm = Benchmark.measure do
|
||||
result = JamRuby::MusicianFilter.users_latency_data(user, remote_ip, latency_good, latency_fair, latency_high, filter_params, offset, limit)
|
||||
latency_data = result[:data]
|
||||
nextOffset = result[:next]
|
||||
|
||||
user_ids = latency_data.map{ |l_data| l_data[:user_id] }
|
||||
#end
|
||||
|
||||
# Bugsnag.notify("search_users_benchmark") do |report|
|
||||
# report.severity = "info"
|
||||
# report.add_tab(:benchmark, benchmark: bm.to_s)
|
||||
# end if Rails.env.production?
|
||||
|
||||
sobj = JamRuby::MusicianSearch.user_search_filter(user)
|
||||
search = sobj.user_search_results(user_ids)
|
||||
|
||||
[search, latency_data, nextOffset]
|
||||
|
||||
rescue => exception
|
||||
logger.debug("Latency exception: #{exception.message}")
|
||||
Bugsnag.notify(exception) do |report|
|
||||
report.severity = "error"
|
||||
report.add_tab(:latency, {
|
||||
params: params,
|
||||
user_id: user.id,
|
||||
name: user.name,
|
||||
url: filter_latency_url,
|
||||
})
|
||||
end
|
||||
raise exception
|
||||
end
|
||||
end
|
||||
|
||||
def self.users_latency_data(user_obj, remote_ip, latency_good, latency_fair, latency_high, filter_opts, offset, limit)
|
||||
filter_latency_url = "#{APP_CONFIG.latency_data_host}/search_users"
|
||||
|
||||
uri = URI(filter_latency_url)
|
||||
begin
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true if APP_CONFIG.latency_data_host.start_with?("https://")
|
||||
req = Net::HTTP::Post.new(uri)
|
||||
req["Authorization"] = "Basic #{APP_CONFIG.latency_data_host_auth_code}"
|
||||
req["Content-Type"] = "application/json"
|
||||
|
||||
req_params = {
|
||||
my_user_id: user_obj.id,
|
||||
my_public_ip: remote_ip,
|
||||
my_device_id: nil,
|
||||
my_client_id: nil,
|
||||
from_location: filter_opts[:from_location] || "0",
|
||||
offset: offset,
|
||||
limit: limit
|
||||
}
|
||||
|
||||
req_params.merge!(instruments: filter_opts[:instruments]) if filter_opts[:instruments]
|
||||
req_params.merge!(genres: filter_opts[:genres]) if filter_opts[:genres]
|
||||
req_params.merge!(joined_within_days: filter_opts[:joined_within_days]) if filter_opts[:joined_within_days]
|
||||
req_params.merge!(active_within_days: filter_opts[:active_within_days]) if filter_opts[:active_within_days]
|
||||
|
||||
req.body = req_params.to_json
|
||||
|
||||
response = http.request(req)
|
||||
|
||||
if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess)
|
||||
json_body = JSON.parse(response.body)
|
||||
graph_db_users = json_body['users']
|
||||
nextOffset = json_body['next']
|
||||
|
||||
if latency_good || latency_fair || latency_high
|
||||
#fiter by latency params
|
||||
graph_db_users.select! do |user|
|
||||
total_latency = user["ars"]["total_latency"].to_f
|
||||
(total_latency >= LATENCY_SCORES[:good][:min] && total_latency <= LATENCY_SCORES[:good][:max] && latency_good) ||
|
||||
(total_latency > LATENCY_SCORES[:fair][:min] && total_latency <= LATENCY_SCORES[:fair][:max] && latency_fair) ||
|
||||
(total_latency > LATENCY_SCORES[:high][:min] && latency_high)
|
||||
end
|
||||
end
|
||||
|
||||
latency_data = graph_db_users.map { | user |
|
||||
{
|
||||
user_id: user["user_id"],
|
||||
audio_latency: user["audio_latency"].to_f,
|
||||
ars_total_latency: user["ars"]["total_latency"].to_f,
|
||||
ars_internet_latency: user["ars"]["internet_latency"].to_f
|
||||
}
|
||||
}.uniq
|
||||
|
||||
return { data: latency_data, next: nextOffset }
|
||||
else
|
||||
logger.debug("Latency response failed: #{response}")
|
||||
Bugsnag.notify("LatencyResponseFailed") do |report|
|
||||
report.severity = "faliure"
|
||||
report.add_tab(:latency, {
|
||||
user_id: user_obj.id,
|
||||
name: user_obj.name,
|
||||
params: params,
|
||||
url: filter_latency_url,
|
||||
code: response.code,
|
||||
body: response.body,
|
||||
})
|
||||
end
|
||||
end
|
||||
rescue => exception
|
||||
raise exception
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2936,10 +2936,17 @@ module JamRuby
|
|||
def recurly_link_to_account
|
||||
"https://#{APP_CONFIG.recurly_subdomain}.recurly.com/accounts/#{id}"
|
||||
end
|
||||
|
||||
def recurly_link_to_subscription
|
||||
"https://#{APP_CONFIG.recurly_subdomain}.recurly.com/subscriptions/#{recurly_subscription_id}"
|
||||
end
|
||||
|
||||
def last_active_timestamp
|
||||
if updated_at || last_jam_updated_at
|
||||
[updated_at, last_jam_updated_at].compact.max.to_i
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def create_remember_token
|
||||
self.remember_token = SecureRandom.urlsafe_base64
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
module JamRuby
|
||||
class UserMatchEmailSending < ActiveRecord::Base
|
||||
|
||||
serialize :sent_user_ids, Array
|
||||
|
||||
def sent_user_ids=(ids)
|
||||
ids = ids.split(',') if ids.is_a?(String)
|
||||
super(ids)
|
||||
end
|
||||
|
||||
def completed?
|
||||
!completed_at.nil?
|
||||
end
|
||||
|
||||
def self.most_recent
|
||||
UserMatchEmailSending.order(created_at: :desc).first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
module JamRuby
|
||||
class NewMusicianMatchEmailer
|
||||
extend Resque::Plugins::JamLonelyJob
|
||||
|
||||
@queue = :scheduled_new_musician_match_emailer
|
||||
@@log = Logging.logger[NewMusicianMatchEmailer]
|
||||
|
||||
def self.perform
|
||||
@@log.debug("waking up")
|
||||
EmailNewMusicianMatch.send_new_musicians
|
||||
@@log.debug("done")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe EmailNewMusicianMatch do
|
||||
let(:user1) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) }
|
||||
let(:user2) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) }
|
||||
let(:user3) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) }
|
||||
let(:user4) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) }
|
||||
let(:user5) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) }
|
||||
let(:user6) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) }
|
||||
let(:user7) { FactoryGirl.create(:user, subscribe_email: false, user_match_email_sent_at: 7.days.ago) }
|
||||
let(:user8) { FactoryGirl.create(:user, subscribe_email: false, subscribe_email_for_user_match: false, user_match_email_sent_at: 7.days.ago) }
|
||||
let(:user9) { FactoryGirl.create(:user, email: 'seth@jamkazam.com', subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) } #a priority user
|
||||
let(:user10) { FactoryGirl.create(:user, email: 'david@jamkazam.com', subscribe_email: false) } #a priority user. but not included as he has marked not to receive email notifications
|
||||
|
||||
let(:search_result){ [[user1, user2, user3, user4, user5], [user6, user7, user8, user9, user10] ] }
|
||||
|
||||
let (:mail) { double("Mail") }
|
||||
let (:admin_mail) { double("Admin Mail") }
|
||||
|
||||
before(:each) do
|
||||
ActionMailer::Base.delivery_method = :test
|
||||
ActionMailer::Base.perform_deliveries = true
|
||||
ActionMailer::Base.deliveries = []
|
||||
User.delete_all
|
||||
allow(JamRuby::MusicianFilter).to receive(:filter).and_return(search_result)
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
end
|
||||
|
||||
it "notify admin" do
|
||||
allow(AdminMailer).to receive(:ugly).and_return(admin_mail)
|
||||
expect(admin_mail).to receive(:deliver_now).exactly(2).times
|
||||
JamRuby::EmailNewMusicianMatch.send_new_musicians
|
||||
end
|
||||
|
||||
it "does not sent to whom have not been opted to receive emails" do
|
||||
JamRuby::EmailNewMusicianMatch.send_new_musicians
|
||||
ActionMailer::Base.deliveries.map{|d| d['to'].to_s }.include?("david@example.com").should be_falsey
|
||||
end
|
||||
|
||||
it "delivers the new musicians notification email" do
|
||||
allow(UserMailer).to receive(:new_musicians_match).and_return(mail)
|
||||
expect(mail).to receive(:deliver_now).exactly(7).times
|
||||
JamRuby::EmailNewMusicianMatch.send_new_musicians
|
||||
end
|
||||
|
||||
xit "delivers to priority recipients first" do
|
||||
JamRuby::EmailNewMusicianMatch.send_new_musicians
|
||||
ActionMailer::Base.deliveries[1]['to'].to_s.should == "seth@jamkazam.com" #NOTE: the first email is sent to user_match_monitoring_email. The second email should be sent to the first priority user in the priority user list
|
||||
end
|
||||
|
||||
describe "halfway done job" do
|
||||
before(:each) do
|
||||
UserMailer.deliveries.clear
|
||||
allow_any_instance_of(UserMatchEmailSending).to receive(:sent_user_ids).and_return([user1.id, user2.id])
|
||||
end
|
||||
|
||||
it "does not deliver to already delivered users" do
|
||||
allow(UserMailer).to receive(:new_musicians_match).and_return(mail)
|
||||
expect(mail).to receive(:deliver_now).exactly(5).times
|
||||
JamRuby::EmailNewMusicianMatch.send_new_musicians
|
||||
end
|
||||
end
|
||||
|
||||
describe "catching errors" do
|
||||
|
||||
before do
|
||||
JamRuby::MusicianFilter.stub(:filter).and_raise
|
||||
end
|
||||
|
||||
it 'notifies admin about the error' do
|
||||
JamRuby::EmailNewMusicianMatch.send_new_musicians
|
||||
ActionMailer::Base.deliveries.length.should == 2
|
||||
ActionMailer::Base.deliveries[1]['subject'].to_s.should == "Error occured when sending weekly user match email."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
require 'spec_helper'
|
||||
require 'webmock/rspec'
|
||||
|
||||
describe MusicianFilter do
|
||||
let(:latency_data_uri) { /\S+\/search_users/ }
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:remote_ip) { "127.0.0.1" }
|
||||
|
||||
let(:user1) { FactoryGirl.create(:user) }
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
let(:user3) { FactoryGirl.create(:user) }
|
||||
let(:user4) { FactoryGirl.create(:user) }
|
||||
let(:user5) { FactoryGirl.create(:user) }
|
||||
let(:user6) { FactoryGirl.create(:user) }
|
||||
let(:user7) { FactoryGirl.create(:user) }
|
||||
let(:user8) { FactoryGirl.create(:user) }
|
||||
|
||||
let(:response_body) { mock_latency_response([
|
||||
{ user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD
|
||||
{ user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, #GOOD
|
||||
{ user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 }, #FAIR
|
||||
{ user: user4, ars_total_latency: 60.0, ars_internet_latency: 30, audio_latency: 30.0 }, #FAIR
|
||||
{ user: user5, ars_total_latency: 60.1, ars_internet_latency: 30.1, audio_latency: 30 }, #HIGH
|
||||
{ user: user6, ars_total_latency: 100.0, ars_internet_latency: 50.0, audio_latency: 50.0 }, #HIGH
|
||||
{ user: user7, ars_total_latency: -2, ars_internet_latency: -1, audio_latency: -1 }, #UNKNOWN
|
||||
{ user: user8, ars_total_latency: 10, ars_internet_latency: 5, audio_latency: 0 } #GOOD (NOTE: audio_latency from neo4j is 0 here)
|
||||
])
|
||||
}
|
||||
|
||||
let(:response_body_pop) { mock_latency_response([
|
||||
{ user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD
|
||||
{ user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, #GOOD
|
||||
{ user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 }, #FAIR
|
||||
])
|
||||
}
|
||||
|
||||
let(:response_body_pop_and_rap) { mock_latency_response([
|
||||
{ user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD
|
||||
])
|
||||
}
|
||||
|
||||
let(:response_body_drums_intermediate) { mock_latency_response([
|
||||
{ user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 },
|
||||
{ user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 },
|
||||
{ user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 },
|
||||
{ user: user4, ars_total_latency: 60.0, ars_internet_latency: 30, audio_latency: 30.0 }
|
||||
])
|
||||
}
|
||||
|
||||
let(:response_body_drums_violin_expert) { mock_latency_response([
|
||||
{ user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 },
|
||||
{ user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 },
|
||||
])
|
||||
}
|
||||
|
||||
let(:response_body_active_within_one_day) { mock_latency_response([
|
||||
{ user: user4, ars_total_latency: 60.0, ars_internet_latency: 30, audio_latency: 30.0 }, #FAIR
|
||||
])
|
||||
}
|
||||
|
||||
let(:response_body_joined_within_one_day) { mock_latency_response([
|
||||
{ user: user4, ars_total_latency: 60.0, ars_internet_latency: 30, audio_latency: 30.0 }, #FAIR
|
||||
{ user: user5, ars_total_latency: 60.1, ars_internet_latency: 30.1, audio_latency: 30 }, #HIGH
|
||||
])
|
||||
}
|
||||
|
||||
before(:each) do
|
||||
User.delete_all
|
||||
|
||||
stub_request(:post, latency_data_uri)
|
||||
.with(:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'})
|
||||
.to_return( body: response_body, status: 200)
|
||||
|
||||
stub_request(:post, latency_data_uri).
|
||||
with(
|
||||
body: hash_including({ genres: ["pop"]}),
|
||||
:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}
|
||||
)
|
||||
.to_return( body: response_body_pop, status: 200)
|
||||
|
||||
stub_request(:post, latency_data_uri).
|
||||
with(
|
||||
body: hash_including({ genres: ["pop", "rap"]}),
|
||||
:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}
|
||||
)
|
||||
.to_return( body: response_body_pop_and_rap, status: 200)
|
||||
|
||||
stub_request(:post, latency_data_uri).
|
||||
with(
|
||||
body: hash_including({ instruments: [{id: 'drums', proficiency: 2}]}),
|
||||
:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}
|
||||
)
|
||||
.to_return( body: response_body_drums_intermediate, status: 200)
|
||||
|
||||
stub_request(:post, latency_data_uri).
|
||||
with(
|
||||
body: hash_including({ instruments: [{id: 'drums', proficiency: 3}, {id: 'violin', proficiency: 3}]}),
|
||||
:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}
|
||||
)
|
||||
.to_return( body: response_body_drums_violin_expert, status: 200)
|
||||
|
||||
stub_request(:post, latency_data_uri).
|
||||
with(
|
||||
body: hash_including({ active_within_days: 1}),
|
||||
:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}
|
||||
)
|
||||
.to_return( body: response_body_active_within_one_day, status: 200)
|
||||
|
||||
stub_request(:post, latency_data_uri).
|
||||
with(
|
||||
body: hash_including({ joined_within_days: 1 }),
|
||||
:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}
|
||||
)
|
||||
.to_return( body: response_body_joined_within_one_day, status: 200)
|
||||
end
|
||||
|
||||
it "when no latency option is selected" do
|
||||
opts = { latency_good: false, latency_fair: false, latency_high: false }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
puts search.results
|
||||
puts "===="
|
||||
puts latency_data
|
||||
puts "===="
|
||||
puts nextOffset
|
||||
expect(search.results.size).to eq(8)
|
||||
expect(latency_data).not_to eq(nil)
|
||||
expect(latency_data[0][:audio_latency]).not_to eq(nil)
|
||||
expect(latency_data[0][:ars_total_latency]).not_to eq(nil)
|
||||
expect(latency_data[0][:ars_internet_latency]).not_to eq(nil)
|
||||
end
|
||||
|
||||
it "filter musicians for all latency options" do
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: true }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
|
||||
expect(search.results.size).to eq(7)
|
||||
expect(latency_data).not_to eq(nil)
|
||||
expect(latency_data[0][:audio_latency]).not_to eq(nil)
|
||||
expect(latency_data[0][:ars_total_latency]).not_to eq(nil)
|
||||
expect(latency_data[0][:ars_internet_latency]).not_to eq(nil)
|
||||
|
||||
#sort by latency
|
||||
expect(search.results[0][:id]).to eq(user1.id)
|
||||
expect(search.results[1][:id]).to eq(user2.id)
|
||||
expect(search.results[2][:id]).to eq(user3.id)
|
||||
expect(search.results[3][:id]).to eq(user4.id)
|
||||
expect(search.results[4][:id]).to eq(user5.id)
|
||||
expect(search.results[5][:id]).to eq(user6.id)
|
||||
expect(search.results[6][:id]).to eq(user8.id)
|
||||
|
||||
end
|
||||
|
||||
it "filter GOOD latency users" do
|
||||
opts = { latency_good: true, latency_fair: false, latency_high: false }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(3)
|
||||
end
|
||||
|
||||
|
||||
it "filter FAIR latency musicians" do
|
||||
opts = { latency_good: false, latency_fair: true, latency_high: false }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(2)
|
||||
end
|
||||
|
||||
it "filter HIGH latency musicians" do
|
||||
opts = { latency_good: false, latency_fair: false, latency_high: true }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(2)
|
||||
end
|
||||
|
||||
it "filter GOOD and FAIR latency musicians" do
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: false }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(5)
|
||||
end
|
||||
|
||||
it "filter GOOD and HIGH latency musicians" do
|
||||
opts = { latency_good: true, latency_fair: false, latency_high: true }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(5)
|
||||
end
|
||||
|
||||
it "filter GOOD, FAIR and HIGH latency musicians" do
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: true }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(7)
|
||||
end
|
||||
|
||||
it "filter musicians by genres" do
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: true, genres: ['pop'] }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(3)
|
||||
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: true, genres: ['pop', 'rap'] }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(1)
|
||||
end
|
||||
|
||||
it "filter musicians by instruments they play" do
|
||||
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: true, instruments: [{value: "drums", label: "Drums"}], proficiency_intermediate: true }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(4)
|
||||
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: true, instruments: [{value: "drums", label: "Drums"}, {value: 'violin', label: 'Violin'}], proficiency_expert: true }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(2)
|
||||
end
|
||||
|
||||
it "filter musicians by days ago that they joined" do
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: true, joined_within_days: 1 }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(2)
|
||||
end
|
||||
|
||||
it "finds user updated_at is within a day ago" do
|
||||
opts = { latency_good: true, latency_fair: true, latency_high: true, active_within_days: 1 }
|
||||
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts)
|
||||
expect(search.results.size).to eq(1)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
require "spec_helper"
|
||||
|
||||
describe "RenderMailers", :slow => true do
|
||||
describe "RenderMailers" do
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:school) {FactoryGirl.create(:school, education:true)}
|
||||
|
|
@ -16,7 +16,7 @@ describe "RenderMailers", :slow => true do
|
|||
describe "UserMailer emails" do
|
||||
|
||||
before(:each) do
|
||||
user.update_email = "my_new_email@jamkazam.com"
|
||||
#user.update_email = "my_new_email@jamkazam.com"
|
||||
UserMailer.deliveries.clear
|
||||
end
|
||||
|
||||
|
|
@ -53,6 +53,32 @@ describe "RenderMailers", :slow => true do
|
|||
it { @filename="friend_request"; UserMailer.friend_request(user, 'So and so has sent you a friend request.', friend_request.id).deliver_now }
|
||||
end
|
||||
|
||||
# describe "sending about new musicians with good latency to the user", focus: true do
|
||||
# let(:user) { User.find_by(email: "nuwan@jamkazam.com") }
|
||||
# let(:params) {
|
||||
# {latency_good: true,
|
||||
# latency_fair: true,
|
||||
# latency_high: false,
|
||||
# proficiency_beginner: true,
|
||||
# proficiency_intermediate: true,
|
||||
# proficiency_expert: true,
|
||||
# from_location: false,
|
||||
# joined_within_days: "",
|
||||
# active_within_days: "",
|
||||
# limit: 20,
|
||||
# offset: 0}
|
||||
# }
|
||||
# let(:ip){ "127.0.0.1" }
|
||||
|
||||
# it{
|
||||
# @filename="new_musicians_match"
|
||||
# search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, ip, params)
|
||||
# matched_musician_data = []
|
||||
# matched_musician_data << [search, latency_data]
|
||||
# UserMailer.new_musicians_match(user, matched_musician_data).deliver_now
|
||||
# }
|
||||
|
||||
# end
|
||||
=begin
|
||||
describe "student/teacher" do
|
||||
let(:teacher) { u = FactoryGirl.create(:teacher); u.user }
|
||||
|
|
@ -497,6 +523,36 @@ describe "RenderMailers", :slow => true do
|
|||
@filename="daily_sessions"; scheduled_batch.deliver_batch
|
||||
end
|
||||
end
|
||||
|
||||
describe "New Musician Match email" do
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:user1) { FactoryGirl.create(:user, first_name: 'David', last_name: 'Macmillan', updated_at: 2.day.ago.to_i) }
|
||||
let(:user2) { FactoryGirl.create(:user, first_name: 'Tom', last_name: 'Cluff', last_jam_updated_at: 1.days.ago.to_i) }
|
||||
let(:matched_musician_data){
|
||||
[
|
||||
{
|
||||
musicians: [user1, user2],
|
||||
latencies: [
|
||||
{:user_id=> user1.id, :audio_latency=>4, :ars_total_latency=>12, :ars_internet_latency=>8},
|
||||
{:user_id=> user2.id, :audio_latency=>4, :ars_total_latency=>12, :ars_internet_latency=>8}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
before(:each) do
|
||||
User.delete_all
|
||||
ActionMailer::Base.deliveries.clear
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
ActionMailer::Base.deliveries.length.should == 1
|
||||
mail = ActionMailer::Base.deliveries[0]
|
||||
save_emails_to_disk(mail, @filename)
|
||||
end
|
||||
|
||||
fit { @filename="new_musicians_match"; UserMailer.new_musicians_match(user, matched_musician_data).deliver_now }
|
||||
end
|
||||
end
|
||||
|
||||
def save_emails_to_disk(mail, filename)
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ describe UserMailer do
|
|||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
stub_const("APP_CONFIG", app_config)
|
||||
#stub_const("APP_CONFIG", app_config)
|
||||
UserMailer.deliveries.clear
|
||||
end
|
||||
|
||||
describe "should send confirm email" do
|
||||
describe "should send confirm email", focus: true do
|
||||
|
||||
let (:mail) { UserMailer.deliveries[0] }
|
||||
let (:signup_confirmation_url) { "/confirm" }
|
||||
|
|
@ -26,7 +26,6 @@ describe UserMailer do
|
|||
UserMailer.confirm_email(user, signup_confirmation_url_with_token).deliver_now
|
||||
end
|
||||
|
||||
|
||||
it { UserMailer.deliveries.length.should == 1 }
|
||||
it { mail['from'].to_s.should == UserMailer::DEFAULT_SENDER }
|
||||
it { mail['to'].to_s.should == user.email }
|
||||
|
|
@ -181,7 +180,16 @@ describe UserMailer do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
describe "sends new musicians email" do
|
||||
let(:mail) { UserMailer.deliveries[0] }
|
||||
before(:each) do
|
||||
UserMailer.new_musicians_match(user, []).deliver_now
|
||||
end
|
||||
it { UserMailer.deliveries.length.should == 1 }
|
||||
it { mail['from'].to_s.should == UserMailer::DEFAULT_SENDER }
|
||||
it { mail['to'].to_s.should == user.email }
|
||||
it { mail.multipart?.should == true } # because we send plain + html
|
||||
end
|
||||
|
||||
# describe "sends new musicians email" do
|
||||
|
||||
|
|
|
|||
|
|
@ -182,6 +182,8 @@ end
|
|||
# gem 'rack-timeout'
|
||||
#end
|
||||
|
||||
gem 'ffi', '1.14.0'
|
||||
|
||||
group :development, :test do
|
||||
gem 'rspec-rails' #, require: "rspec/rails" #, '2.14.2'
|
||||
gem 'rspec-collection_matchers'
|
||||
|
|
@ -248,4 +250,4 @@ end
|
|||
|
||||
group :package do
|
||||
#gem 'fpm'
|
||||
end
|
||||
end
|
||||
|
|
@ -459,7 +459,9 @@ GEM
|
|||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2021.0212)
|
||||
mimemagic (0.3.5)
|
||||
mimemagic (0.4.3)
|
||||
nokogiri (~> 1)
|
||||
rake
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.14.3)
|
||||
|
|
@ -517,7 +519,7 @@ GEM
|
|||
paypal-sdk-merchant-jk (1.118.1)
|
||||
paypal-sdk-core (~> 0.3.0)
|
||||
pdf-core (0.7.0)
|
||||
pg (0.17.1)
|
||||
pg (0.21.0)
|
||||
pg_array_parser (0.0.9)
|
||||
pleaserun (0.0.31)
|
||||
cabin (> 0)
|
||||
|
|
@ -554,6 +556,8 @@ GEM
|
|||
rabl (0.13.1)
|
||||
activesupport (>= 2.3.14)
|
||||
rack (1.6.13)
|
||||
rack-cors (1.0.6)
|
||||
rack (>= 1.6.0)
|
||||
rack-oauth2 (1.12.0)
|
||||
activesupport
|
||||
attr_required
|
||||
|
|
@ -864,7 +868,7 @@ DEPENDENCIES
|
|||
omniauth-stripe-connect
|
||||
omniauth-twitter
|
||||
paypal-sdk-merchant-jk (= 1.118.1)
|
||||
pg (= 0.17.1)
|
||||
pg (= 0.21.0)
|
||||
postgres-copy
|
||||
postgres_ext
|
||||
prawn-table
|
||||
|
|
@ -873,6 +877,7 @@ DEPENDENCIES
|
|||
puma
|
||||
quiet_assets
|
||||
rabl (= 0.13.1)
|
||||
rack-cors (~> 1.0, >= 1.0.6)
|
||||
rack-test
|
||||
rails (= 4.2.8)
|
||||
rails-assets-bluebird!
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -5,7 +5,7 @@ class ApiSearchController < ApiController
|
|||
|
||||
respond_to :json
|
||||
|
||||
include LatencyHelper
|
||||
#include LatencyHelper
|
||||
|
||||
def index
|
||||
if 1 == params[Search::PARAM_MUSICIAN].to_i || 1 == params[Search::PARAM_BAND].to_i
|
||||
|
|
@ -95,94 +95,98 @@ class ApiSearchController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
def filter
|
||||
# def filter
|
||||
|
||||
latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good])
|
||||
latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair])
|
||||
latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high])
|
||||
offset = [params[:offset].to_i, 0].max
|
||||
limit = [params[:limit].to_i, 20].max
|
||||
filter_params = {}
|
||||
# latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good])
|
||||
# latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair])
|
||||
# latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high])
|
||||
# offset = [params[:offset].to_i, 0].max
|
||||
# limit = [params[:limit].to_i, 20].max
|
||||
# filter_params = {}
|
||||
|
||||
filter_params.merge!(from_location: params[:from_location] ? '1' : '0')
|
||||
# filter_params.merge!(from_location: params[:from_location] ? '1' : '0')
|
||||
|
||||
genres = params[:genres]
|
||||
filter_params.merge!(genres: genres) if genres
|
||||
# if genres && genres.any?
|
||||
# genres.map!{|genre| {id: genre} }
|
||||
# filter_params.merge!(genres: genres)
|
||||
# end
|
||||
# genres = params[:genres]
|
||||
# filter_params.merge!(genres: genres) if genres
|
||||
# # if genres && genres.any?
|
||||
# # genres.map!{|genre| {id: genre} }
|
||||
# # filter_params.merge!(genres: genres)
|
||||
# # end
|
||||
|
||||
beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner])
|
||||
intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate])
|
||||
expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert])
|
||||
# beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner])
|
||||
# intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate])
|
||||
# expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert])
|
||||
|
||||
proficiency_levels = []
|
||||
proficiency_levels.push(1) if beginner
|
||||
proficiency_levels.push(2) if intermediate
|
||||
proficiency_levels.push(3) if expert
|
||||
# proficiency_levels = []
|
||||
# proficiency_levels.push(1) if beginner
|
||||
# proficiency_levels.push(2) if intermediate
|
||||
# proficiency_levels.push(3) if expert
|
||||
|
||||
instruments = params[:instruments]
|
||||
# instruments = params[:instruments]
|
||||
|
||||
#debugger
|
||||
# #debugger
|
||||
|
||||
if instruments && instruments.any? && proficiency_levels.any?
|
||||
inst = []
|
||||
instruments.each do |ii|
|
||||
proficiency_levels.each do |pl|
|
||||
inst << { id: ii[:value], proficiency: pl}
|
||||
end
|
||||
end
|
||||
filter_params.merge!(instruments: inst)
|
||||
end
|
||||
# if instruments && instruments.any? && proficiency_levels.any?
|
||||
# inst = []
|
||||
# instruments.each do |ii|
|
||||
# proficiency_levels.each do |pl|
|
||||
# inst << { id: ii[:value], proficiency: pl}
|
||||
# end
|
||||
# end
|
||||
# filter_params.merge!(instruments: inst)
|
||||
# end
|
||||
|
||||
filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank?
|
||||
filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank?
|
||||
# filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank?
|
||||
# filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank?
|
||||
|
||||
@latency_data = []
|
||||
begin
|
||||
# @latency_data = []
|
||||
# begin
|
||||
|
||||
#bm = Benchmark.measure do
|
||||
result = users_latency_data(latency_good, latency_fair, latency_high, filter_params, offset, limit)
|
||||
@latency_data = result[:data]
|
||||
@nextOffset = result[:next]
|
||||
# #bm = Benchmark.measure do
|
||||
# result = JamRuby::MusicianFilter.users_latency_data(current_user, request.remote_ip, latency_good, latency_fair, latency_high, filter_params, offset, limit)
|
||||
# @latency_data = result[:data]
|
||||
# @nextOffset = result[:next]
|
||||
|
||||
user_ids = @latency_data.map{ |l_data| l_data[:user_id] }
|
||||
#end
|
||||
# user_ids = @latency_data.map{ |l_data| l_data[:user_id] }
|
||||
# #end
|
||||
|
||||
# Bugsnag.notify("search_users_benchmark") do |report|
|
||||
# report.severity = "info"
|
||||
# report.add_tab(:benchmark, benchmark: bm.to_s)
|
||||
# end if Rails.env.production?
|
||||
# # Bugsnag.notify("search_users_benchmark") do |report|
|
||||
# # report.severity = "info"
|
||||
# # report.add_tab(:benchmark, benchmark: bm.to_s)
|
||||
# # end if Rails.env.production?
|
||||
|
||||
sobj = MusicianSearch.user_search_filter(current_user)
|
||||
#@search = sobj.search_results_page(filter_params, page, user_ids)
|
||||
#debugger
|
||||
@search = sobj.user_search_results(user_ids)
|
||||
# sobj = MusicianSearch.user_search_filter(current_user)
|
||||
# #@search = sobj.search_results_page(filter_params, page, user_ids)
|
||||
# debugger
|
||||
# @search = sobj.user_search_results(user_ids)
|
||||
|
||||
# respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter'
|
||||
|
||||
# rescue => exception
|
||||
# logger.debug("Latency exception: #{exception.message}")
|
||||
# Bugsnag.notify(exception) do |report|
|
||||
# report.severity = "error"
|
||||
# report.add_tab(:latency, {
|
||||
# params: params,
|
||||
# user_id: current_user.id,
|
||||
# name: current_user.name,
|
||||
# url: filter_latency_url,
|
||||
# })
|
||||
# end
|
||||
# render json: {}, status: 500
|
||||
# end
|
||||
|
||||
# end
|
||||
|
||||
def filter
|
||||
begin
|
||||
@search, @latency_data, @nextOffset = JamRuby::MusicianFilter.filter(current_user, request.remote_ip, params)
|
||||
Rails.logger.debug("=====SEARCH : #{@search.results.inspect}")
|
||||
Rails.logger.debug("=====LATENCY : #{@latency_data}")
|
||||
respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter'
|
||||
|
||||
rescue => exception
|
||||
logger.debug("Latency exception: #{exception.message}")
|
||||
Bugsnag.notify(exception) do |report|
|
||||
report.severity = "error"
|
||||
report.add_tab(:latency, {
|
||||
params: params,
|
||||
user_id: current_user.id,
|
||||
name: current_user.name,
|
||||
url: filter_latency_url,
|
||||
})
|
||||
end
|
||||
rescue
|
||||
render json: {}, status: 500
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_latency_url
|
||||
"#{Rails.application.config.latency_data_host}/search_users"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -445,6 +445,16 @@ JS
|
|||
render text: 'You have been unsubscribed.'
|
||||
end
|
||||
|
||||
def unsubscribe_user_match
|
||||
if params[:user_token].present? && @user = User.read_access_token(params[:user_token])
|
||||
@user.subscribe_email_for_user_match = false
|
||||
@user.save!
|
||||
render json: { head: :ok }
|
||||
else
|
||||
render json: { status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _render(action)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,6 @@ module LatencyHelper
|
|||
raise exception
|
||||
end
|
||||
|
||||
latency_data
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -25,7 +25,7 @@ node :is_blank_filter do |foo|
|
|||
end
|
||||
|
||||
child(:results => :musicians) {
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :is_friend, :is_following, :pending_friend_request
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :is_friend, :is_following, :pending_friend_request, :last_active_timestamp
|
||||
|
||||
# node :is_friend do |musician|
|
||||
# @search.is_friend?(musician)
|
||||
|
|
@ -82,11 +82,11 @@ child(:results => :musicians) {
|
|||
end if @latency_data
|
||||
end
|
||||
|
||||
node :last_active_timestamp do |musician|
|
||||
if musician.updated_at || musician.last_jam_updated_at
|
||||
[musician.updated_at, musician.last_jam_updated_at].compact.max.to_i
|
||||
end
|
||||
end
|
||||
# node :last_active_timestamp do |musician|
|
||||
# if musician.updated_at || musician.last_jam_updated_at
|
||||
# [musician.updated_at, musician.last_jam_updated_at].compact.max.to_i
|
||||
# end
|
||||
# end
|
||||
|
||||
child :genres => :genres do
|
||||
attributes :genre_id, :description
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ if @search.is_a?(BaseSearch)
|
|||
end
|
||||
|
||||
child(:results => :musicians) {
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :last_active_timestamp
|
||||
|
||||
node :is_friend do |musician|
|
||||
@search.is_friend?(musician)
|
||||
|
|
@ -72,11 +72,11 @@ if @search.is_a?(BaseSearch)
|
|||
end if @latency_data
|
||||
end
|
||||
|
||||
node :last_active_timestamp do |musician|
|
||||
if musician.updated_at || musician.last_jam_updated_at
|
||||
[musician.updated_at, musician.last_jam_updated_at].compact.max.to_i
|
||||
end
|
||||
end
|
||||
# node :last_active_timestamp do |musician|
|
||||
# if musician.updated_at || musician.last_jam_updated_at
|
||||
# [musician.updated_at, musician.last_jam_updated_at].compact.max.to_i
|
||||
# end
|
||||
# end
|
||||
|
||||
child :genres => :genres do
|
||||
attributes :genre_id, :description
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
object @profile
|
||||
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :concert_count, :studio_session_count, :virtual_band, :virtual_band_commitment, :traditional_band, :traditional_band_commitment, :traditional_band_touring, :paid_sessions, :paid_sessions_hourly_rate,
|
||||
:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email, :is_a_teacher, :is_a_student
|
||||
:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email, :is_a_teacher, :is_a_student, :last_active_timestamp
|
||||
|
||||
child :online_presences => :online_presences do
|
||||
attributes :id, :service_type, :username
|
||||
|
|
@ -32,11 +32,11 @@ child :musician_instruments => :instruments do
|
|||
attributes :description, :proficiency_level, :priority, :instrument_id
|
||||
end
|
||||
|
||||
node :last_active_timestamp do |user|
|
||||
if user.updated_at || user.last_jam_updated_at
|
||||
[user.updated_at, user.last_jam_updated_at].compact.max.to_i
|
||||
end
|
||||
end
|
||||
# node :last_active_timestamp do |user|
|
||||
# if user.updated_at || user.last_jam_updated_at
|
||||
# [user.updated_at, user.last_jam_updated_at].compact.max.to_i
|
||||
# end
|
||||
# end
|
||||
|
||||
node :created_at_timestamp do |user|
|
||||
user.created_at.to_i
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ if defined?(Bundler)
|
|||
config.latency_data_host = "http://localhost:4001"
|
||||
config.latency_data_host_auth_code = "c2VydmVyOnBhc3N3b3Jk"
|
||||
config.manual_override_installer_ends_with = "JamKazam-1.0.3776.dmg"
|
||||
config.spa_origin = "http://beta.jamkazam.local:3000"
|
||||
|
||||
config.spa_origin = "http://beta.jamkazam.local:4000"
|
||||
config.user_match_monitoring_email = "user_match_monitoring_email@jamkazam.com"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -120,4 +120,7 @@ SampleApp::Application.configure do
|
|||
config.spa_origin = "http://beta.jamkazam.local:4000"
|
||||
|
||||
config.session_cookie_domain = ".jamkazam.local"
|
||||
|
||||
config.action_controller.asset_host = 'http://localhost:3000'
|
||||
config.action_mailer.asset_host = config.action_controller.asset_host
|
||||
end
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ Rails.application.routes.draw do
|
|||
get '/reset_password_complete' => 'users#reset_password_complete', :as => 'reset_password_complete'
|
||||
|
||||
match '/unsubscribe/:user_token' => 'users#unsubscribe', via: [:get, :post]
|
||||
match '/unsubscribe_user_match/:user_token' => 'users#unsubscribe_user_match', via: [:post]
|
||||
|
||||
# email update
|
||||
get '/confirm_email' => 'users#finalize_update_email', :as => 'confirm_email' # NOTE: if you change this, you break outstanding email changes because links in user inboxes are broken
|
||||
|
|
|
|||
|
|
@ -65,6 +65,11 @@ NewMusicianEmailer:
|
|||
class: "JamRuby::NewMusicianEmailer"
|
||||
description: "Sends weekly emails of new users with good latency"
|
||||
|
||||
NewMusicianMatchEmailer:
|
||||
cron: "0 2 * * 6"
|
||||
class: "JamRuby::NewMusicianMatchEmailer"
|
||||
description: "Sends weekly emails of new users with good latency - (User latency data from neo4j)"
|
||||
|
||||
MusicSessionScheduler:
|
||||
cron: "0 * * * *"
|
||||
class: "JamRuby::MusicSessionScheduler"
|
||||
|
|
|
|||
|
|
@ -135,7 +135,6 @@ describe "Musician Filter API", type: :request do
|
|||
|
||||
it "filter musicians when no latency option is selected" do
|
||||
post '/api/filter.json', { latency_good: false, latency_fair: false, latency_high: false }
|
||||
|
||||
expect(JSON.parse(response.body)["musicians"].size).to eq(8)
|
||||
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]).not_to eq(nil)
|
||||
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]["audio_latency"]).not_to eq(nil)
|
||||
|
|
@ -223,7 +222,7 @@ describe "Musician Filter API", type: :request do
|
|||
expect(JSON.parse(response.body)["musicians"].size).to eq(2)
|
||||
end
|
||||
|
||||
it "filter musicians by days ago that they joined" do
|
||||
fit "filter musicians by days ago that they joined" do
|
||||
post '/api/filter.json', { latency_good: true, latency_fair: true, latency_high: true, joined_within_days: 1 }
|
||||
expect(JSON.parse(response.body)["musicians"].size).to eq(2)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,6 +28,20 @@ describe UsersController, :type => :api do
|
|||
user.subscribe_email.should eql false
|
||||
end
|
||||
|
||||
fit "unsubscribe_user_match" do
|
||||
user.subscribe_email_for_user_match.should eql false #default value is false
|
||||
|
||||
user.subscribe_email_for_user_match = true #we make it true here for this test
|
||||
user.save!
|
||||
|
||||
get '/unsubscribe_user_match/' + user.unsubscribe_token
|
||||
|
||||
expect(last_response).to have_http_status(200)
|
||||
|
||||
user.reload
|
||||
user.subscribe_email_for_user_match.should eql false
|
||||
end
|
||||
|
||||
describe "track_origin" do
|
||||
describe "logged out" do
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue