mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
8a1f9ca38f
10 changed files with 208 additions and 8 deletions
145
src/Analytics.js
Normal file
145
src/Analytics.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getCurrentLanguage } from './languageHandler';
|
||||||
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
import PlatformPeg from './PlatformPeg';
|
||||||
|
import SdkConfig from './SdkConfig';
|
||||||
|
|
||||||
|
function redact(str) {
|
||||||
|
return str.replace(/#\/(room|user)\/(.+)/, "#/$1/<redacted>");
|
||||||
|
}
|
||||||
|
|
||||||
|
const customVariables = {
|
||||||
|
'App Platform': 1,
|
||||||
|
'App Version': 2,
|
||||||
|
'User Type': 3,
|
||||||
|
'Chosen Language': 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Analytics {
|
||||||
|
constructor() {
|
||||||
|
this._paq = null;
|
||||||
|
this.disabled = true;
|
||||||
|
this.firstPage = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable Analytics if initialized but disabled
|
||||||
|
* otherwise try and initalize, no-op if piwik config missing
|
||||||
|
*/
|
||||||
|
enable() {
|
||||||
|
if (this._paq || this._init()) {
|
||||||
|
this.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable Analytics calls, will not fully unload Piwik until a refresh,
|
||||||
|
* but this is second best, Piwik should not pull anything implicitly.
|
||||||
|
*/
|
||||||
|
disable() {
|
||||||
|
this.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_init() {
|
||||||
|
const config = SdkConfig.get();
|
||||||
|
if (!config || !config.piwik || !config.piwik.url || !config.piwik.siteId) return;
|
||||||
|
|
||||||
|
const url = config.piwik.url;
|
||||||
|
const siteId = config.piwik.siteId;
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
window._paq = this._paq = window._paq || [];
|
||||||
|
|
||||||
|
this._paq.push(['setTrackerUrl', url+'piwik.php']);
|
||||||
|
this._paq.push(['setSiteId', siteId]);
|
||||||
|
|
||||||
|
this._paq.push(['trackAllContentImpressions']);
|
||||||
|
this._paq.push(['discardHashTag', false]);
|
||||||
|
this._paq.push(['enableHeartBeatTimer']);
|
||||||
|
this._paq.push(['enableLinkTracking', true]);
|
||||||
|
|
||||||
|
const platform = PlatformPeg.get();
|
||||||
|
this._setVisitVariable('App Platform', platform.getHumanReadableName());
|
||||||
|
platform.getAppVersion().then((version) => {
|
||||||
|
this._setVisitVariable('App Version', version);
|
||||||
|
}).catch(() => {
|
||||||
|
this._setVisitVariable('App Version', 'unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
this._setVisitVariable('Chosen Language', getCurrentLanguage());
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const g = document.createElement('script');
|
||||||
|
const s = document.getElementsByTagName('script')[0];
|
||||||
|
g.type='text/javascript'; g.async=true; g.defer=true; g.src=url+'piwik.js';
|
||||||
|
|
||||||
|
g.onload = function() {
|
||||||
|
console.log('Initialised anonymous analytics');
|
||||||
|
self._paq = window._paq;
|
||||||
|
};
|
||||||
|
|
||||||
|
s.parentNode.insertBefore(g, s);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackPageChange() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
if (this.firstPage) {
|
||||||
|
// De-duplicate first page
|
||||||
|
// router seems to hit the fn twice
|
||||||
|
this.firstPage = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._paq.push(['setCustomUrl', redact(window.location.href)]);
|
||||||
|
this._paq.push(['trackPageView']);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackEvent(category, action, name) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this._paq.push(['trackEvent', category, action, name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this._paq.push(['deleteCookies']);
|
||||||
|
}
|
||||||
|
|
||||||
|
login() { // not used currently
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
if (this.disabled || !cli) return;
|
||||||
|
|
||||||
|
this._paq.push(['setUserId', `@${cli.getUserIdLocalpart()}:${cli.getDomain()}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setVisitVariable(key, value) {
|
||||||
|
this._paq.push(['setCustomVariable', customVariables[key], key, value, 'visit']);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGuest(guest) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this._setVisitVariable('User Type', guest ? 'Guest' : 'Logged In');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!global.mxAnalytics) {
|
||||||
|
global.mxAnalytics = new Analytics();
|
||||||
|
}
|
||||||
|
module.exports = global.mxAnalytics;
|
|
@ -29,6 +29,11 @@ export default class BasePlatform {
|
||||||
this.errorDidOccur = false;
|
this.errorDidOccur = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used primarily for Analytics
|
||||||
|
getHumanReadableName(): string {
|
||||||
|
return 'Base Platform';
|
||||||
|
}
|
||||||
|
|
||||||
setNotificationCount(count: number) {
|
setNotificationCount(count: number) {
|
||||||
this.notificationCount = count;
|
this.notificationCount = count;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import q from 'q';
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
import Analytics from './Analytics';
|
||||||
import Notifier from './Notifier';
|
import Notifier from './Notifier';
|
||||||
import UserActivity from './UserActivity';
|
import UserActivity from './UserActivity';
|
||||||
import Presence from './Presence';
|
import Presence from './Presence';
|
||||||
|
@ -276,6 +277,8 @@ export function initRtsClient(url) {
|
||||||
export function setLoggedIn(credentials) {
|
export function setLoggedIn(credentials) {
|
||||||
credentials.guest = Boolean(credentials.guest);
|
credentials.guest = Boolean(credentials.guest);
|
||||||
|
|
||||||
|
Analytics.setGuest(credentials.guest);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"setLoggedIn: mxid:", credentials.userId,
|
"setLoggedIn: mxid:", credentials.userId,
|
||||||
"deviceId:", credentials.deviceId,
|
"deviceId:", credentials.deviceId,
|
||||||
|
@ -403,6 +406,7 @@ export function onLoggedOut() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _clearLocalStorage() {
|
function _clearLocalStorage() {
|
||||||
|
Analytics.logout();
|
||||||
if (!window.localStorage) {
|
if (!window.localStorage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var ReactDOM = require('react-dom');
|
var ReactDOM = require('react-dom');
|
||||||
|
import Analytics from './Analytics';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
|
|
||||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
|
@ -104,6 +105,9 @@ class ModalManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
createDialog(Element, props, className) {
|
createDialog(Element, props, className) {
|
||||||
|
if (props && props.title) {
|
||||||
|
Analytics.trackEvent('Modal', props.title, 'createDialog');
|
||||||
|
}
|
||||||
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
|
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
import PlatformPeg from './PlatformPeg';
|
import PlatformPeg from './PlatformPeg';
|
||||||
import TextForEvent from './TextForEvent';
|
import TextForEvent from './TextForEvent';
|
||||||
|
import Analytics from './Analytics';
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
|
@ -121,6 +122,9 @@ const Notifier = {
|
||||||
setEnabled: function(enable, callback) {
|
setEnabled: function(enable, callback) {
|
||||||
const plaf = PlatformPeg.get();
|
const plaf = PlatformPeg.get();
|
||||||
if (!plaf) return;
|
if (!plaf) return;
|
||||||
|
|
||||||
|
Analytics.trackEvent('Notifier', 'Set Enabled', enable);
|
||||||
|
|
||||||
// make sure that we persist the current setting audio_enabled setting
|
// make sure that we persist the current setting audio_enabled setting
|
||||||
// before changing anything
|
// before changing anything
|
||||||
if (global.localStorage) {
|
if (global.localStorage) {
|
||||||
|
@ -199,6 +203,8 @@ const Notifier = {
|
||||||
setToolbarHidden: function(hidden, persistent = true) {
|
setToolbarHidden: function(hidden, persistent = true) {
|
||||||
this.toolbarHidden = hidden;
|
this.toolbarHidden = hidden;
|
||||||
|
|
||||||
|
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
|
||||||
|
|
||||||
// XXX: why are we dispatching this here?
|
// XXX: why are we dispatching this here?
|
||||||
// this is nothing to do with notifier_enabled
|
// this is nothing to do with notifier_enabled
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
|
|
|
@ -20,6 +20,8 @@ import q from 'q';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Matrix from "matrix-js-sdk";
|
import Matrix from "matrix-js-sdk";
|
||||||
|
|
||||||
|
import Analytics from "../../Analytics";
|
||||||
|
import UserSettingsStore from '../../UserSettingsStore';
|
||||||
import MatrixClientPeg from "../../MatrixClientPeg";
|
import MatrixClientPeg from "../../MatrixClientPeg";
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
|
@ -189,6 +191,8 @@ module.exports = React.createClass({
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
SdkConfig.put(this.props.config);
|
SdkConfig.put(this.props.config);
|
||||||
|
|
||||||
|
if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable();
|
||||||
|
|
||||||
// Used by _viewRoom before getting state from sync
|
// Used by _viewRoom before getting state from sync
|
||||||
this.firstSyncComplete = false;
|
this.firstSyncComplete = false;
|
||||||
this.firstSyncPromise = q.defer();
|
this.firstSyncPromise = q.defer();
|
||||||
|
@ -1002,6 +1006,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.onNewScreen) {
|
if (this.props.onNewScreen) {
|
||||||
this.props.onNewScreen(screen);
|
this.props.onNewScreen(screen);
|
||||||
}
|
}
|
||||||
|
Analytics.trackPageChange();
|
||||||
},
|
},
|
||||||
|
|
||||||
onAliasClick: function(event, alias) {
|
onAliasClick: function(event, alias) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
const Email = require('../../email');
|
const Email = require('../../email');
|
||||||
const AddThreepid = require('../../AddThreepid');
|
const AddThreepid = require('../../AddThreepid');
|
||||||
const SdkConfig = require('../../SdkConfig');
|
const SdkConfig = require('../../SdkConfig');
|
||||||
|
import Analytics from '../../Analytics';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import * as languageHandler from '../../languageHandler';
|
import * as languageHandler from '../../languageHandler';
|
||||||
|
@ -90,12 +91,25 @@ const SETTINGS_LABELS = [
|
||||||
*/
|
*/
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ANALYTICS_SETTINGS_LABELS = [
|
||||||
|
{
|
||||||
|
id: 'analyticsOptOut',
|
||||||
|
label: 'Opt out of analytics',
|
||||||
|
fn: function(checked) {
|
||||||
|
Analytics[checked ? 'disable' : 'enable']();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
|
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
|
||||||
// since they will be translated when rendered.
|
// since they will be translated when rendered.
|
||||||
const CRYPTO_SETTINGS_LABELS = [
|
const CRYPTO_SETTINGS_LABELS = [
|
||||||
{
|
{
|
||||||
id: 'blacklistUnverifiedDevices',
|
id: 'blacklistUnverifiedDevices',
|
||||||
label: 'Never send encrypted messages to unverified devices from this device',
|
label: 'Never send encrypted messages to unverified devices from this device',
|
||||||
|
fn: function(checked) {
|
||||||
|
MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// XXX: this is here for documentation; the actual setting is managed via RoomSettings
|
// XXX: this is here for documentation; the actual setting is managed via RoomSettings
|
||||||
// {
|
// {
|
||||||
|
@ -599,7 +613,12 @@ module.exports = React.createClass({
|
||||||
<input id={ setting.id }
|
<input id={ setting.id }
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
defaultChecked={ this._syncedSettings[setting.id] }
|
defaultChecked={ this._syncedSettings[setting.id] }
|
||||||
onChange={ (e) => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
|
onChange={
|
||||||
|
(e) => {
|
||||||
|
UserSettingsStore.setSyncedSetting(setting.id, e.target.checked);
|
||||||
|
if (setting.fn) setting.fn(e.target.checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={ setting.id }>
|
<label htmlFor={ setting.id }>
|
||||||
{ _t(setting.label) }
|
{ _t(setting.label) }
|
||||||
|
@ -675,7 +694,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderLocalSetting: function(setting) {
|
_renderLocalSetting: function(setting) {
|
||||||
const client = MatrixClientPeg.get();
|
|
||||||
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
||||||
<input id={ setting.id }
|
<input id={ setting.id }
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@ -683,11 +701,9 @@ module.exports = React.createClass({
|
||||||
onChange={
|
onChange={
|
||||||
(e) => {
|
(e) => {
|
||||||
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
|
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
|
||||||
if (setting.id === 'blacklistUnverifiedDevices') { // XXX: this is a bit ugly
|
if (setting.fn) setting.fn(e.target.checked);
|
||||||
client.setGlobalBlacklistUnverifiedDevices(e.target.checked);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={ setting.id }>
|
<label htmlFor={ setting.id }>
|
||||||
{ _t(setting.label) }
|
{ _t(setting.label) }
|
||||||
|
@ -722,6 +738,16 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderAnalyticsControl: function() {
|
||||||
|
return <div>
|
||||||
|
<h3>{ _t('Analytics') }</h3>
|
||||||
|
<div className="mx_UserSettings_section">
|
||||||
|
{_t('Riot collects anonymous analytics to allow us to improve the application.')}
|
||||||
|
{ANALYTICS_SETTINGS_LABELS.map( this._renderLocalSetting )}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
_renderLabs: function() {
|
_renderLabs: function() {
|
||||||
// default to enabled if undefined
|
// default to enabled if undefined
|
||||||
if (this.props.enableLabs === false) return null;
|
if (this.props.enableLabs === false) return null;
|
||||||
|
@ -1019,6 +1045,8 @@ module.exports = React.createClass({
|
||||||
{this._renderBulkOptions()}
|
{this._renderBulkOptions()}
|
||||||
{this._renderBugReport()}
|
{this._renderBugReport()}
|
||||||
|
|
||||||
|
{this._renderAnalyticsControl()}
|
||||||
|
|
||||||
<h3>{ _t("Advanced") }</h3>
|
<h3>{ _t("Advanced") }</h3>
|
||||||
|
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
|
|
|
@ -67,7 +67,7 @@ export default React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onKeyDown={this._onKeyDown} className={this.props.className}>
|
<div onKeyDown={this._onKeyDown} className={this.props.className}>
|
||||||
<AccessibleButton onClick={this._onCancelClick}
|
<AccessibleButton onClick={this._onCancelClick}
|
||||||
|
|
|
@ -33,7 +33,7 @@ const TRUNCATE_QUERY_LIST = 40;
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: "ChatInviteDialog",
|
displayName: "ChatInviteDialog",
|
||||||
propTypes: {
|
propTypes: {
|
||||||
title: React.PropTypes.string,
|
title: React.PropTypes.string.isRequired,
|
||||||
description: React.PropTypes.oneOfType([
|
description: React.PropTypes.oneOfType([
|
||||||
React.PropTypes.element,
|
React.PropTypes.element,
|
||||||
React.PropTypes.string,
|
React.PropTypes.string,
|
||||||
|
|
|
@ -674,6 +674,9 @@
|
||||||
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)schanged their avatar %(repeats)s times",
|
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)schanged their avatar %(repeats)s times",
|
||||||
"%(severalUsers)schanged their avatar": "%(severalUsers)schanged their avatar",
|
"%(severalUsers)schanged their avatar": "%(severalUsers)schanged their avatar",
|
||||||
"%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar",
|
"%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar",
|
||||||
|
"Analytics": "Analytics",
|
||||||
|
"Opt out of analytics": "Opt out of analytics",
|
||||||
|
"Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.",
|
||||||
"Please select the destination room for this message": "Please select the destination room for this message",
|
"Please select the destination room for this message": "Please select the destination room for this message",
|
||||||
"Passphrases must match": "Passphrases must match",
|
"Passphrases must match": "Passphrases must match",
|
||||||
"Passphrase must not be empty": "Passphrase must not be empty",
|
"Passphrase must not be empty": "Passphrase must not be empty",
|
||||||
|
|
Loading…
Reference in a new issue