Reinstating initial setup; Only reporting to analytics where approved, fixes #247 and #268

This commit is contained in:
James Barnsley
2018-06-05 13:43:02 +12:00
parent 984d613e17
commit c27588f2f0
22 changed files with 26310 additions and 24003 deletions

View File

@ -3741,6 +3741,9 @@ input[type="submit"] {
position: relative; }
.modal .content form .field .label {
font-size: 16px; }
.modal .content form .field .description {
padding-left: 0;
padding-right: 0; }
.modal .content form .field.text > .label, .modal .content form .field.radio > .label, .modal .content form .field.checkbox > .label {
opacity: 0.5;
width: 100px;
@ -3763,7 +3766,6 @@ input[type="submit"] {
.modal .content form .field.text input:focus, .modal .content form .field.text input:active {
border-color: #08d58f; }
.modal .content form .field.radio, .modal .content form .field.checkbox {
padding-left: 100px;
padding-top: 8px; }
.modal .content form button[type="submit"] {
margin-top: 50px; }

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -3741,6 +3741,9 @@ input[type="submit"] {
position: relative; }
.modal .content form .field .label {
font-size: 16px; }
.modal .content form .field .description {
padding-left: 0;
padding-right: 0; }
.modal .content form .field.text > .label, .modal .content form .field.radio > .label, .modal .content form .field.checkbox > .label {
opacity: 0.5;
width: 100px;
@ -3763,7 +3766,6 @@ input[type="submit"] {
.modal .content form .field.text input:focus, .modal .content form .field.text input:active {
border-color: #08d58f; }
.modal .content form .field.radio, .modal .content form .field.checkbox {
padding-left: 100px;
padding-top: 8px; }
.modal .content form button[type="submit"] {
margin-top: 50px; }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -77,7 +77,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1527845439";
var build = "1528160456";
var version = "3.20.0";
// Construct the script tag

View File

@ -4,6 +4,7 @@ import ReactDOM from 'react-dom';
import { createStore, bindActionCreators } from 'redux'
import { hashHistory, Link } from 'react-router'
import { connect } from 'react-redux'
import ReactGA from 'react-ga'
import Sidebar from './components/Sidebar'
import PlaybackControls from './components/Fields/PlaybackControls'
@ -47,6 +48,10 @@ class App extends React.Component{
componentDidMount(){
if (this.props.allow_reporting){
ReactGA.initialize('UA-64701652-3');
}
// Fire up our services
this.props.mopidyActions.connect();
this.props.pusherActions.connect();
@ -55,11 +60,22 @@ class App extends React.Component{
// when we navigate to a new route
hashHistory.listen(location => {
// Log our pageview
if (this.props.allow_reporting){
ReactGA.set({ page: window.location.hash });
ReactGA.pageview(window.location.hash);
}
// Hide our sidebar
this.props.uiActions.toggleSidebar(false )
// Unselect any tracks
this.props.uiActions.setSelectedTracks([])
this.props.uiActions.setSelectedTracks([]);
// Close context menu
if (this.props.context_menu){
this.props.uiActions.hideContextMenu();
}
});
// Check our slim_mode
@ -76,10 +92,9 @@ class App extends React.Component{
}
// show initial setup if required
/*
if (this.props.show_initial_setup){
if (!this.props.initial_setup_complete){
this.props.uiActions.openModal('initial_setup');
}*/
}
}
componentWillReceiveProps(nextProps){
@ -87,11 +102,6 @@ class App extends React.Component{
// We've navigated to a new location
if (this.props.location.pathname !== nextProps.location.pathname){
// Close context menu
if (this.props.context_menu){
this.props.uiActions.hideContextMenu();
}
// Scroll to bottom, only if we've PUSHed to a new route
// We also prevent scroll reset for any sub_view routes (like tabs, services, etc)
if (nextProps.location.action == 'PUSH' && nextProps.params.sub_view === undefined){
@ -317,8 +327,9 @@ class App extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
allow_reporting: state.ui.allow_reporting,
touch_dragging: state.ui.touch_dragging,
show_initial_setup: state.ui.show_initial_setup,
initial_setup_complete: state.ui.initial_setup_complete,
slim_mode: state.ui.slim_mode,
broadcasts: (state.ui.broadcasts ? state.ui.broadcasts : []),
volume: (state.mopidy.volume ? state.mopidy.volume : false),

2
src/js/bootstrap.js vendored
View File

@ -48,7 +48,7 @@ var initialState = {
http_streaming_url: "http://"+window.location.hostname+":8000/mopidy"
},
ui: {
show_initial_setup: true,
allow_reporting: true,
slim_mode: false,
selected_tracks: [],
notifications: {},

View File

@ -2,6 +2,9 @@
import React, { PropTypes } from 'react'
import Icon from '../Icon'
import SpotifyAuthenticationFrame from '../Fields/SpotifyAuthenticationFrame'
import LastfmAuthenticationFrame from '../Fields/LastfmAuthenticationFrame'
import * as helpers from '../../helpers'
export default class InitialSetupModal extends React.Component{
@ -9,27 +12,47 @@ export default class InitialSetupModal extends React.Component{
super(props);
this.state = {
username: 'Anonymous'
username: 'Anonymous',
allow_reporting: this.props.allow_reporting,
host: this.props.host,
port: this.props.port,
ssl: this.props.ssl
}
}
handleSubmit(e){
// save and falseify show_initial_setup
}
e.preventDefault();
handleCancel(e){
// falseify show_initial_setup
this.props.pusherActions.setUsername(this.state.username);
this.props.uiActions.set({
initial_setup_complete: true,
allow_reporting: this.state.allow_reporting
});
this.props.mopidyActions.setConfig({
host: this.state.host,
port: this.state.port,
ssl: this.state.ssl
});
this.setState({saving: true});
// Wait two seconds to allow our actions to complete
// Then reload the interface
setTimeout(function(){
window.location.reload(true);
}, 2000);
return false;
}
render(){
return (
<div>
<h1>Welcome to Iris</h1>
<h1>Get started</h1>
<form onSubmit={(e) => this.handleSubmit(e)}>
<div className="field">
<div className="name">Username</div>
<div className="input">
<div className="field text">
<span className="label">Username</span>
<input
type="text"
onChange={e => this.setState({username: e.target.value.replace(/\W/g, '')})}
@ -38,11 +61,66 @@ export default class InitialSetupModal extends React.Component{
A non-unique string used to identify this client (no special characters)
</div>
</div>
<div className="field">
<div className="name">Host</div>
<div className="input">
<input
type="text"
onChange={ e => this.setState({host: e.target.value})}
value={ this.state.host } />
</div>
</div>
<div className="field">
<div className="name">Port</div>
<div className="input">
<input
type="text"
onChange={ e => this.setState({port: e.target.value})}
value={ this.state.port } />
</div>
</div>
<div className="field checkbox">
<div className="name">Encryption</div>
<div className="input">
<label>
<input
type="checkbox"
name="ssl"
checked={this.state.ssl}
onChange={e => this.setState({ssl: !this.state.ssl})} />
<span className="label has-tooltip">
Enable SSL
<span className="tooltip">Requires SSL proxy</span>
</span>
</label>
</div>
</div>
<div className="field checkbox">
<div className="name">Reporting</div>
<div className="input">
<label>
<input
type="checkbox"
name="allow_reporting"
checked={ this.props.ui.allow_reporting }
onChange={ e => this.props.uiActions.set({ allow_reporting: !this.props.ui.allow_reporting })} />
<span className="label">
Allow reporting of anonymous usage statistics
</span>
</label>
</div>
</div>
<div className="field">
<div>Google Analytics is used to collect usage data and errors to help trace issues and provide valuable insight into how we can continue to make improvements. Personal information is anonymized prior to collection.</div>
</div>
<div className="actions centered-text">
<button className="large" onClick={e => this.handleCancel(e)}>Skip</button>
<button className="primary large" onClick={e => this.handleSubmit(e)}>Save</button>
<button className={"primary large"+(this.state.saving ? " working" : "")} onClick={e => this.handleSubmit(e)}>
{this.state.saving ? "Saving" : "Save"}
</button>
</div>
</form>

View File

@ -123,7 +123,12 @@ class Modal extends React.Component{
current_track={this.props.current_track} /> : null }
{ this.props.modal.name == 'initial_setup' ? <InitialSetupModal
host={this.props.mopidy_host}
port={this.props.mopidy_port}
ssl={this.props.mopidy_ssl}
allow_reporting={this.props.allow_reporting}
uiActions={this.props.uiActions}
mopidyActions={this.props.mopidyActions}
pusherActions={this.props.pusherActions} /> : null }
</div>
@ -148,7 +153,11 @@ const mapStateToProps = (state, ownProps) => {
mopidy_connected: state.mopidy.connected,
spotify_authorized: state.spotify.authorization,
spotify_library_playlists: state.spotify.library_playlists,
mopidy_library_playlists: state.mopidy.library_playlists
mopidy_library_playlists: state.mopidy.library_playlists,
allow_reporting: state.ui.allow_reporting,
mopidy_host: state.mopidy.host,
mopidy_port: state.mopidy.port,
mopidy_ssl: state.mopidy.ssl
}
}

View File

@ -7,7 +7,6 @@ import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import { Router, Route, Link, IndexRoute, hashHistory } from 'react-router'
import ReactGA from 'react-ga'
import store from './bootstrap.js'
require('../scss/app.scss');
@ -44,18 +43,11 @@ console.error = function(message, error){
originalConsoleError.apply(this, arguments)
*/
// setup our analytics tracking
ReactGA.initialize('UA-64701652-3');
function handleUpdate(){
ReactGA.set({ page: window.location.hash });
ReactGA.pageview(window.location.hash);
}
global.baseURL = '/'
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory} onUpdate={handleUpdate}>
<Router history={hashHistory}>
<Route path={global.baseURL} component={App}>
<IndexRoute component={Queue} />

View File

@ -72,62 +72,82 @@ const CoreMiddleware = (function(){
);
// Log with Raven Sentry
if (store.getState().ui.allow_reporting){
Raven.captureException(
new Error(message),
{
extra: data
}
);
}
// Log with Analytics
if (store.getState().ui.allow_reporting){
ReactGA.event({
category: "Error",
action: message,
label: description,
nonInteraction: true
});
}
store.dispatch(uiActions.createNotification({content: message, type: 'bad', description: description}));
console.error(message, description, data);
break;
case 'PLAY_PLAYLIST':
ReactGA.event({ category: 'Playlist', action: 'Play', label: action.uri })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Play', label: action.uri });
}
next(action)
break
case 'SAVE_PLAYLIST':
ReactGA.event({ category: 'Playlist', action: 'Save', label: action.key })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Save', label: action.key });
}
next(action)
break
case 'CREATE_PLAYLIST':
ReactGA.event({ category: 'Playlist', action: 'Create', label: +action.name })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Create', label: +action.name });
}
next(action)
break
case 'REORDER_PLAYLIST_TRACKS':
ReactGA.event({ category: 'Playlist', action: 'Reorder tracks', label: action.key })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Reorder tracks', label: action.key });
}
next(action)
break
case 'ADD_PLAYLIST_TRACKS':
ReactGA.event({ category: 'Playlist', action: 'Add tracks', label: action.playlist_uri })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Add tracks', label: action.playlist_uri });
}
next(action)
break
case 'REMOVE_PLAYLIST_TRACKS':
ReactGA.event({ category: 'Playlist', action: 'Remove tracks', label: action.playlist_uri })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Remove tracks', label: action.playlist_uri });
}
next(action)
break
case 'DELETE_PLAYLIST':
ReactGA.event({ category: 'Playlist', action: 'Delete', label: action.uri })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Delete', label: action.uri });
}
next(action)
break
case 'SEARCH_STARTED':
ReactGA.event({ category: 'Search', action: 'Started', label: action.type+': '+action.query })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Search', action: 'Started', label: action.type+': '+action.query });
}
next(action)
var state = store.getState()

View File

@ -1,6 +1,4 @@
import ReactGA from 'react-ga'
var helpers = require('./../../helpers')
var lastfmActions = require('./actions')
var uiActions = require('../ui/actions')

View File

@ -220,7 +220,9 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_CONNECTED':
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Mopidy', action: 'Connected', label: window.location.hostname });
}
next(action);
break;
@ -2155,7 +2157,9 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_DIRECTORY':
if (action.data) ReactGA.event({ category: 'Directory', action: 'Load', label: action.data.uri })
if (store.getState().ui.allow_reporting && action.data){
ReactGA.event({ category: 'Directory', action: 'Load', label: action.data.uri });
}
next(action)
break

View File

@ -215,7 +215,9 @@ const PusherMiddleware = (function(){
break;
case 'PUSHER_CONNECTED':
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.username});
}
store.dispatch(pusherActions.getConfig());
store.dispatch(pusherActions.getVersion());
@ -375,7 +377,9 @@ const PusherMiddleware = (function(){
case 'PUSHER_START_RADIO':
case 'PUSHER_UPDATE_RADIO':
ReactGA.event({ category: 'Pusher', action: 'Start radio', label: action.uris.join() })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Pusher', action: 'Start radio', label: action.uris.join() });
}
// start our UI process notification
if (action.type == 'PUSHER_UPDATE_RADIO'){
@ -438,7 +442,10 @@ const PusherMiddleware = (function(){
case 'PUSHER_STOP_RADIO':
store.dispatch(uiActions.createNotification({content: 'Stopping radio'}));
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Pusher', action: 'Stop radio' });
}
store.dispatch(pusherActions.deliverBroadcast(
'notification',
@ -503,7 +510,9 @@ const PusherMiddleware = (function(){
break
case 'PUSHER_UPGRADE':
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Pusher', action: 'Upgrade', label: '' });
}
request(store, 'upgrade')
.then(
response => {
@ -516,7 +525,9 @@ const PusherMiddleware = (function(){
break;
case 'PUSHER_VERSION':
ReactGA.event({ category: 'Pusher', action: 'Version', label: action.version.current })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Pusher', action: 'Version', label: action.version.current });
}
if (action.version.upgrade_available){
store.dispatch(uiActions.createNotification({content: 'Version '+action.version.latest+' is available. See settings to upgrade.'}));
@ -574,8 +585,10 @@ const PusherMiddleware = (function(){
break;
case 'PUSHER_ERROR':
store.dispatch(uiActions.createNotification(action.message, 'bad'))
ReactGA.event({ category: 'Pusher', action: 'Error', label: action.message })
store.dispatch(uiActions.createNotification(action.message, 'bad'));
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Pusher', action: 'Error', label: action.message });
}
break

View File

@ -18,7 +18,9 @@ const SpotifyMiddleware = (function(){
switch(action.type){
case 'SPOTIFY_AUTHORIZATION_GRANTED':
if (store.getState().ui.allow_reporting){
ReactGA.event({category: 'Spotify', action: 'Authorization granted'});
}
// Flush out the previous user's library
store.dispatch(spotifyActions.flushLibrary());
@ -27,13 +29,16 @@ const SpotifyMiddleware = (function(){
break;
case 'SPOTIFY_AUTHORIZATION_REVOKED':
if (store.getState().ui.allow_reporting){
var hashed_username = null
if (store.getState().spotify.me){
hashed_username = md5(store.getState().spotify.me);
ReactGA.set({userId: hashed_username});
}
ReactGA.event({ category: 'Spotify', action: 'Authorization revoked', label: hashed_username})
next(action)
ReactGA.event({ category: 'Spotify', action: 'Authorization revoked', label: hashed_username});
}
next(action);
// Now dispatch a getMe to get the backend-provided user
store.dispatch(spotifyActions.getMe());
@ -44,12 +49,14 @@ const SpotifyMiddleware = (function(){
break;
case 'SPOTIFY_IMPORT_AUTHORIZATION':
if (store.getState().ui.allow_reporting){
var hashed_username = null
if (store.getState().spotify.me){
hashed_username = md5(store.getState().spotify.me);
ReactGA.set({userId: hashed_username});
}
ReactGA.event({category: 'Spotify', action: 'Authorization imported', label: hashed_username});
}
// Flush out the previous user's library
store.dispatch(spotifyActions.flushLibrary());
@ -58,14 +65,16 @@ const SpotifyMiddleware = (function(){
break;
case 'SPOTIFY_RECOMMENDATIONS_LOADED':
if (action.seeds_uris){
if (store.getState().ui.allow_reporting && action.seeds_uris){
ReactGA.event({ category: 'Spotify', action: 'Recommendations', label: action.seeds_uris.join(',') })
}
next(action)
break;
case 'SPOTIFY_USER_LOADED':
if (action.data) ReactGA.event({ category: 'User', action: 'Load', label: action.data.uri })
if (store.getState().ui.allow_reporting && action.data){
ReactGA.event({ category: 'User', action: 'Load', label: action.data.uri });
}
next(action)
break;
@ -414,9 +423,11 @@ const SpotifyMiddleware = (function(){
store.dispatch(pusherActions.setUsername(name));
}
if (store.getState().ui.allow_reporting){
var hashed_username = md5(action.data.id);
ReactGA.set({userId: hashed_username});
ReactGA.event({category: 'Spotify', action: 'Authorization verified', label: hashed_username});
}
store.dispatch({
type: 'USERS_LOADED',

View File

@ -28,7 +28,9 @@ const UIMiddleware = (function(){
break
case 'OPEN_MODAL':
ReactGA.event({ category: 'Modal', action: 'Opened', label: action.modal.name })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Modal', action: 'Opened', label: action.modal.name });
}
$('body').addClass('modal-open')
store.dispatch(uiActions.hideContextMenu())
store.dispatch(uiActions.hideTouchContextMenu())
@ -36,7 +38,9 @@ const UIMiddleware = (function(){
break
case 'CLOSE_MODAL':
ReactGA.event({ category: 'Modal', action: 'Closed', label: null })
if (store.getState().ui.allow_reporting){
ReactGA.event({ category: 'Modal', action: 'Closed', label: null });
}
$('body').removeClass('modal-open')
next(action)
break

View File

@ -90,13 +90,17 @@ class Playlist extends React.Component{
}
follow(){
ReactGA.event({ category: 'Playlist', action: 'Follow', label: this.props.playlist.uri })
if (this.props.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Follow', label: this.props.playlist.uri });
}
this.props.spotifyActions.toggleFollowingPlaylist(this.props.playlist.uri, 'PUT')
}
// TODO: Once unfollowing occurs, remove playlist from global playlists list
unfollow(){
ReactGA.event({ category: 'Playlist', action: 'Unfollow', label: this.props.playlist.uri })
if (this.props.allow_reporting){
ReactGA.event({ category: 'Playlist', action: 'Unfollow', label: this.props.playlist.uri });
}
this.props.spotifyActions.toggleFollowingPlaylist(this.props.playlist.uri, 'DELETE' )
}
@ -239,6 +243,7 @@ class Playlist extends React.Component{
const mapStateToProps = (state, ownProps) => {
var uri = ownProps.params.uri;
return {
allow_reporting: state.ui.allow_reporting,
slim_mode: state.ui.slim_mode,
load_queue: state.ui.load_queue,
tracks: state.core.tracks,

View File

@ -3,7 +3,6 @@ import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { Link, hashHistory } from 'react-router'
import { bindActionCreators } from 'redux'
import ReactGA from 'react-ga'
import Header from '../components/Header'
import Icon from '../components/Icon'

View File

@ -239,6 +239,28 @@ class Settings extends React.Component {
<h4 className="underline">Services</h4>
<Services active={this.props.params.sub_view} />
<h4 className="underline">Privacy</h4>
<div className="field checkbox">
<div className="name">Reporting</div>
<div className="input">
<label>
<input
type="checkbox"
name="allow_reporting"
checked={ this.props.ui.allow_reporting }
onChange={ e => this.props.uiActions.set({ allow_reporting: !this.props.ui.allow_reporting })} />
<span className="label">
Allow reporting of anonymous usage statistics
</span>
</label>
</div>
</div>
<div className="field">
<div>Google Analytics is used to collect usage data and errors to help trace issues and provide valuable insight into how we can continue to make improvements. Personal information is anonymized prior to collection.</div>
</div>
<h4 className="underline">Advanced</h4>
<div className="field checkbox">
@ -301,10 +323,6 @@ class Settings extends React.Component {
<div className="field">
<div>
<em><a href="https://github.com/jaedb/Iris" target="_blank">Iris</a></em> is an open-source project by <a href="https://github.com/jaedb" target="_blank">James Barnsley</a>. It is provided free and with absolutely no warranty. If you paid someone for this software, please let me know.
<br />
<br />
Google Analytics is used to collect usage data and errors to help trace issues and provide valuable insight into how we can continue to make improvements. Personal information is anonymized prior to collection. For more information, see <a href="https://github.com/jaedb/Iris/wiki/Terms-of-use" target="_blank">terms and conditions</a>.
<br />
</div>
<br /><br />
<div>

View File

@ -56,6 +56,11 @@
font-size: 16px;
}
.description {
padding-left: 0;
padding-right: 0;
}
&.text,
&.radio,
&.checkbox {
@ -93,7 +98,6 @@
&.radio,
&.checkbox {
padding-left: 100px;
padding-top: 8px;
}
}