diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index 4b2fa67c1c..3ed3b0097f 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -1111,6 +1111,7 @@ export default class RoomView extends React.Component {
dis.dispatch({
action: 'join_room',
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
+ _type: "unknown", // TODO: instrumentation
});
return Promise.resolve();
});
diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx
index 64ee94628e..4847d41fa8 100644
--- a/src/components/structures/UserMenu.tsx
+++ b/src/components/structures/UserMenu.tsx
@@ -23,7 +23,7 @@ import { _t } from "../../languageHandler";
import { ContextMenuButton } from "./ContextMenu";
import {USER_NOTIFICATIONS_TAB, USER_SECURITY_TAB} from "../views/dialogs/UserSettingsDialog";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
-import RedesignFeedbackDialog from "../views/dialogs/RedesignFeedbackDialog";
+import FeedbackDialog from "../views/dialogs/FeedbackDialog";
import Modal from "../../Modal";
import LogoutDialog from "../views/dialogs/LogoutDialog";
import SettingsStore from "../../settings/SettingsStore";
@@ -186,7 +186,7 @@ export default class UserMenu extends React.Component {
ev.preventDefault();
ev.stopPropagation();
- Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
+ Modal.createTrackedDialog('Feedback Dialog', '', FeedbackDialog);
this.setState({contextMenuPosition: null}); // also close the menu
};
diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js
index 3fa2713a35..54d4b5de83 100644
--- a/src/components/structures/auth/ForgotPassword.js
+++ b/src/components/structures/auth/ForgotPassword.js
@@ -26,6 +26,7 @@ import PasswordReset from "../../../PasswordReset";
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import classNames from 'classnames';
import AuthPage from "../../views/auth/AuthPage";
+import CountlyAnalytics from "../../../CountlyAnalytics";
// Phases
// Show controls to configure server details
@@ -64,6 +65,12 @@ export default class ForgotPassword extends React.Component {
serverRequiresIdServer: null,
};
+ constructor(props) {
+ super(props);
+
+ CountlyAnalytics.instance.track("onboarding_forgot_password_begin");
+ }
+
componentDidMount() {
this.reset = null;
this._checkServerLiveliness(this.props.serverConfig);
@@ -299,6 +306,8 @@ export default class ForgotPassword extends React.Component {
value={this.state.email}
onChange={this.onInputChanged.bind(this, "email")}
autoFocus
+ onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_focus")}
+ onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_blur")}
/>
@@ -308,6 +317,8 @@ export default class ForgotPassword extends React.Component {
label={_t('Password')}
value={this.state.password}
onChange={this.onInputChanged.bind(this, "password")}
+ onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_focus")}
+ onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_blur")}
/>
CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword2_focus")}
+ onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword2_blur")}
/>
{_t(
diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js
index 118eed59e3..c3cbac0442 100644
--- a/src/components/structures/auth/Login.js
+++ b/src/components/structures/auth/Login.js
@@ -30,6 +30,7 @@ import SSOButton from "../../views/elements/SSOButton";
import PlatformPeg from '../../../PlatformPeg';
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
+import CountlyAnalytics from "../../../CountlyAnalytics";
// For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
@@ -126,6 +127,8 @@ export default class LoginComponent extends React.Component {
'm.login.cas': () => this._renderSsoStep("cas"),
'm.login.sso': () => this._renderSsoStep("sso"),
};
+
+ CountlyAnalytics.instance.track("onboarding_login_begin");
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
diff --git a/src/components/views/auth/CaptchaForm.js b/src/components/views/auth/CaptchaForm.js
index 783d519621..5cce93f0b8 100644
--- a/src/components/views/auth/CaptchaForm.js
+++ b/src/components/views/auth/CaptchaForm.js
@@ -17,6 +17,7 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
+import CountlyAnalytics from "../../../CountlyAnalytics";
const DIV_ID = 'mx_recaptcha';
@@ -45,6 +46,8 @@ export default class CaptchaForm extends React.Component {
this._captchaWidgetId = null;
this._recaptchaContainer = createRef();
+
+ CountlyAnalytics.instance.track("onboarding_grecaptcha_begin");
}
componentDidMount() {
@@ -99,10 +102,12 @@ export default class CaptchaForm extends React.Component {
console.log("Loaded recaptcha script.");
try {
this._renderRecaptcha(DIV_ID);
+ CountlyAnalytics.instance.track("onboarding_grecaptcha_loaded");
} catch (e) {
this.setState({
errorText: e.toString(),
});
+ CountlyAnalytics.instance.track("onboarding_grecaptcha_error", { error: e.toString() });
}
}
diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js
index 47263c1e21..f49e6959fb 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.js
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.js
@@ -26,6 +26,7 @@ import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
import Spinner from "../elements/Spinner";
+import CountlyAnalytics from "../../../CountlyAnalytics";
/* This file contains a collection of components which are used by the
* InteractiveAuth to prompt the user to enter the information needed
@@ -189,6 +190,7 @@ export class RecaptchaAuthEntry extends React.Component {
}
_onCaptchaResponse = response => {
+ CountlyAnalytics.instance.track("onboarding_grecaptcha_submit");
this.props.submitAuthDict({
type: RecaptchaAuthEntry.LOGIN_TYPE,
response: response,
@@ -297,6 +299,8 @@ export class TermsAuthEntry extends React.Component {
toggledPolicies: initToggles,
policies: pickedPolicies,
};
+
+ CountlyAnalytics.instance.track("onboarding_terms_begin");
}
@@ -326,8 +330,12 @@ export class TermsAuthEntry extends React.Component {
allChecked = allChecked && checked;
}
- if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
- else this.setState({errorText: _t("Please review and accept all of the homeserver's policies")});
+ if (allChecked) {
+ this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
+ CountlyAnalytics.instance.track("onboarding_terms_complete");
+ } else {
+ this.setState({errorText: _t("Please review and accept all of the homeserver's policies")});
+ }
};
render() {
diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js
index 3bd9b557bc..405f9051b9 100644
--- a/src/components/views/auth/PasswordLogin.js
+++ b/src/components/views/auth/PasswordLogin.js
@@ -24,6 +24,7 @@ import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import AccessibleButton from "../elements/AccessibleButton";
+import CountlyAnalytics from "../../../CountlyAnalytics";
/**
* A pure UI component which displays a username/password form.
@@ -150,7 +151,20 @@ export default class PasswordLogin extends React.Component {
this.props.onUsernameChanged(ev.target.value);
}
+ onUsernameFocus() {
+ if (this.state.loginType === PasswordLogin.LOGIN_FIELD_MXID) {
+ CountlyAnalytics.instance.track("onboarding_login_mxid_focus");
+ } else {
+ CountlyAnalytics.instance.track("onboarding_login_email_focus");
+ }
+ }
+
onUsernameBlur(ev) {
+ if (this.state.loginType === PasswordLogin.LOGIN_FIELD_MXID) {
+ CountlyAnalytics.instance.track("onboarding_login_mxid_blur");
+ } else {
+ CountlyAnalytics.instance.track("onboarding_login_email_blur");
+ }
this.props.onUsernameBlur(ev.target.value);
}
@@ -161,6 +175,7 @@ export default class PasswordLogin extends React.Component {
loginType: loginType,
username: "", // Reset because email and username use the same state
});
+ CountlyAnalytics.instance.track("onboarding_login_type_changed", { loginType });
}
onPhoneCountryChanged(country) {
@@ -176,8 +191,13 @@ export default class PasswordLogin extends React.Component {
this.props.onPhoneNumberChanged(ev.target.value);
}
+ onPhoneNumberFocus() {
+ CountlyAnalytics.instance.track("onboarding_login_phone_number_focus");
+ }
+
onPhoneNumberBlur(ev) {
this.props.onPhoneNumberBlur(ev.target.value);
+ CountlyAnalytics.instance.track("onboarding_login_phone_number_blur");
}
onPasswordChanged(ev) {
@@ -202,6 +222,7 @@ export default class PasswordLogin extends React.Component {
placeholder="joe@example.com"
value={this.state.username}
onChange={this.onUsernameChanged}
+ onFocus={this.onUsernameFocus}
onBlur={this.onUsernameBlur}
disabled={this.props.disableSubmit}
autoFocus={autoFocus}
@@ -216,6 +237,7 @@ export default class PasswordLogin extends React.Component {
label={_t("Username")}
value={this.state.username}
onChange={this.onUsernameChanged}
+ onFocus={this.onUsernameFocus}
onBlur={this.onUsernameBlur}
disabled={this.props.disableSubmit}
autoFocus={autoFocus}
@@ -240,6 +262,7 @@ export default class PasswordLogin extends React.Component {
value={this.state.phoneNumber}
prefixComponent={phoneCountry}
onChange={this.onPhoneNumberChanged}
+ onFocus={this.onPhoneNumberFocus}
onBlur={this.onPhoneNumberBlur}
disabled={this.props.disableSubmit}
autoFocus={autoFocus}
diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js
index c07486d3bd..db7d1df994 100644
--- a/src/components/views/auth/RegistrationForm.js
+++ b/src/components/views/auth/RegistrationForm.js
@@ -29,6 +29,7 @@ import { SAFE_LOCALPART_REGEX } from '../../../Registration';
import withValidation from '../elements/Validation';
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import PassphraseField from "./PassphraseField";
+import CountlyAnalytics from "../../../CountlyAnalytics";
const FIELD_EMAIL = 'field_email';
const FIELD_PHONE_NUMBER = 'field_phone_number';
@@ -77,6 +78,8 @@ export default class RegistrationForm extends React.Component {
passwordConfirm: this.props.defaultPassword || "",
passwordComplexity: null,
};
+
+ CountlyAnalytics.instance.track("onboarding_registration_begin");
}
onSubmit = async ev => {
@@ -86,6 +89,7 @@ export default class RegistrationForm extends React.Component {
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
if (!allFieldsValid) {
+ CountlyAnalytics.instance.track("onboarding_registration_submit_failed");
return;
}
@@ -110,6 +114,8 @@ export default class RegistrationForm extends React.Component {
return;
}
+ CountlyAnalytics.instance.track("onboarding_registration_submit_warn");
+
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('If you don\'t specify an email address...', '', QuestionDialog, {
title: _t("Warning!"),
@@ -128,6 +134,11 @@ export default class RegistrationForm extends React.Component {
_doSubmit(ev) {
const email = this.state.email.trim();
+
+ CountlyAnalytics.instance.track("onboarding_registration_submit_ok", {
+ email: !!email,
+ });
+
const promise = this.props.onRegisterClick({
username: this.state.username.trim(),
password: this.state.password.trim(),
@@ -422,6 +433,8 @@ export default class RegistrationForm extends React.Component {
value={this.state.email}
onChange={this.onEmailChange}
onValidate={this.onEmailValidate}
+ onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_email_focus")}
+ onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_email_blur")}
/>;
}
@@ -433,6 +446,8 @@ export default class RegistrationForm extends React.Component {
value={this.state.password}
onChange={this.onPasswordChange}
onValidate={this.onPasswordValidate}
+ onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_password_focus")}
+ onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_password_blur")}
/>;
}
@@ -447,6 +462,8 @@ export default class RegistrationForm extends React.Component {
value={this.state.passwordConfirm}
onChange={this.onPasswordConfirmChange}
onValidate={this.onPasswordConfirmValidate}
+ onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_passwordConfirm_focus")}
+ onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_passwordConfirm_blur")}
/>;
}
@@ -487,6 +504,8 @@ export default class RegistrationForm extends React.Component {
value={this.state.username}
onChange={this.onUsernameChange}
onValidate={this.onUsernameValidate}
+ onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_username_focus")}
+ onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_username_blur")}
/>;
}
diff --git a/src/components/views/auth/ServerConfig.js b/src/components/views/auth/ServerConfig.js
index ee6f57a521..e04bf9e25a 100644
--- a/src/components/views/auth/ServerConfig.js
+++ b/src/components/views/auth/ServerConfig.js
@@ -26,6 +26,7 @@ import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
import SdkConfig from "../../../SdkConfig";
import { createClient } from 'matrix-js-sdk/src/matrix';
import classNames from 'classnames';
+import CountlyAnalytics from "../../../CountlyAnalytics";
/*
* A pure UI component which displays the HS and IS to use.
@@ -70,6 +71,8 @@ export default class ServerConfig extends React.PureComponent {
isUrl: props.serverConfig.isUrl,
showIdentityServer: false,
};
+
+ CountlyAnalytics.instance.track("onboarding_custom_server");
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
diff --git a/src/components/views/auth/Welcome.js b/src/components/views/auth/Welcome.js
index 21032f4f1a..0205f4e0b9 100644
--- a/src/components/views/auth/Welcome.js
+++ b/src/components/views/auth/Welcome.js
@@ -23,11 +23,18 @@ import AuthPage from "./AuthPage";
import {_td} from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
+import CountlyAnalytics from "../../../CountlyAnalytics";
// translatable strings for Welcome pages
_td("Sign in with SSO");
export default class Welcome extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ CountlyAnalytics.instance.track("onboarding_welcome");
+ }
+
render() {
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
const LanguageSelector = sdk.getComponent('auth.LanguageSelector');
diff --git a/src/components/views/dialogs/FeedbackDialog.js b/src/components/views/dialogs/FeedbackDialog.js
new file mode 100644
index 0000000000..2515377709
--- /dev/null
+++ b/src/components/views/dialogs/FeedbackDialog.js
@@ -0,0 +1,138 @@
+/*
+Copyright 2018 New Vector Ltd
+
+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 React, {useState} from 'react';
+import QuestionDialog from './QuestionDialog';
+import { _t } from '../../../languageHandler';
+import Field from "../elements/Field";
+import AccessibleButton from "../elements/AccessibleButton";
+import CountlyAnalytics from "../../../CountlyAnalytics";
+import SdkConfig from "../../../SdkConfig";
+import Modal from "../../../Modal";
+import BugReportDialog from "./BugReportDialog";
+import InfoDialog from "./InfoDialog";
+import StyledRadioGroup from "../elements/StyledRadioGroup";
+
+const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" +
+ "?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
+const newIssueUrl = "https://github.com/vector-im/element-web/issues/new";
+
+
+export default (props) => {
+ const [rating, setRating] = useState("");
+ const [comment, setComment] = useState("");
+
+ const onDebugLogsLinkClick = () => {
+ props.onFinished();
+ Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
+ };
+
+ const hasFeedback = CountlyAnalytics.instance.canEnable();
+ const onFinished = (sendFeedback) => {
+ if (hasFeedback && sendFeedback) {
+ CountlyAnalytics.instance.reportFeedback(parseInt(rating, 10), comment);
+ Modal.createTrackedDialog('Feedback sent', '', InfoDialog, {
+ title: _t('Feedback sent'),
+ description: _t('Thank you!'),
+ });
+ props.onFinished();
+ }
+ };
+
+ const brand = SdkConfig.get().brand;
+
+ let countlyFeedbackSection;
+ if (hasFeedback) {
+ countlyFeedbackSection =
+
+
+
{_t("Rate %(brand)s", { brand })}
+
+
{_t("Tell us below how you feel about %(brand)s so far.", { brand })}
+
{_t("Please go into as much detail as you like, so we can track down the problem.")}
+
+
+
+
+ ;
+ }
+
+ let subheading;
+ if (hasFeedback) {
+ subheading = (
+ {_t("There are two ways you can provide feedback and help us improve %(brand)s.", { brand })}
+ );
+ }
+
+ return (
+ { subheading }
+
+
+
{_t("Report a bug")}
+
{
+ _t("Please view existing bugs on Github first. " +
+ "No match? Start a new one.", {}, {
+ existingIssuesLink: (sub) => {
+ return { sub };
+ },
+ newIssueLink: (sub) => {
+ return { sub };
+ },
+ })
+ }
+
{
+ _t("PRO TIP: If you start a bug, please submit debug logs " +
+ "to help us track down the problem.", {}, {
+ debugLogsLink: sub => (
+ {sub}
+ ),
+ })
+ }
+
+ { countlyFeedbackSection }
+ }
+ button={hasFeedback ? _t("Send feedback") : _t("Go back")}
+ buttonDisabled={hasFeedback && rating === ""}
+ onFinished={onFinished}
+ />);
+};
diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js
index 73101056f3..fc3245aa18 100644
--- a/src/components/views/dialogs/InviteDialog.js
+++ b/src/components/views/dialogs/InviteDialog.js
@@ -40,6 +40,7 @@ import RoomListStore from "../../../stores/room-list/RoomListStore";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
+import CountlyAnalytics from "../../../CountlyAnalytics";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */
@@ -325,6 +326,8 @@ export default class InviteDialog extends React.PureComponent {
room.getMembersWithMembership('join').forEach(m => alreadyInvited.add(m.userId));
// add banned users, so we don't try to invite them
room.getMembersWithMembership('ban').forEach(m => alreadyInvited.add(m.userId));
+
+ CountlyAnalytics.instance.trackBeginInvite(props.roomId);
}
this.state = {
@@ -627,6 +630,7 @@ export default class InviteDialog extends React.PureComponent {
};
_inviteUsers = () => {
+ const startTime = CountlyAnalytics.getTimestamp();
this.setState({busy: true});
this._convertFilter();
const targets = this._convertFilter();
@@ -643,6 +647,7 @@ export default class InviteDialog extends React.PureComponent {
}
inviteMultipleToRoom(this.props.roomId, targetIds).then(result => {
+ CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
if (!this._shouldAbortAfterInviteError(result)) { // handles setting error message too
this.props.onFinished();
}
diff --git a/src/components/views/dialogs/QuestionDialog.js b/src/components/views/dialogs/QuestionDialog.js
index d6de60195f..3d90236b08 100644
--- a/src/components/views/dialogs/QuestionDialog.js
+++ b/src/components/views/dialogs/QuestionDialog.js
@@ -17,6 +17,8 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
+import classNames from "classnames";
+
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
@@ -26,12 +28,14 @@ export default class QuestionDialog extends React.Component {
description: PropTypes.node,
extraButtons: PropTypes.node,
button: PropTypes.string,
+ buttonDisabled: PropTypes.bool,
danger: PropTypes.bool,
focus: PropTypes.bool,
onFinished: PropTypes.func.isRequired,
headerImage: PropTypes.string,
quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x].
fixedWidth: PropTypes.bool,
+ className: PropTypes.string,
};
static defaultProps = {
@@ -61,7 +65,7 @@ export default class QuestionDialog extends React.Component {
}
return (
{
- const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" +
- "?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
- const newIssueUrl = "https://github.com/vector-im/element-web/issues/new";
-
- const description1 =
- _t("If you run into any bugs or have feedback you'd like to share, " +
- "please let us know on GitHub.");
- const description2 = _t("To help avoid duplicate issues, " +
- "please view existing issues " +
- "first (and add a +1) or create a new issue " +
- "if you can't find it.", {},
- {
- existingIssuesLink: (sub) => {
- return { sub };
- },
- newIssueLink: (sub) => {
- return { sub };
- },
- });
-
- return ({description1}
{description2}
}
- button={_t("Go back")}
- onFinished={props.onFinished}
- />);
-};
diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js
index 78c7de887d..6fc1d3e1ad 100644
--- a/src/components/views/rooms/EditMessageComposer.js
+++ b/src/components/views/rooms/EditMessageComposer.js
@@ -32,6 +32,7 @@ import BasicMessageComposer from "./BasicMessageComposer";
import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {Action} from "../../../dispatcher/actions";
+import CountlyAnalytics from "../../../CountlyAnalytics";
function _isReply(mxEvent) {
const relatesTo = mxEvent.getContent()["m.relates_to"];
@@ -182,6 +183,7 @@ export default class EditMessageComposer extends React.Component {
}
_sendEdit = () => {
+ const startTime = CountlyAnalytics.getTimestamp();
const editedEvent = this.props.editState.getEvent();
const editContent = createEditContent(this.model, editedEvent);
const newContent = editContent["m.new_content"];
@@ -190,8 +192,9 @@ export default class EditMessageComposer extends React.Component {
if (this._isContentModified(newContent)) {
const roomId = editedEvent.getRoomId();
this._cancelPreviousPendingEdit();
- this.context.sendMessage(roomId, editContent);
+ const prom = this.context.sendMessage(roomId, editContent);
dis.dispatch({action: "message_sent"});
+ CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, true, false, editContent);
}
// close the event editing and focus composer
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index 40eaf77272..3e2c15ab35 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -371,6 +371,7 @@ export default class MessageComposer extends React.Component {
event_id: createEventId,
room_id: replacementRoomId,
auto_join: true,
+ _type: "tombstone", // instrumentation
// Try to join via the server that sent the event. This converts @something:example.org
// into a server domain by splitting on colons and ignoring the first entry ("@something").
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index 4828277d8a..9438cceef5 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -42,6 +42,7 @@ import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RateLimitedFunc from '../../../ratelimitedfunc';
import {Action} from "../../../dispatcher/actions";
+import CountlyAnalytics from "../../../CountlyAnalytics";
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent);
@@ -304,9 +305,10 @@ export default class SendMessageComposer extends React.Component {
const replyToEvent = this.props.replyToEvent;
if (shouldSend) {
+ const startTime = CountlyAnalytics.getTimestamp();
const {roomId} = this.props.room;
const content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent);
- this.context.sendMessage(roomId, content);
+ const prom = this.context.sendMessage(roomId, content);
if (replyToEvent) {
// Clear reply_to_event as we put the message into the queue
// if the send fails, retry will handle resending.
@@ -316,6 +318,7 @@ export default class SendMessageComposer extends React.Component {
});
}
dis.dispatch({action: "message_sent"});
+ CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content);
}
this.sendHistoryManager.save(this.model, replyToEvent);
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
index 61402e8881..a0c7a6f3d0 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
@@ -33,6 +33,7 @@ import SecureBackupPanel from "../../SecureBackupPanel";
import SettingsStore from "../../../../../settings/SettingsStore";
import {UIFeature} from "../../../../../settings/UIFeature";
import {isE2eAdvancedPanelPossible} from "../../E2eAdvancedPanel";
+import CountlyAnalytics from "../../../../../CountlyAnalytics";
export class IgnoredUser extends React.Component {
static propTypes = {
@@ -102,6 +103,7 @@ export default class SecurityUserSettingsTab extends React.Component {
_updateAnalytics = (checked) => {
checked ? Analytics.enable() : Analytics.disable();
+ checked ? CountlyAnalytics.enable() : CountlyAnalytics.disable();
};
_onExportE2eKeysClicked = () => {
@@ -339,7 +341,7 @@ export default class SecurityUserSettingsTab extends React.Component {
}
let privacySection;
- if (Analytics.canEnable()) {
+ if (Analytics.canEnable() || CountlyAnalytics.canEnable()) {
privacySection =
{_t("Privacy")}
diff --git a/src/createRoom.ts b/src/createRoom.ts
index 34eb65df4e..a42fcc5e7b 100644
--- a/src/createRoom.ts
+++ b/src/createRoom.ts
@@ -28,6 +28,7 @@ import DMRoomMap from "./utils/DMRoomMap";
import {getAddressType} from "./UserAddress";
import { getE2EEWellKnown } from "./utils/WellKnownUtils";
import GroupStore from "./stores/GroupStore";
+import CountlyAnalytics from "./CountlyAnalytics";
// we define a number of interfaces which take their names from the js-sdk
/* eslint-disable camelcase */
@@ -108,6 +109,8 @@ export default function createRoom(opts: IOpts): Promise
{
if (opts.guestAccess === undefined) opts.guestAccess = true;
if (opts.encryption === undefined) opts.encryption = false;
+ const startTime = CountlyAnalytics.getTimestamp();
+
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const Loader = sdk.getComponent("elements.Spinner");
@@ -203,6 +206,7 @@ export default function createRoom(opts: IOpts): Promise {
joining: true,
});
}
+ CountlyAnalytics.instance.trackRoomCreate(startTime, roomId);
return roomId;
}, function(err) {
// Raise the error if the caller requested that we do so.
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 1548dd5c13..01a6731562 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1699,6 +1699,18 @@
"There was an error updating your community. The server is unable to process your request.": "There was an error updating your community. The server is unable to process your request.",
"Update community": "Update community",
"An error has occurred.": "An error has occurred.",
+ "Feedback sent": "Feedback sent",
+ "Rate %(brand)s": "Rate %(brand)s",
+ "Tell us below how you feel about %(brand)s so far.": "Tell us below how you feel about %(brand)s so far.",
+ "Please go into as much detail as you like, so we can track down the problem.": "Please go into as much detail as you like, so we can track down the problem.",
+ "Add comment": "Add comment",
+ "Comment": "Comment",
+ "Feedback": "Feedback",
+ "There are two ways you can provide feedback and help us improve %(brand)s.": "There are two ways you can provide feedback and help us improve %(brand)s.",
+ "Report a bug": "Report a bug",
+ "Please view existing bugs on Github first. No match? Start a new one.": "Please view existing bugs on Github first. No match? Start a new one.",
+ "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.",
+ "Send feedback": "Send feedback",
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
"Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.",
"Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.",
@@ -1771,9 +1783,6 @@
"Use this session to verify your new one, granting it access to encrypted messages:": "Use this session to verify your new one, granting it access to encrypted messages:",
"If you didn’t sign in to this session, your account may be compromised.": "If you didn’t sign in to this session, your account may be compromised.",
"This wasn't me": "This wasn't me",
- "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.",
- "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.",
- "Report bugs & give feedback": "Report bugs & give feedback",
"Please fill why you're reporting.": "Please fill why you're reporting.",
"Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator",
"Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.",
@@ -2132,7 +2141,6 @@
"Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
"Failed to find the general chat for this community": "Failed to find the general chat for this community",
- "Feedback": "Feedback",
"Notification settings": "Notification settings",
"Security & privacy": "Security & privacy",
"All settings": "All settings",
diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index be1141fa1e..f4c0c1b15c 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -28,6 +28,7 @@ import { _t } from '../languageHandler';
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
import {ActionPayload} from "../dispatcher/payloads";
import {retry} from "../utils/promise";
+import CountlyAnalytics from "../CountlyAnalytics";
const NUM_JOIN_RETRY = 5;
@@ -264,6 +265,7 @@ class RoomViewStore extends Store {
}
private async joinRoom(payload: ActionPayload) {
+ const startTime = CountlyAnalytics.getTimestamp();
this.setState({
joining: true,
});
@@ -275,6 +277,7 @@ class RoomViewStore extends Store {
// if we received a Gateway timeout then retry
return err.httpStatus === 504;
});
+ CountlyAnalytics.instance.trackRoomJoin(startTime, this.state.roomId, payload._type);
// We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not
// have come down the sync stream yet, and that's the point at which we'd consider the user joined to the
diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts
index e5953d739d..4e67416d92 100644
--- a/src/stores/widgets/StopGapWidget.ts
+++ b/src/stores/widgets/StopGapWidget.ts
@@ -53,6 +53,7 @@ import WidgetOpenIDPermissionsDialog from "../../components/views/dialogs/Widget
import {ModalWidgetStore} from "../ModalWidgetStore";
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import {getCustomTheme} from "../../theme";
+import CountlyAnalytics from "../../CountlyAnalytics";
// TODO: Destroy all of this code
@@ -301,6 +302,7 @@ export class StopGapWidget extends EventEmitter {
this.messaging.on("action:set_always_on_screen",
(ev: CustomEvent) => {
if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) {
+ CountlyAnalytics.instance.trackJoinCall(this.appTileProps.room.roomId, true, true);
ActiveWidgetStore.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value);
ev.preventDefault();
this.messaging.transport.reply(ev.detail, {}); // ack