Remove Piwik support (#8835)

* Remove all mentions of Piwik

* Kill off all consumer of the old Piwik Analytics module

* Simplify ModalManager interface

* i18n

* Attempt to fix old e2e tests

* Remove unused component

* Iterate PR
This commit is contained in:
Michael Telatynski 2022-06-14 17:51:51 +01:00 committed by GitHub
parent 7d14d15ba6
commit 3c5c2bef6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
142 changed files with 446 additions and 1412 deletions

View file

@ -89,7 +89,6 @@
@import "./views/context_menus/_IconizedContextMenu.scss"; @import "./views/context_menus/_IconizedContextMenu.scss";
@import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_MessageContextMenu.scss";
@import "./views/dialogs/_AddExistingToSpaceDialog.scss"; @import "./views/dialogs/_AddExistingToSpaceDialog.scss";
@import "./views/dialogs/_Analytics.scss";
@import "./views/dialogs/_AnalyticsLearnMoreDialog.scss"; @import "./views/dialogs/_AnalyticsLearnMoreDialog.scss";
@import "./views/dialogs/_BugReportDialog.scss"; @import "./views/dialogs/_BugReportDialog.scss";
@import "./views/dialogs/_BulkRedactDialog.scss"; @import "./views/dialogs/_BulkRedactDialog.scss";

View file

@ -1,23 +0,0 @@
/*
Copyright 2019 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.
*/
.mx_AnalyticsModal table {
margin: 10px 0px;
.mx_AnalyticsModal_label {
width: 400px;
}
}

View file

@ -34,7 +34,6 @@ import type { Renderer } from "react-dom";
import RightPanelStore from "../stores/right-panel/RightPanelStore"; import RightPanelStore from "../stores/right-panel/RightPanelStore";
import WidgetStore from "../stores/WidgetStore"; import WidgetStore from "../stores/WidgetStore";
import CallHandler from "../CallHandler"; import CallHandler from "../CallHandler";
import { Analytics } from "../Analytics";
import UserActivity from "../UserActivity"; import UserActivity from "../UserActivity";
import { ModalWidgetStore } from "../stores/ModalWidgetStore"; import { ModalWidgetStore } from "../stores/ModalWidgetStore";
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
@ -91,7 +90,6 @@ declare global {
mxWidgetStore: WidgetStore; mxWidgetStore: WidgetStore;
mxWidgetLayoutStore: WidgetLayoutStore; mxWidgetLayoutStore: WidgetLayoutStore;
mxCallHandler: CallHandler; mxCallHandler: CallHandler;
mxAnalytics: Analytics;
mxUserActivity: UserActivity; mxUserActivity: UserActivity;
mxModalWidgetStore: ModalWidgetStore; mxModalWidgetStore: ModalWidgetStore;
mxVoipUserMapper: VoipUserMapper; mxVoipUserMapper: VoipUserMapper;

View file

@ -206,7 +206,7 @@ export default class AddThreepid {
continueKind: "primary", continueKind: "primary",
}, },
}; };
const { finished } = Modal.createTrackedDialog('Add Email', '', InteractiveAuthDialog, { const { finished } = Modal.createDialog(InteractiveAuthDialog, {
title: _t("Add Email Address"), title: _t("Add Email Address"),
matrixClient: MatrixClientPeg.get(), matrixClient: MatrixClientPeg.get(),
authData: e.data, authData: e.data,
@ -319,7 +319,7 @@ export default class AddThreepid {
continueKind: "primary", continueKind: "primary",
}, },
}; };
const { finished } = Modal.createTrackedDialog('Add MSISDN', '', InteractiveAuthDialog, { const { finished } = Modal.createDialog(InteractiveAuthDialog, {
title: _t("Add Phone Number"), title: _t("Add Phone Number"),
matrixClient: MatrixClientPeg.get(), matrixClient: MatrixClientPeg.get(),
authData: e.data, authData: e.data,

View file

@ -1,460 +0,0 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2020 - 2022 The Matrix.org Foundation C.I.C.
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 from 'react';
import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk";
import { randomString } from 'matrix-js-sdk/src/randomstring';
import { getCurrentLanguage, _t, _td, IVariables } from './languageHandler';
import PlatformPeg from './PlatformPeg';
import SdkConfig from './SdkConfig';
import Modal from './Modal';
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import { SnakedObject } from "./utils/SnakedObject";
import { IConfigOptions } from "./IConfigOptions";
// Note: we keep the analytics redaction on groups in case people have old links.
const hashRegex = /#\/(groups?|room|user|settings|register|login|forgot_password|home|directory)/;
const hashVarRegex = /#\/(group|room|user)\/.*$/;
// Remove all but the first item in the hash path. Redact unexpected hashes.
function getRedactedHash(hash: string): string {
// Don't leak URLs we aren't expecting - they could contain tokens/PII
const match = hashRegex.exec(hash);
if (!match) {
logger.warn(`Unexpected hash location "${hash}"`);
return '#/<unexpected hash location>';
}
if (hashVarRegex.test(hash)) {
return hash.replace(hashVarRegex, "#/$1/<redacted>");
}
return hash.replace(hashRegex, "#/$1");
}
// Return the current origin, path and hash separated with a `/`. This does
// not include query parameters.
function getRedactedUrl(): string {
const { origin, hash } = window.location;
let { pathname } = window.location;
// Redact paths which could contain unexpected PII
if (origin.startsWith('file://')) {
pathname = "/<redacted>/";
}
return origin + pathname + getRedactedHash(hash);
}
interface IData {
/* eslint-disable camelcase */
gt_ms?: string;
e_c?: string;
e_a?: string;
e_n?: string;
e_v?: string;
ping?: string;
/* eslint-enable camelcase */
}
interface IVariable {
id: number;
expl: string; // explanation
example: string; // example value
getTextVariables?(): IVariables; // object to pass as 2nd argument to `_t`
}
const customVariables: Record<string, IVariable> = {
// The Matomo installation at https://matomo.riot.im is currently configured
// with a limit of 10 custom variables.
'App Platform': {
id: 1,
expl: _td('The platform you\'re on'),
example: 'Electron Platform',
},
'App Version': {
id: 2,
expl: _td('The version of %(brand)s'),
getTextVariables: () => ({
brand: SdkConfig.get().brand,
}),
example: '15.0.0',
},
'User Type': {
id: 3,
expl: _td('Whether or not you\'re logged in (we don\'t record your username)'),
example: 'Logged In',
},
'Chosen Language': {
id: 4,
expl: _td('Your language of choice'),
example: 'en',
},
'Instance': {
id: 5,
expl: _td('Which officially provided instance you are using, if any'),
example: 'app',
},
'RTE: Uses Richtext Mode': {
id: 6,
expl: _td('Whether or not you\'re using the Richtext mode of the Rich Text Editor'),
example: 'off',
},
'Homeserver URL': {
id: 7,
expl: _td('Your homeserver\'s URL'),
example: 'https://matrix.org',
},
'Touch Input': {
id: 8,
expl: _td("Whether you're using %(brand)s on a device where touch is the primary input mechanism"),
getTextVariables: () => ({
brand: SdkConfig.get().brand,
}),
example: 'false',
},
'Breadcrumbs': {
id: 9,
expl: _td("Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)"),
example: 'disabled',
},
'Installed PWA': {
id: 10,
expl: _td("Whether you're using %(brand)s as an installed Progressive Web App"),
getTextVariables: () => ({
brand: SdkConfig.get().brand,
}),
example: 'false',
},
};
function whitelistRedact(whitelist: string[], str: string): string {
if (whitelist.includes(str)) return str;
return '<redacted>';
}
const UID_KEY = "mx_Riot_Analytics_uid";
const CREATION_TS_KEY = "mx_Riot_Analytics_cts";
const VISIT_COUNT_KEY = "mx_Riot_Analytics_vc";
const LAST_VISIT_TS_KEY = "mx_Riot_Analytics_lvts";
function getUid(): string {
try {
let data = localStorage?.getItem(UID_KEY);
if (!data && localStorage) {
localStorage.setItem(UID_KEY, data = randomString(16));
}
return data;
} catch (e) {
logger.error("Analytics error: ", e);
return "";
}
}
const HEARTBEAT_INTERVAL = 30 * 1000; // seconds
export class Analytics {
private baseUrl: URL = null;
private visitVariables: Record<number, [string, string]> = {}; // {[id: number]: [name: string, value: string]}
private firstPage = true;
private heartbeatIntervalID: number = null;
private readonly creationTs: string;
private readonly lastVisitTs: string;
private readonly visitCount: string;
constructor() {
this.creationTs = localStorage && localStorage.getItem(CREATION_TS_KEY);
if (!this.creationTs && localStorage) {
localStorage.setItem(CREATION_TS_KEY, this.creationTs = String(new Date().getTime()));
}
this.lastVisitTs = localStorage && localStorage.getItem(LAST_VISIT_TS_KEY);
this.visitCount = localStorage && localStorage.getItem(VISIT_COUNT_KEY) || "0";
this.visitCount = String(parseInt(this.visitCount, 10) + 1); // increment
if (localStorage) {
localStorage.setItem(VISIT_COUNT_KEY, this.visitCount);
}
}
public get disabled() {
return !this.baseUrl;
}
public canEnable() {
const piwikConfig = SdkConfig.get("piwik");
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
if (typeof piwikConfig === 'object') {
piwik = new SnakedObject(piwikConfig);
}
return navigator.doNotTrack !== "1" && piwik?.get("site_id");
}
/**
* Enable Analytics if initialized but disabled
* otherwise try and initalize, no-op if piwik config missing
*/
public async enable() {
if (!this.disabled) return;
if (!this.canEnable()) return;
const piwikConfig = SdkConfig.get("piwik");
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
if (typeof piwikConfig === 'object') {
piwik = new SnakedObject(piwikConfig);
}
this.baseUrl = new URL("piwik.php", piwik.get("url"));
// set constants
this.baseUrl.searchParams.set("rec", "1"); // rec is required for tracking
this.baseUrl.searchParams.set("idsite", piwik.get("site_id")); // idsite is required for tracking
this.baseUrl.searchParams.set("apiv", "1"); // API version to use
this.baseUrl.searchParams.set("send_image", "0"); // we want a 204, not a tiny GIF
// set user parameters
this.baseUrl.searchParams.set("_id", getUid()); // uuid
this.baseUrl.searchParams.set("_idts", this.creationTs); // first ts
this.baseUrl.searchParams.set("_idvc", this.visitCount); // visit count
if (this.lastVisitTs) {
this.baseUrl.searchParams.set("_viewts", this.lastVisitTs); // last visit ts
}
const platform = PlatformPeg.get();
this.setVisitVariable('App Platform', platform.getHumanReadableName());
try {
this.setVisitVariable('App Version', await platform.getAppVersion());
} catch (e) {
this.setVisitVariable('App Version', 'unknown');
}
this.setVisitVariable('Chosen Language', getCurrentLanguage());
const hostname = window.location.hostname;
if (hostname === 'riot.im') {
this.setVisitVariable('Instance', window.location.pathname);
} else if (hostname.endsWith('.element.io')) {
this.setVisitVariable('Instance', hostname.replace('.element.io', ''));
}
let installedPWA = "unknown";
try {
// Known to work at least for desktop Chrome
installedPWA = String(window.matchMedia('(display-mode: standalone)').matches);
} catch (e) { }
this.setVisitVariable('Installed PWA', installedPWA);
let touchInput = "unknown";
try {
// MDN claims broad support across browsers
touchInput = String(window.matchMedia('(pointer: coarse)').matches);
} catch (e) { }
this.setVisitVariable('Touch Input', touchInput);
// start heartbeat
this.heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL);
}
/**
* Disable Analytics, stop the heartbeat and clear identifiers from localStorage
*/
public disable() {
if (this.disabled) return;
this.trackEvent('Analytics', 'opt-out');
window.clearInterval(this.heartbeatIntervalID);
this.baseUrl = null;
this.visitVariables = {};
localStorage.removeItem(UID_KEY);
localStorage.removeItem(CREATION_TS_KEY);
localStorage.removeItem(VISIT_COUNT_KEY);
localStorage.removeItem(LAST_VISIT_TS_KEY);
}
private async track(data: IData) {
if (this.disabled) return;
const now = new Date();
const params = {
...data,
url: getRedactedUrl(),
_cvar: JSON.stringify(this.visitVariables), // user custom vars
res: `${window.screen.width}x${window.screen.height}`, // resolution as WWWWxHHHH
rand: String(Math.random()).slice(2, 8), // random nonce to cache-bust
h: now.getHours(),
m: now.getMinutes(),
s: now.getSeconds(),
};
const url = new URL(this.baseUrl.toString()); // copy
for (const key in params) {
url.searchParams.set(key, params[key]);
}
try {
await window.fetch(url.toString(), {
method: "GET",
mode: "no-cors",
cache: "no-cache",
redirect: "follow",
});
} catch (e) {
logger.error("Analytics error: ", e);
}
}
public ping() {
this.track({
ping: "1",
});
localStorage.setItem(LAST_VISIT_TS_KEY, String(new Date().getTime())); // update last visit ts
}
public trackPageChange(generationTimeMs?: number) {
if (this.disabled) return;
if (this.firstPage) {
// De-duplicate first page
// router seems to hit the fn twice
this.firstPage = false;
return;
}
if (typeof generationTimeMs !== 'number') {
logger.warn('Analytics.trackPageChange: expected generationTimeMs to be a number');
// But continue anyway because we still want to track the change
}
this.track({
gt_ms: String(generationTimeMs),
});
}
public trackEvent(category: string, action: string, name?: string, value?: string) {
if (this.disabled) return;
this.track({
e_c: category,
e_a: action,
e_n: name,
e_v: value,
});
}
private setVisitVariable(key: keyof typeof customVariables, value: string) {
if (this.disabled) return;
this.visitVariables[customVariables[key].id] = [key, value];
}
public setLoggedIn(isGuest: boolean, homeserverUrl: string) {
if (this.disabled) return;
const piwikConfig = SdkConfig.get("piwik");
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
if (typeof piwikConfig === 'object') {
piwik = new SnakedObject(piwikConfig);
}
if (!piwik) return;
const whitelistedHSUrls = piwik.get("whitelisted_hs_urls", "whitelistedHSUrls") || [];
this.setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
this.setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
}
public setBreadcrumbs(state: boolean) {
if (this.disabled) return;
this.setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled');
}
public showDetailsModal = () => {
let rows = [];
if (!this.disabled) {
rows = Object.values(this.visitVariables);
} else {
rows = Object.keys(customVariables).map(
(k) => [
k,
_t('e.g. %(exampleValue)s', { exampleValue: customVariables[k].example }),
],
);
}
const resolution = `${window.screen.width}x${window.screen.height}`;
const otherVariables = [
{
expl: _td('Every page you use in the app'),
value: _t(
'e.g. <CurrentPageURL>',
{},
{
CurrentPageURL: getRedactedUrl,
},
),
},
{ expl: _td('Your user agent'), value: navigator.userAgent },
{ expl: _td('Your device resolution'), value: resolution },
];
const piwikConfig = SdkConfig.get("piwik");
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
if (typeof piwikConfig === 'object') {
piwik = new SnakedObject(piwikConfig);
}
const cookiePolicyUrl = piwik?.get("policy_url");
const cookiePolicyLink = _t(
"Our complete cookie policy can be found <CookiePolicyLink>here</CookiePolicyLink>.",
{},
{
"CookiePolicyLink": (sub) => {
return <a href={cookiePolicyUrl} target="_blank" rel="noreferrer noopener">{ sub }</a>;
},
});
Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, {
title: _t('Analytics'),
description: <div className="mx_AnalyticsModal">
{ cookiePolicyUrl && <p>{ cookiePolicyLink }</p> }
<div>{ _t('Some examples of the information being sent to us to help make %(brand)s better includes:', {
brand: SdkConfig.get().brand,
}) }</div>
<table>
{ rows.map((row) => <tr key={row[0]}>
<td className="mx_AnalyticsModal_label">{ _t(
customVariables[row[0]].expl,
customVariables[row[0]].getTextVariables ?
customVariables[row[0]].getTextVariables() :
null,
) }</td>
{ row[1] !== undefined && <td><code>{ row[1] }</code></td> }
</tr>) }
{ otherVariables.map((item, index) =>
<tr key={index}>
<td>{ _t(item.expl) }</td>
<td><code>{ item.value }</code></td>
</tr>,
) }
</table>
<div>
{ _t('Where this page includes identifiable information, such as a room, '
+ 'user ID, that data is removed before being sent to the server.') }
</div>
</div>,
});
};
}
if (!window.mxAnalytics) {
window.mxAnalytics = new Analytics();
}
export default window.mxAnalytics;

View file

@ -47,7 +47,6 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import WidgetStore from "./stores/WidgetStore"; import WidgetStore from "./stores/WidgetStore";
import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
import Analytics from './Analytics';
import { UIFeature } from "./settings/UIFeature"; import { UIFeature } from "./settings/UIFeature";
import { Action } from './dispatcher/actions'; import { Action } from './dispatcher/actions';
import VoipUserMapper from './VoipUserMapper'; import VoipUserMapper from './VoipUserMapper';
@ -305,7 +304,6 @@ export default class CallHandler extends EventEmitter {
return; return;
} }
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
this.addCallForRoom(mappedRoomId, call); this.addCallForRoom(mappedRoomId, call);
this.setCallListeners(call); this.setCallListeners(call);
// Explicitly handle first state change // Explicitly handle first state change
@ -447,7 +445,6 @@ export default class CallHandler extends EventEmitter {
call.on(CallEvent.Error, (err: CallError) => { call.on(CallEvent.Error, (err: CallError) => {
if (!this.matchesCallForThisRoom(call)) return; if (!this.matchesCallForThisRoom(call)) return;
Analytics.trackEvent('voip', 'callError', 'error', err.toString());
logger.error("Call error:", err); logger.error("Call error:", err);
if (err.code === CallErrorCode.NoUserMedia) { if (err.code === CallErrorCode.NoUserMedia) {
@ -463,7 +460,7 @@ export default class CallHandler extends EventEmitter {
return; return;
} }
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Call Failed'), title: _t('Call Failed'),
description: err.message, description: err.message,
}); });
@ -471,8 +468,6 @@ export default class CallHandler extends EventEmitter {
call.on(CallEvent.Hangup, () => { call.on(CallEvent.Hangup, () => {
if (!this.matchesCallForThisRoom(call)) return; if (!this.matchesCallForThisRoom(call)) return;
Analytics.trackEvent('voip', 'callHangup');
this.removeCallForRoom(mappedRoomId); this.removeCallForRoom(mappedRoomId);
}); });
call.on(CallEvent.State, (newState: CallState, oldState: CallState) => { call.on(CallEvent.State, (newState: CallState, oldState: CallState) => {
@ -584,7 +579,6 @@ export default class CallHandler extends EventEmitter {
} }
case CallState.Ended: { case CallState.Ended: {
const hangupReason = call.hangupReason; const hangupReason = call.hangupReason;
Analytics.trackEvent('voip', 'callEnded', 'hangupReason', hangupReason);
this.removeCallForRoom(mappedRoomId); this.removeCallForRoom(mappedRoomId);
if (oldState === CallState.InviteSent && call.hangupParty === CallParty.Remote) { if (oldState === CallState.InviteSent && call.hangupParty === CallParty.Remote) {
this.play(AudioID.Busy); this.play(AudioID.Busy);
@ -603,13 +597,13 @@ export default class CallHandler extends EventEmitter {
description = _t("The call could not be established"); description = _t("The call could not be established");
} }
Modal.createTrackedDialog('Call Handler', 'Call Failed', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title, description, title, description,
}); });
} else if ( } else if (
hangupReason === CallErrorCode.AnsweredElsewhere && oldState === CallState.Connecting hangupReason === CallErrorCode.AnsweredElsewhere && oldState === CallState.Connecting
) { ) {
Modal.createTrackedDialog('Call Handler', 'Call Failed', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Answered Elsewhere"), title: _t("Answered Elsewhere"),
description: _t("The call was answered on another device."), description: _t("The call was answered on another device."),
}); });
@ -709,7 +703,7 @@ export default class CallHandler extends EventEmitter {
private showICEFallbackPrompt(): void { private showICEFallbackPrompt(): void {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const code = sub => <code>{ sub }</code>; const code = sub => <code>{ sub }</code>;
Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t("Call failed due to misconfigured server"), title: _t("Call failed due to misconfigured server"),
description: <div> description: <div>
<p>{ _t( <p>{ _t(
@ -759,14 +753,12 @@ export default class CallHandler extends EventEmitter {
</div>; </div>;
} }
Modal.createTrackedDialog('Media capture failed', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title, description, title, description,
}, null, true); }, null, true);
} }
private async placeMatrixCall(roomId: string, type: CallType, transferee?: MatrixCall): Promise<void> { private async placeMatrixCall(roomId: string, type: CallType, transferee?: MatrixCall): Promise<void> {
Analytics.trackEvent('voip', 'placeCall', 'type', type);
const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId; const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId;
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId); logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
@ -789,7 +781,7 @@ export default class CallHandler extends EventEmitter {
try { try {
this.addCallForRoom(roomId, call); this.addCallForRoom(roomId, call);
} catch (e) { } catch (e) {
Modal.createTrackedDialog('Call Handler', 'Existing Call with user', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Already in call'), title: _t('Already in call'),
description: _t("You're already in a call with this person."), description: _t("You're already in a call with this person."),
}); });
@ -821,7 +813,7 @@ export default class CallHandler extends EventEmitter {
// if the runtime env doesn't do VoIP, whine. // if the runtime env doesn't do VoIP, whine.
if (!MatrixClientPeg.get().supportsVoip()) { if (!MatrixClientPeg.get().supportsVoip()) {
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Calls are unsupported'), title: _t('Calls are unsupported'),
description: _t('You cannot place calls in this browser.'), description: _t('You cannot place calls in this browser.'),
}); });
@ -829,7 +821,7 @@ export default class CallHandler extends EventEmitter {
} }
if (MatrixClientPeg.get().getSyncState() === SyncState.Error) { if (MatrixClientPeg.get().getSyncState() === SyncState.Error) {
Modal.createTrackedDialog('Call Handler', 'Sync error', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Connectivity to the server has been lost'), title: _t('Connectivity to the server has been lost'),
description: _t('You cannot place calls without a connection to the server.'), description: _t('You cannot place calls without a connection to the server.'),
}); });
@ -838,7 +830,7 @@ export default class CallHandler extends EventEmitter {
// don't allow > 2 calls to be placed. // don't allow > 2 calls to be placed.
if (this.getAllActiveCalls().length > 1) { if (this.getAllActiveCalls().length > 1) {
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Too Many Calls'), title: _t('Too Many Calls'),
description: _t("You've reached the maximum number of simultaneous calls."), description: _t("You've reached the maximum number of simultaneous calls."),
}); });
@ -856,7 +848,7 @@ export default class CallHandler extends EventEmitter {
const members = room.getJoinedMembers(); const members = room.getJoinedMembers();
if (members.length <= 1) { if (members.length <= 1) {
Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, { Modal.createDialog(ErrorDialog, {
description: _t('You cannot place a call with yourself.'), description: _t('You cannot place a call with yourself.'),
}); });
} else if (members.length === 2) { } else if (members.length === 2) {
@ -901,7 +893,7 @@ export default class CallHandler extends EventEmitter {
if (!this.calls.has(roomId)) return; if (!this.calls.has(roomId)) return;
if (this.getAllActiveCalls().length > 1) { if (this.getAllActiveCalls().length > 1) {
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Too Many Calls'), title: _t('Too Many Calls'),
description: _t("You've reached the maximum number of simultaneous calls."), description: _t("You've reached the maximum number of simultaneous calls."),
}); });
@ -926,7 +918,7 @@ export default class CallHandler extends EventEmitter {
public async dialNumber(number: string, transferee?: MatrixCall): Promise<void> { public async dialNumber(number: string, transferee?: MatrixCall): Promise<void> {
const results = await this.pstnLookup(number); const results = await this.pstnLookup(number);
if (!results || results.length === 0 || !results[0].userid) { if (!results || results.length === 0 || !results[0].userid) {
Modal.createTrackedDialog('', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to look up phone number"), title: _t("Unable to look up phone number"),
description: _t("There was an error looking up the phone number"), description: _t("There was an error looking up the phone number"),
}); });
@ -969,7 +961,7 @@ export default class CallHandler extends EventEmitter {
const results = await this.pstnLookup(destination); const results = await this.pstnLookup(destination);
if (!results || results.length === 0 || !results[0].userid) { if (!results || results.length === 0 || !results[0].userid) {
Modal.createTrackedDialog('', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to transfer call"), title: _t("Unable to transfer call"),
description: _t("There was an error looking up the phone number"), description: _t("There was an error looking up the phone number"),
}); });
@ -998,7 +990,7 @@ export default class CallHandler extends EventEmitter {
await call.transfer(destination); await call.transfer(destination);
} catch (e) { } catch (e) {
logger.log("Failed to transfer call", e); logger.log("Failed to transfer call", e);
Modal.createTrackedDialog('Failed to transfer call', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Transfer Failed'), title: _t('Transfer Failed'),
description: _t('Failed to transfer call'), description: _t('Failed to transfer call'),
}); });
@ -1036,7 +1028,6 @@ export default class CallHandler extends EventEmitter {
private async placeJitsiCall(roomId: string, type: CallType): Promise<void> { private async placeJitsiCall(roomId: string, type: CallType): Promise<void> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
logger.info(`Place conference call in ${roomId}`); logger.info(`Place conference call in ${roomId}`);
Analytics.trackEvent('voip', 'placeConferenceCall');
dis.dispatch({ action: 'appsDrawer', show: true }); dis.dispatch({ action: 'appsDrawer', show: true });
@ -1053,7 +1044,7 @@ export default class CallHandler extends EventEmitter {
logger.log('Jitsi widget added'); logger.log('Jitsi widget added');
} catch (e) { } catch (e) {
if (e.errcode === 'M_FORBIDDEN') { if (e.errcode === 'M_FORBIDDEN') {
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Permission Required'), title: _t('Permission Required'),
description: _t("You do not have permission to start a conference call in this room"), description: _t("You do not have permission to start a conference call in this room"),
}); });
@ -1062,29 +1053,6 @@ export default class CallHandler extends EventEmitter {
} }
} }
public terminateCallApp(roomId: string): void {
logger.info("Terminating conference call in " + roomId);
Modal.createTrackedDialog('Confirm Jitsi Terminate', '', QuestionDialog, {
hasCancelButton: true,
title: _t("End conference"),
description: _t("This will end the conference for everyone. Continue?"),
button: _t("End conference"),
onFinished: (proceed) => {
if (!proceed) return;
// We'll just obliterate them all. There should only ever be one, but might as well
// be safe.
const roomInfo = WidgetStore.instance.getRoom(roomId);
const jitsiWidgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type));
jitsiWidgets.forEach(w => {
// setting invalid content removes it
WidgetUtils.setRoomWidget(roomId, w.id);
});
},
});
}
public hangupCallApp(roomId: string): void { public hangupCallApp(roomId: string): void {
logger.info("Leaving conference call in " + roomId); logger.info("Leaving conference call in " + roomId);

View file

@ -397,7 +397,7 @@ export default class ContentMessages {
} }
if (tooBigFiles.length > 0) { if (tooBigFiles.length > 0) {
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, { const { finished } = Modal.createDialog<[boolean]>(UploadFailureDialog, {
badFiles: tooBigFiles, badFiles: tooBigFiles,
totalFiles: files.length, totalFiles: files.length,
contentMessages: this, contentMessages: this,
@ -413,13 +413,11 @@ export default class ContentMessages {
for (let i = 0; i < okFiles.length; ++i) { for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i]; const file = okFiles[i];
if (!uploadAll) { if (!uploadAll) {
const { finished } = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation', const { finished } = Modal.createDialog<[boolean, boolean]>(UploadConfirmDialog, {
'', UploadConfirmDialog, { file,
file, currentIndex: i,
currentIndex: i, totalFiles: okFiles.length,
totalFiles: okFiles.length, });
},
);
const [shouldContinue, shouldUploadAll] = await finished; const [shouldContinue, shouldUploadAll] = await finished;
if (!shouldContinue) break; if (!shouldContinue) break;
if (shouldUploadAll) { if (shouldUploadAll) {
@ -588,7 +586,7 @@ export default class ContentMessages {
{ fileName: upload.fileName }, { fileName: upload.fileName },
); );
} }
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Upload Failed'), title: _t('Upload Failed'),
description: desc, description: desc,
}); });

View file

@ -18,7 +18,6 @@ import { MatrixError } from "matrix-js-sdk/src/http-api";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Error as ErrorEvent } from "@matrix-org/analytics-events/types/typescript/Error"; import { Error as ErrorEvent } from "@matrix-org/analytics-events/types/typescript/Error";
import Analytics from "./Analytics";
import { PosthogAnalytics } from './PosthogAnalytics'; import { PosthogAnalytics } from './PosthogAnalytics';
export class DecryptionFailure { export class DecryptionFailure {
@ -37,7 +36,6 @@ export type ErrCodeMapFn = (errcode: string) => ErrorCode;
export class DecryptionFailureTracker { export class DecryptionFailureTracker {
private static internalInstance = new DecryptionFailureTracker((total, errorCode, rawError) => { private static internalInstance = new DecryptionFailureTracker((total, errorCode, rawError) => {
Analytics.trackEvent('E2E', 'Decryption failure', errorCode, String(total));
for (let i = 0; i < total; i++) { for (let i = 0; i < total; i++) {
PosthogAnalytics.instance.trackEvent<ErrorEvent>({ PosthogAnalytics.instance.trackEvent<ErrorEvent>({
eventName: "Error", eventName: "Error",

View file

@ -135,18 +135,15 @@ export interface IConfigOptions {
servers: string[]; servers: string[];
}; };
// piwik (matomo) is deprecated in favour of posthog
piwik?: false | { piwik?: false | {
url: string; // piwik instance policy_url: string; // deprecated in favour of `privacy_policy_url` at root instead
site_id: string;
policy_url: string; // cookie policy
whitelisted_hs_urls: string[];
}; };
posthog?: { posthog?: {
project_api_key: string; project_api_key: string;
api_host: string; // hostname api_host: string; // hostname
}; };
analytics_owner?: string; // defaults to `brand` analytics_owner?: string; // defaults to `brand`
privacy_policy_url?: string; // location for cookie policy
// Server hosting upsell options // Server hosting upsell options
hosting_signup_link?: string; // slightly different from `host_signup` hosting_signup_link?: string; // slightly different from `host_signup`

View file

@ -143,27 +143,25 @@ export default class IdentityAuthClient {
!doesAccountDataHaveIdentityServer() && !doesAccountDataHaveIdentityServer() &&
!(await doesIdentityServerHaveTerms(identityServerUrl)) !(await doesIdentityServerHaveTerms(identityServerUrl))
) { ) {
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(QuestionDialog, {
'Default identity server terms warning', '', title: _t("Identity server has no terms of service"),
QuestionDialog, { description: (
title: _t("Identity server has no terms of service"), <div>
description: ( <p>{ _t(
<div> "This action requires accessing the default identity server " +
<p>{ _t( "<server /> to validate an email address or phone number, " +
"This action requires accessing the default identity server " + "but the server does not have any terms of service.", {},
"<server /> to validate an email address or phone number, " + {
"but the server does not have any terms of service.", {}, server: () => <b>{ abbreviateUrl(identityServerUrl) }</b>,
{ },
server: () => <b>{ abbreviateUrl(identityServerUrl) }</b>, ) }</p>
}, <p>{ _t(
) }</p> "Only continue if you trust the owner of the server.",
<p>{ _t( ) }</p>
"Only continue if you trust the owner of the server.", </div>
) }</p> ),
</div> button: _t("Trust"),
), });
button: _t("Trust"),
});
const [confirmed] = await finished; const [confirmed] = await finished;
if (confirmed) { if (confirmed) {
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks

View file

@ -28,7 +28,6 @@ import { IMatrixClientCreds, MatrixClientPeg } from './MatrixClientPeg';
import SecurityCustomisations from "./customisations/Security"; import SecurityCustomisations from "./customisations/Security";
import EventIndexPeg from './indexing/EventIndexPeg'; import EventIndexPeg from './indexing/EventIndexPeg';
import createMatrixClient from './utils/createMatrixClient'; import createMatrixClient from './utils/createMatrixClient';
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';
@ -201,7 +200,7 @@ export function attemptTokenLogin(
const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY); const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY);
if (!homeserver) { if (!homeserver) {
logger.warn("Cannot log in with token: can't determine HS URL to use"); logger.warn("Cannot log in with token: can't determine HS URL to use");
Modal.createTrackedDialog("SSO", "Unknown HS", ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("We couldn't log you in"), title: _t("We couldn't log you in"),
description: _t("We asked the browser to remember which homeserver you use to let you sign in, " + description: _t("We asked the browser to remember which homeserver you use to let you sign in, " +
"but unfortunately your browser has forgotten it. Go to the sign in page and try again."), "but unfortunately your browser has forgotten it. Go to the sign in page and try again."),
@ -226,7 +225,7 @@ export function attemptTokenLogin(
return true; return true;
}); });
}).catch((err) => { }).catch((err) => {
Modal.createTrackedDialog("SSO", "Token Rejected", ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("We couldn't log you in"), title: _t("We couldn't log you in"),
description: err.name === "ConnectionError" description: err.name === "ConnectionError"
? _t("Your homeserver was unreachable and was not able to log you in. Please try again. " + ? _t("Your homeserver was unreachable and was not able to log you in. Please try again. " +
@ -470,7 +469,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
async function handleLoadSessionFailure(e: Error): Promise<boolean> { async function handleLoadSessionFailure(e: Error): Promise<boolean> {
logger.error("Unable to load session", e); logger.error("Unable to load session", e);
const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, { const modal = Modal.createDialog(SessionRestoreErrorDialog, {
error: e, error: e,
}); });
@ -597,8 +596,6 @@ async function doSetLoggedIn(
await abortLogin(); await abortLogin();
} }
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl);
MatrixClientPeg.replaceUsingCreds(credentials); MatrixClientPeg.replaceUsingCreds(credentials);
setSentryUser(credentials.userId); setSentryUser(credentials.userId);
@ -640,7 +637,7 @@ async function doSetLoggedIn(
function showStorageEvictedDialog(): Promise<boolean> { function showStorageEvictedDialog(): Promise<boolean> {
return new Promise(resolve => { return new Promise(resolve => {
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, { Modal.createDialog(StorageEvictedDialog, {
onFinished: resolve, onFinished: resolve,
}); });
}); });
@ -880,8 +877,6 @@ export async function onLoggedOut(): Promise<void> {
* @returns {Promise} promise which resolves once the stores have been cleared * @returns {Promise} promise which resolves once the stores have been cleared
*/ */
async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void> { async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void> {
Analytics.disable();
if (window.localStorage) { if (window.localStorage) {
// try to save any 3pid invites from being obliterated and registration time // try to save any 3pid invites from being obliterated and registration time
const pendingInvites = ThreepidInviteStore.instance.getWireInvites(); const pendingInvites = ThreepidInviteStore.instance.getWireInvites();

View file

@ -193,8 +193,6 @@ class MatrixClientPegClass implements IMatrixClientPeg {
} }
} }
StorageManager.trackStores(this.matrixClient);
// try to initialise e2e on the new client // try to initialise e2e on the new client
try { try {
// check that we have a version of the js-sdk which includes initCrypto // check that we have a version of the js-sdk which includes initCrypto

View file

@ -20,7 +20,6 @@ import ReactDOM from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { defer, sleep } from "matrix-js-sdk/src/utils"; import { defer, sleep } from "matrix-js-sdk/src/utils";
import Analytics from './Analytics';
import dis from './dispatcher/dispatcher'; import dis from './dispatcher/dispatcher';
import AsyncWrapper from './AsyncWrapper'; import AsyncWrapper from './AsyncWrapper';
@ -103,24 +102,6 @@ export class ModalManager {
return this.priorityModal || this.staticModal || this.modals.length > 0; return this.priorityModal || this.staticModal || this.modals.length > 0;
} }
public createTrackedDialog<T extends any[]>(
analyticsAction: string,
analyticsInfo: string,
...rest: Parameters<ModalManager["createDialog"]>
) {
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
return this.createDialog<T>(...rest);
}
public appendTrackedDialog<T extends any[]>(
analyticsAction: string,
analyticsInfo: string,
...rest: Parameters<ModalManager["appendDialog"]>
) {
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
return this.appendDialog<T>(...rest);
}
public createDialog<T extends any[]>( public createDialog<T extends any[]>(
Element: React.ComponentType, Element: React.ComponentType,
...rest: ParametersWithoutFirst<ModalManager["createDialogAsync"]> ...rest: ParametersWithoutFirst<ModalManager["createDialogAsync"]>
@ -135,24 +116,6 @@ export class ModalManager {
return this.appendDialogAsync<T>(Promise.resolve(Element), ...rest); return this.appendDialogAsync<T>(Promise.resolve(Element), ...rest);
} }
public createTrackedDialogAsync<T extends any[]>(
analyticsAction: string,
analyticsInfo: string,
...rest: Parameters<ModalManager["createDialogAsync"]>
) {
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
return this.createDialogAsync<T>(...rest);
}
public appendTrackedDialogAsync<T extends any[]>(
analyticsAction: string,
analyticsInfo: string,
...rest: Parameters<ModalManager["appendDialogAsync"]>
) {
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
return this.appendDialogAsync<T>(...rest);
}
public closeCurrentModal(reason: string) { public closeCurrentModal(reason: string) {
const modal = this.getCurrentModal(); const modal = this.getCurrentModal();
if (!modal) { if (!modal) {
@ -273,7 +236,7 @@ export class ModalManager {
* @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog * @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog
* @returns {object} Object with 'close' parameter being a function that will close the dialog * @returns {object} Object with 'close' parameter being a function that will close the dialog
*/ */
private createDialogAsync<T extends any[]>( public createDialogAsync<T extends any[]>(
prom: Promise<React.ComponentType>, prom: Promise<React.ComponentType>,
props?: IProps<T>, props?: IProps<T>,
className?: string, className?: string,

View file

@ -28,7 +28,6 @@ import { MatrixClientPeg } from './MatrixClientPeg';
import SdkConfig from './SdkConfig'; import SdkConfig from './SdkConfig';
import PlatformPeg from './PlatformPeg'; import PlatformPeg from './PlatformPeg';
import * as TextForEvent from './TextForEvent'; import * as TextForEvent from './TextForEvent';
import Analytics from './Analytics';
import * as Avatar from './Avatar'; import * as Avatar from './Avatar';
import dis from './dispatcher/dispatcher'; import dis from './dispatcher/dispatcher';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
@ -230,8 +229,6 @@ export const Notifier = {
// calculated value. It is determined based upon whether or not the master rule is enabled // calculated value. It is determined based upon whether or not the master rule is enabled
// and other flags. Setting it here would cause a circular reference. // and other flags. Setting it here would cause a circular reference.
Analytics.trackEvent('Notifier', 'Set Enabled', String(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 (SettingsStore.isLevelSupported(SettingLevel.DEVICE)) { if (SettingsStore.isLevelSupported(SettingLevel.DEVICE)) {
@ -249,7 +246,7 @@ export const Notifier = {
? _t('%(brand)s does not have permission to send you notifications - ' + ? _t('%(brand)s does not have permission to send you notifications - ' +
'please check your browser settings', { brand }) 'please check your browser settings', { brand })
: _t('%(brand)s was not given permission to send notifications - please try again', { brand }); : _t('%(brand)s was not given permission to send notifications - please try again', { brand });
Modal.createTrackedDialog('Unable to enable Notifications', result, ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Unable to enable Notifications'), title: _t('Unable to enable Notifications'),
description, description,
}); });
@ -298,8 +295,6 @@ export const Notifier = {
setPromptHidden: function(hidden: boolean, persistent = true) { setPromptHidden: function(hidden: boolean, persistent = true) {
this.toolbarHidden = hidden; this.toolbarHidden = hidden;
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', String(hidden));
hideNotificationsToast(); hideNotificationsToast();
// update the info to localStorage for persistent settings // update the info to localStorage for persistent settings

View file

@ -49,7 +49,7 @@ export async function startAnyRegistrationFlow(
options: { go_home_on_cancel?: boolean, go_welcome_on_cancel?: boolean, screen_after?: boolean}, options: { go_home_on_cancel?: boolean, go_welcome_on_cancel?: boolean, screen_after?: boolean},
): Promise<void> { ): Promise<void> {
if (options === undefined) options = {}; if (options === undefined) options = {};
const modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, { const modal = Modal.createDialog(QuestionDialog, {
hasCancelButton: true, hasCancelButton: true,
quitOnly: true, quitOnly: true,
title: _t("Sign In or Create Account"), title: _t("Sign In or Create Account"),

View file

@ -61,16 +61,16 @@ export function inviteMultipleToRoom(
export function showStartChatInviteDialog(initialText = ""): void { export function showStartChatInviteDialog(initialText = ""): void {
// This dialog handles the room creation internally - we don't need to worry about it. // This dialog handles the room creation internally - we don't need to worry about it.
Modal.createTrackedDialog( Modal.createDialog(
'Start DM', '', InviteDialog, { kind: KIND_DM, initialText }, InviteDialog, { kind: KIND_DM, initialText },
/*className=*/"mx_InviteDialog_flexWrapper", /*isPriority=*/false, /*isStatic=*/true, /*className=*/"mx_InviteDialog_flexWrapper", /*isPriority=*/false, /*isStatic=*/true,
); );
} }
export function showRoomInviteDialog(roomId: string, initialText = ""): void { export function showRoomInviteDialog(roomId: string, initialText = ""): void {
// This dialog handles the room creation internally - we don't need to worry about it. // This dialog handles the room creation internally - we don't need to worry about it.
Modal.createTrackedDialog( Modal.createDialog(
"Invite Users", "", InviteDialog, { InviteDialog, {
kind: KIND_INVITE, kind: KIND_INVITE,
initialText, initialText,
roomId, roomId,
@ -108,7 +108,7 @@ export function inviteUsersToRoom(
showAnyInviteErrors(result.states, room, result.inviter); showAnyInviteErrors(result.states, room, result.inviter);
}).catch((err) => { }).catch((err) => {
logger.error(err.stack); logger.error(err.stack);
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to invite"), title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
@ -127,7 +127,7 @@ export function showAnyInviteErrors(
// Just get the first message because there was a fatal problem on the first // Just get the first message because there was a fatal problem on the first
// user. This usually means that no other users were attempted, making it // user. This usually means that no other users were attempted, making it
// pointless for us to list who failed exactly. // pointless for us to list who failed exactly.
Modal.createTrackedDialog('Failed to invite users to the room', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to invite users to %(roomName)s", { roomName: room.name }), title: _t("Failed to invite users to %(roomName)s", { roomName: room.name }),
description: inviter.getErrorText(failedUsers[0]), description: inviter.getErrorText(failedUsers[0]),
}); });
@ -175,7 +175,7 @@ export function showAnyInviteErrors(
</div> </div>
</div>; </div>;
Modal.createTrackedDialog("Some invites could not be sent", "", ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Some invites couldn't be sent"), title: _t("Some invites couldn't be sent"),
description, description,
}); });

View file

@ -150,7 +150,7 @@ async function getSecretStorageKey(
} }
const inputToKey = makeInputToKey(keyInfo); const inputToKey = makeInputToKey(keyInfo);
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", const { finished } = Modal.createDialog(
AccessSecretStorageDialog, AccessSecretStorageDialog,
/* props= */ /* props= */
{ {
@ -195,7 +195,7 @@ export async function getDehydrationKey(
} }
const inputToKey = makeInputToKey(keyInfo); const inputToKey = makeInputToKey(keyInfo);
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", const { finished } = Modal.createDialog(
AccessSecretStorageDialog, AccessSecretStorageDialog,
/* props= */ /* props= */
{ {
@ -298,7 +298,7 @@ export const crossSigningCallbacks: ICryptoCallbacks = {
export async function promptForBackupPassphrase(): Promise<Uint8Array> { export async function promptForBackupPassphrase(): Promise<Uint8Array> {
let key: Uint8Array; let key: Uint8Array;
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { const { finished } = Modal.createDialog(RestoreKeyBackupDialog, {
showSummary: false, keyCallback: k => key = k, showSummary: false, keyCallback: k => key = k,
}, null, /* priority = */ false, /* static = */ true); }, null, /* priority = */ false, /* static = */ true);
@ -336,7 +336,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
if (!(await cli.hasSecretStorageKey()) || forceReset) { if (!(await cli.hasSecretStorageKey()) || forceReset) {
// This dialog calls bootstrap itself after guiding the user through // This dialog calls bootstrap itself after guiding the user through
// passphrase creation. // passphrase creation.
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', const { finished } = Modal.createDialogAsync(
import( import(
"./async-components/views/dialogs/security/CreateSecretStorageDialog" "./async-components/views/dialogs/security/CreateSecretStorageDialog"
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,
@ -363,14 +363,11 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
} else { } else {
await cli.bootstrapCrossSigning({ await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => { authUploadDeviceSigningKeys: async (makeRequest) => {
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(InteractiveAuthDialog, {
'Cross-signing keys dialog', '', InteractiveAuthDialog, title: _t("Setting up keys"),
{ matrixClient: cli,
title: _t("Setting up keys"), makeRequest,
matrixClient: cli, });
makeRequest,
},
);
const [confirmed] = await finished; const [confirmed] = await finished;
if (!confirmed) { if (!confirmed) {
throw new Error("Cross-signing key upload auth canceled"); throw new Error("Cross-signing key upload auth canceled");

View file

@ -82,7 +82,7 @@ const singleMxcUpload = async (): Promise<any> => {
fileSelector.onchange = (ev: HTMLInputEvent) => { fileSelector.onchange = (ev: HTMLInputEvent) => {
const file = ev.target.files[0]; const file = ev.target.files[0];
Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, { Modal.createDialog(UploadConfirmDialog, {
file, file,
onFinished: (shouldContinue) => { onFinished: (shouldContinue) => {
resolve(shouldContinue ? MatrixClientPeg.get().uploadContent(file) : null); resolve(shouldContinue ? MatrixClientPeg.get().uploadContent(file) : null);
@ -307,7 +307,7 @@ export const Commands = [
); );
} }
const { finished } = Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation', const { finished } = Modal.createDialog(
RoomUpgradeWarningDialog, { roomId: roomId, targetVersion: args }, /*className=*/null, RoomUpgradeWarningDialog, { roomId: roomId, targetVersion: args }, /*className=*/null,
/*isPriority=*/false, /*isStatic=*/true); /*isPriority=*/false, /*isStatic=*/true);
@ -483,7 +483,7 @@ export const Commands = [
const ref = e => e && linkifyElement(e); const ref = e => e && linkifyElement(e);
const body = topicToHtml(topic.text, topic.html, ref, true); const body = topicToHtml(topic.text, topic.html, ref, true);
Modal.createTrackedDialog('Slash Commands', 'Topic', InfoDialog, { Modal.createDialog(InfoDialog, {
title: room.name, title: room.name,
description: <div ref={ref}>{ body }</div>, description: <div ref={ref}>{ body }</div>,
hasCloseButton: true, hasCloseButton: true,
@ -529,22 +529,18 @@ export const Commands = [
) { ) {
const defaultIdentityServerUrl = getDefaultIdentityServerUrl(); const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
if (defaultIdentityServerUrl) { if (defaultIdentityServerUrl) {
const { finished } = Modal.createTrackedDialog<[boolean]>( const { finished } = Modal.createDialog<[boolean]>(QuestionDialog, {
'Slash Commands', title: _t("Use an identity server"),
'Identity server', description: <p>{ _t(
QuestionDialog, { "Use an identity server to invite by email. " +
title: _t("Use an identity server"), "Click continue to use the default identity server " +
description: <p>{ _t( "(%(defaultIdentityServerName)s) or manage in Settings.",
"Use an identity server to invite by email. " + {
"Click continue to use the default identity server " + defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
"(%(defaultIdentityServerName)s) or manage in Settings.", },
{ ) }</p>,
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl), button: _t("Continue"),
}, });
) }</p>,
button: _t("Continue"),
},
);
prom = finished.then(([useDefault]) => { prom = finished.then(([useDefault]) => {
if (useDefault) { if (useDefault) {
@ -810,7 +806,7 @@ export const Commands = [
ignoredUsers.push(userId); // de-duped internally in the js-sdk ignoredUsers.push(userId); // de-duped internally in the js-sdk
return success( return success(
cli.setIgnoredUsers(ignoredUsers).then(() => { cli.setIgnoredUsers(ignoredUsers).then(() => {
Modal.createTrackedDialog('Slash Commands', 'User ignored', InfoDialog, { Modal.createDialog(InfoDialog, {
title: _t('Ignored user'), title: _t('Ignored user'),
description: <div> description: <div>
<p>{ _t('You are now ignoring %(userId)s', { userId }) }</p> <p>{ _t('You are now ignoring %(userId)s', { userId }) }</p>
@ -840,7 +836,7 @@ export const Commands = [
if (index !== -1) ignoredUsers.splice(index, 1); if (index !== -1) ignoredUsers.splice(index, 1);
return success( return success(
cli.setIgnoredUsers(ignoredUsers).then(() => { cli.setIgnoredUsers(ignoredUsers).then(() => {
Modal.createTrackedDialog('Slash Commands', 'User unignored', InfoDialog, { Modal.createDialog(InfoDialog, {
title: _t('Unignored user'), title: _t('Unignored user'),
description: <div> description: <div>
<p>{ _t('You are no longer ignoring %(userId)s', { userId }) }</p> <p>{ _t('You are no longer ignoring %(userId)s', { userId }) }</p>
@ -1040,7 +1036,7 @@ export const Commands = [
await cli.setDeviceVerified(userId, deviceId, true); await cli.setDeviceVerified(userId, deviceId, true);
// Tell the user we verified everything // Tell the user we verified everything
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, { Modal.createDialog(InfoDialog, {
title: _t('Verified key'), title: _t('Verified key'),
description: <div> description: <div>
<p> <p>
@ -1098,7 +1094,7 @@ export const Commands = [
command: "help", command: "help",
description: _td("Displays list of commands with usages and descriptions"), description: _td("Displays list of commands with usages and descriptions"),
runFn: function() { runFn: function() {
Modal.createTrackedDialog('Slash Commands', 'Help', SlashCommandHelpDialog); Modal.createDialog(SlashCommandHelpDialog);
return success(); return success();
}, },
category: CommandCategories.advanced, category: CommandCategories.advanced,
@ -1130,7 +1126,7 @@ export const Commands = [
args: "<description>", args: "<description>",
runFn: function(roomId, args) { runFn: function(roomId, args) {
return success( return success(
Modal.createTrackedDialog('Slash Commands', 'Bug Report Dialog', BugReportDialog, { Modal.createDialog(BugReportDialog, {
initialText: args, initialText: args,
}).finished, }).finished,
); );

View file

@ -190,7 +190,7 @@ export async function dialogTermsInteractionCallback(
): Promise<string[]> { ): Promise<string[]> {
logger.log("Terms that need agreement", policiesAndServicePairs); logger.log("Terms that need agreement", policiesAndServicePairs);
const { finished } = Modal.createTrackedDialog<[boolean, string[]]>('Terms of Service', '', TermsDialog, { const { finished } = Modal.createDialog<[boolean, string[]]>(TermsDialog, {
policiesAndServicePairs, policiesAndServicePairs,
agreedUrls, agreedUrls,
}, classNames("mx_TermsDialog", extraClassNames)); }, classNames("mx_TermsDialog", extraClassNames));

View file

@ -91,7 +91,7 @@ export default class RoomListActions {
room, newTag === DefaultTagID.DM, room, newTag === DefaultTagID.DM,
).catch((err) => { ).catch((err) => {
logger.error("Failed to set DM tag " + err); logger.error("Failed to set DM tag " + err);
Modal.createTrackedDialog('Failed to set direct message tag', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to set direct message tag'), title: _t('Failed to set direct message tag'),
description: ((err && err.message) ? err.message : _t('Operation failed')), description: ((err && err.message) ? err.message : _t('Operation failed')),
}); });
@ -111,7 +111,7 @@ export default class RoomListActions {
roomId, oldTag, roomId, oldTag,
).catch(function(err) { ).catch(function(err) {
logger.error("Failed to remove tag " + oldTag + " from room: " + err); logger.error("Failed to remove tag " + oldTag + " from room: " + err);
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to remove tag %(tagName)s from room', { tagName: oldTag }), title: _t('Failed to remove tag %(tagName)s from room', { tagName: oldTag }),
description: ((err && err.message) ? err.message : _t('Operation failed')), description: ((err && err.message) ? err.message : _t('Operation failed')),
}); });
@ -130,7 +130,7 @@ export default class RoomListActions {
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function(err) { const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function(err) {
logger.error("Failed to add tag " + newTag + " to room: " + err); logger.error("Failed to add tag " + newTag + " to room: " + err);
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to add tag %(tagName)s to room', { tagName: newTag }), title: _t('Failed to add tag %(tagName)s to room', { tagName: newTag }),
description: ((err && err.message) ? err.message : _t('Operation failed')), description: ((err && err.message) ? err.message : _t('Operation failed')),
}); });

View file

@ -134,10 +134,7 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
private onDisable = async () => { private onDisable = async () => {
const DisableEventIndexDialog = (await import("./DisableEventIndexDialog")).default; const DisableEventIndexDialog = (await import("./DisableEventIndexDialog")).default;
Modal.createTrackedDialog("Disable message search", "Disable message search", Modal.createDialog(DisableEventIndexDialog, null, null, /* priority = */ false, /* static = */ true);
DisableEventIndexDialog,
null, null, /* priority = */ false, /* static = */ true,
);
}; };
private onCrawlerSleepTimeChange = (e) => { private onCrawlerSleepTimeChange = (e) => {

View file

@ -303,18 +303,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
}, },
}; };
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(InteractiveAuthDialog, {
'Cross-signing keys dialog', '', InteractiveAuthDialog, title: _t("Setting up keys"),
{ matrixClient: MatrixClientPeg.get(),
title: _t("Setting up keys"), makeRequest,
matrixClient: MatrixClientPeg.get(), aestheticsForStagePhases: {
makeRequest, [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
aestheticsForStagePhases: { [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
},
}, },
); });
const [confirmed] = await finished; const [confirmed] = await finished;
if (!confirmed) { if (!confirmed) {
throw new Error("Cross-signing key upload auth canceled"); throw new Error("Cross-signing key upload auth canceled");
@ -390,14 +387,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
// so let's stash it here, rather than prompting for it twice. // so let's stash it here, rather than prompting for it twice.
const keyCallback = k => this.backupKey = k; const keyCallback = k => this.backupKey = k;
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(RestoreKeyBackupDialog, {
'Restore Backup', '', RestoreKeyBackupDialog, showSummary: false,
{ keyCallback,
showSummary: false, }, null, /* priority = */ false, /* static = */ false);
keyCallback,
},
null, /* priority = */ false, /* static = */ false,
);
await finished; await finished;
const { backupSigStatus } = await this.fetchBackupInfo(); const { backupSigStatus } = await this.fetchBackupInfo();

View file

@ -43,11 +43,9 @@ export default class NewRecoveryMethodDialog extends React.PureComponent<IProps>
}; };
private onSetupClick = async (): Promise<void> => { private onSetupClick = async (): Promise<void> => {
Modal.createTrackedDialog( Modal.createDialog(RestoreKeyBackupDialog, {
'Restore Backup', '', RestoreKeyBackupDialog, { onFinished: this.props.onFinished,
onFinished: this.props.onFinished, }, null, /* priority = */ false, /* static = */ true);
}, null, /* priority = */ false, /* static = */ true,
);
}; };
public render(): JSX.Element { public render(): JSX.Element {

View file

@ -35,7 +35,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent<IPr
private onSetupClick = (): void => { private onSetupClick = (): void => {
this.props.onFinished(); this.props.onFinished();
Modal.createTrackedDialogAsync("Key Backup", "Key Backup", Modal.createDialogAsync(
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>, import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
null, null, /* priority = */ false, /* static = */ true, null, null, /* priority = */ false, /* static = */ true,
); );

View file

@ -30,24 +30,20 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore";
import { useEventEmitter } from "../../hooks/useEventEmitter"; import { useEventEmitter } from "../../hooks/useEventEmitter";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUploader"; import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUploader";
import Analytics from "../../Analytics";
import PosthogTrackers from "../../PosthogTrackers"; import PosthogTrackers from "../../PosthogTrackers";
import EmbeddedPage from "./EmbeddedPage"; import EmbeddedPage from "./EmbeddedPage";
const onClickSendDm = (ev: ButtonEvent) => { const onClickSendDm = (ev: ButtonEvent) => {
Analytics.trackEvent('home_page', 'button', 'dm');
PosthogTrackers.trackInteraction("WebHomeCreateChatButton", ev); PosthogTrackers.trackInteraction("WebHomeCreateChatButton", ev);
dis.dispatch({ action: 'view_create_chat' }); dis.dispatch({ action: 'view_create_chat' });
}; };
const onClickExplore = (ev: ButtonEvent) => { const onClickExplore = (ev: ButtonEvent) => {
Analytics.trackEvent('home_page', 'button', 'room_directory');
PosthogTrackers.trackInteraction("WebHomeExploreRoomsButton", ev); PosthogTrackers.trackInteraction("WebHomeExploreRoomsButton", ev);
dis.fire(Action.ViewRoomDirectory); dis.fire(Action.ViewRoomDirectory);
}; };
const onClickNewRoom = (ev: ButtonEvent) => { const onClickNewRoom = (ev: ButtonEvent) => {
Analytics.trackEvent('home_page', 'button', 'create_room');
PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev); PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev);
dis.dispatch({ action: 'view_create_room' }); dis.dispatch({ action: 'view_create_room' });
}; };

View file

@ -39,7 +39,6 @@ import 'focus-visible';
import 'what-input'; import 'what-input';
import PosthogTrackers from '../../PosthogTrackers'; import PosthogTrackers from '../../PosthogTrackers';
import Analytics from "../../Analytics";
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker"; import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg"; import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
import PlatformPeg from "../../PlatformPeg"; import PlatformPeg from "../../PlatformPeg";
@ -73,8 +72,7 @@ import LoggedInView from './LoggedInView';
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { import {
hideToast as hideAnalyticsToast, hideToast as hideAnalyticsToast,
showAnonymousAnalyticsOptInToast, showToast as showAnalyticsToast,
showPseudonymousAnalyticsOptInToast,
} from "../../toasts/AnalyticsToast"; } from "../../toasts/AnalyticsToast";
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast"; import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
@ -344,10 +342,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}); });
} }
if (SettingsStore.getValue("pseudonymousAnalyticsOptIn")) {
Analytics.enable();
}
initSentry(SdkConfig.get("sentry")); initSentry(SdkConfig.get("sentry"));
} }
@ -406,7 +400,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (this.shouldTrackPageChange(prevState, this.state)) { if (this.shouldTrackPageChange(prevState, this.state)) {
const durationMs = this.stopPageChangeTimer(); const durationMs = this.stopPageChangeTimer();
Analytics.trackPageChange(durationMs);
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs); PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
} }
if (this.focusComposer) { if (this.focusComposer) {
@ -625,7 +618,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.copyRoom(payload.room_id); this.copyRoom(payload.room_id);
break; break;
case 'reject_invite': case 'reject_invite':
Modal.createTrackedDialog('Reject invitation', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t('Reject invitation'), title: _t('Reject invitation'),
description: _t('Are you sure you want to reject the invitation?'), description: _t('Are you sure you want to reject the invitation?'),
onFinished: (confirm) => { onFinished: (confirm) => {
@ -640,7 +633,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
}, (err) => { }, (err) => {
modal.close(); modal.close();
Modal.createTrackedDialog('Failed to reject invitation', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to reject invitation'), title: _t('Failed to reject invitation'),
description: err.toString(), description: err.toString(),
}); });
@ -684,7 +677,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
break; break;
case Action.ViewUserSettings: { case Action.ViewUserSettings: {
const tabPayload = payload as OpenToTabPayload; const tabPayload = payload as OpenToTabPayload;
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, Modal.createDialog(UserSettingsDialog,
{ initialTabId: tabPayload.initialTabId }, { initialTabId: tabPayload.initialTabId },
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true); /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
@ -699,7 +692,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.viewSomethingBehindModal(); this.viewSomethingBehindModal();
break; break;
case Action.ViewRoomDirectory: { case Action.ViewRoomDirectory: {
Modal.createTrackedDialog('Room directory', '', RoomDirectory, { Modal.createDialog(RoomDirectory, {
initialText: payload.initialText, initialText: payload.initialText,
}, 'mx_RoomDirectory_dialogWrapper', false, true); }, 'mx_RoomDirectory_dialogWrapper', false, true);
@ -756,7 +749,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}); });
break; break;
case Action.OpenDialPad: case Action.OpenDialPad:
Modal.createTrackedDialog('Dial pad', '', DialPadModal, {}, "mx_Dialog_dialPadWrapper"); Modal.createDialog(DialPadModal, {}, "mx_Dialog_dialPadWrapper");
break; break;
case Action.OnLoggedIn: case Action.OnLoggedIn:
if ( if (
@ -801,19 +794,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
hideToSRUsers: false, hideToSRUsers: false,
}); });
break; break;
case Action.AnonymousAnalyticsAccept:
hideAnalyticsToast();
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, true);
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
if (Analytics.canEnable()) {
Analytics.enable();
}
break;
case Action.AnonymousAnalyticsReject:
hideAnalyticsToast();
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, false);
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
break;
case Action.PseudonymousAnalyticsAccept: case Action.PseudonymousAnalyticsAccept:
hideAnalyticsToast(); hideAnalyticsToast();
SettingsStore.setValue("pseudonymousAnalyticsOptIn", null, SettingLevel.ACCOUNT, true); SettingsStore.setValue("pseudonymousAnalyticsOptIn", null, SettingLevel.ACCOUNT, true);
@ -1009,7 +989,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
private async createRoom(defaultPublic = false, defaultName?: string, type?: RoomType) { private async createRoom(defaultPublic = false, defaultName?: string, type?: RoomType) {
const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, { const modal = Modal.createDialog(CreateRoomDialog, {
type, type,
defaultPublic, defaultPublic,
defaultName, defaultName,
@ -1112,7 +1092,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const warnings = this.leaveRoomWarnings(roomId); const warnings = this.leaveRoomWarnings(roomId);
const isSpace = roomToLeave?.isSpaceRoom(); const isSpace = roomToLeave?.isSpaceRoom();
Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: isSpace ? _t("Leave space") : _t("Leave room"), title: isSpace ? _t("Leave space") : _t("Leave room"),
description: ( description: (
<span> <span>
@ -1156,7 +1136,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved); RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
}).catch((err) => { }).catch((err) => {
const errCode = err.errcode || _td("unknown error code"); const errCode = err.errcode || _td("unknown error code");
Modal.createTrackedDialog("Failed to forget room", '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to forget room %(errCode)s", { errCode }), title: _t("Failed to forget room %(errCode)s", { errCode }),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
@ -1167,7 +1147,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const roomLink = makeRoomPermalink(roomId); const roomLink = makeRoomPermalink(roomId);
const success = await copyPlaintext(roomLink); const success = await copyPlaintext(roomLink);
if (!success) { if (!success) {
Modal.createTrackedDialog("Unable to copy room link", "", ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to copy room link"), title: _t("Unable to copy room link"),
description: _t("Unable to copy a link to the room to the clipboard."), description: _t("Unable to copy a link to the room to the clipboard."),
}); });
@ -1271,8 +1251,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (PosthogAnalytics.instance.isEnabled() && SettingsStore.isLevelSupported(SettingLevel.ACCOUNT)) { if (PosthogAnalytics.instance.isEnabled() && SettingsStore.isLevelSupported(SettingLevel.ACCOUNT)) {
this.initPosthogAnalyticsToast(); this.initPosthogAnalyticsToast();
} else if (Analytics.canEnable() && SettingsStore.getValue("showCookieBar")) {
showAnonymousAnalyticsOptInToast();
} }
if (SdkConfig.get("mobile_guide_toast")) { if (SdkConfig.get("mobile_guide_toast")) {
@ -1282,14 +1260,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
} }
private showPosthogToast(analyticsOptIn: boolean) {
showPseudonymousAnalyticsOptInToast(analyticsOptIn);
}
private initPosthogAnalyticsToast() { private initPosthogAnalyticsToast() {
// Show the analytics toast if necessary // Show the analytics toast if necessary
if (SettingsStore.getValue("pseudonymousAnalyticsOptIn") === null) { if (SettingsStore.getValue("pseudonymousAnalyticsOptIn") === null) {
this.showPosthogToast(SettingsStore.getValue("analyticsOptIn", null, true)); showAnalyticsToast();
} }
// Listen to changes in settings and show the toast if appropriate - this is necessary because account // Listen to changes in settings and show the toast if appropriate - this is necessary because account
@ -1298,7 +1272,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
SettingsStore.watchSetting("pseudonymousAnalyticsOptIn", null, SettingsStore.watchSetting("pseudonymousAnalyticsOptIn", null,
(originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue) => { (originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue) => {
if (newValue === null) { if (newValue === null) {
this.showPosthogToast(SettingsStore.getValue("analyticsOptIn", null, true)); showAnalyticsToast();
} else { } else {
// It's possible for the value to change if a cached sync loads at page load, but then network // It's possible for the value to change if a cached sync loads at page load, but then network
// sync contains a new value of the flag with it set to false (e.g. another device set it since last // sync contains a new value of the flag with it set to false (e.g. another device set it since last
@ -1478,7 +1452,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
return; return;
} }
Modal.createTrackedDialog('Signed out', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Signed Out'), title: _t('Signed Out'),
description: _t('For security, this session has been signed out. Please sign in again.'), description: _t('For security, this session has been signed out. Please sign in again.'),
}); });
@ -1488,7 +1462,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}); });
}); });
cli.on(HttpApiEvent.NoConsent, function(message, consentUri) { cli.on(HttpApiEvent.NoConsent, function(message, consentUri) {
Modal.createTrackedDialog('No Consent Dialog', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t('Terms and Conditions'), title: _t('Terms and Conditions'),
description: <div> description: <div>
<p> { _t( <p> { _t(
@ -1535,7 +1509,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
cli.on(CryptoEvent.Warning, (type) => { cli.on(CryptoEvent.Warning, (type) => {
switch (type) { switch (type) {
case 'CRYPTO_WARNING_OLD_VERSION_DETECTED': case 'CRYPTO_WARNING_OLD_VERSION_DETECTED':
Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Old cryptography data detected'), title: _t('Old cryptography data detected'),
description: _t( description: _t(
"Data from an older version of %(brand)s has been detected. " + "Data from an older version of %(brand)s has been detected. " +
@ -1569,14 +1543,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
if (haveNewVersion) { if (haveNewVersion) {
Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method', Modal.createDialogAsync(
import( import(
'../../async-components/views/dialogs/security/NewRecoveryMethodDialog' '../../async-components/views/dialogs/security/NewRecoveryMethodDialog'
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,
{ newVersionInfo }, { newVersionInfo },
); );
} else { } else {
Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed', Modal.createDialogAsync(
import( import(
'../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog' '../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,
@ -1585,16 +1559,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}); });
cli.on(CryptoEvent.KeySignatureUploadFailure, (failures, source, continuation) => { cli.on(CryptoEvent.KeySignatureUploadFailure, (failures, source, continuation) => {
Modal.createTrackedDialog( Modal.createDialog(
'Failed to upload key signatures',
'Failed to upload key signatures',
KeySignatureUploadFailedDialog, KeySignatureUploadFailedDialog,
{ failures, source, continuation }); { failures, source, continuation });
}); });
cli.on(CryptoEvent.VerificationRequest, request => { cli.on(CryptoEvent.VerificationRequest, request => {
if (request.verifier) { if (request.verifier) {
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { Modal.createDialog(IncomingSasDialog, {
verifier: request.verifier, verifier: request.verifier,
}, null, /* priority = */ false, /* static = */ true); }, null, /* priority = */ false, /* static = */ true);
} else if (request.pending) { } else if (request.pending) {

View file

@ -27,7 +27,6 @@ import Modal from "../../Modal";
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import SdkConfig from '../../SdkConfig'; import SdkConfig from '../../SdkConfig';
import { instanceForInstanceId, protocolNameForInstanceId, ALL_ROOMS, Protocols } from '../../utils/DirectoryUtils'; import { instanceForInstanceId, protocolNameForInstanceId, ALL_ROOMS, Protocols } from '../../utils/DirectoryUtils';
import Analytics from '../../Analytics';
import NetworkDropdown from "../views/directory/NetworkDropdown"; import NetworkDropdown from "../views/directory/NetworkDropdown";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { IDialogProps } from "../views/dialogs/IDialogProps"; import { IDialogProps } from "../views/dialogs/IDialogProps";
@ -47,10 +46,6 @@ import { GenericError } from "../../utils/error";
const LAST_SERVER_KEY = "mx_last_room_directory_server"; const LAST_SERVER_KEY = "mx_last_room_directory_server";
const LAST_INSTANCE_KEY = "mx_last_room_directory_instance"; const LAST_INSTANCE_KEY = "mx_last_room_directory_instance";
function track(action: string) {
Analytics.trackEvent('RoomDirectory', action);
}
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
initialText?: string; initialText?: string;
} }
@ -121,7 +116,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
// thing you see when loading the client! // thing you see when loading the client!
return; return;
} }
track('Failed to get protocol list from homeserver');
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
this.setState({ this.setState({
error: _t( error: _t(
@ -225,7 +219,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
} }
logger.error("Failed to get publicRooms: %s", JSON.stringify(err)); logger.error("Failed to get publicRooms: %s", JSON.stringify(err));
track('Failed to get public room list');
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
this.setState({ this.setState({
loading: false, loading: false,
@ -255,7 +248,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
desc = _t('Remove %(name)s from the directory?', { name: name }); desc = _t('Remove %(name)s from the directory?', { name: name });
} }
Modal.createTrackedDialog('Remove from Directory', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t('Remove from Directory'), title: _t('Remove from Directory'),
description: desc, description: desc,
onFinished: (shouldDelete: boolean) => { onFinished: (shouldDelete: boolean) => {
@ -275,7 +268,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
modal.close(); modal.close();
this.refreshRoomList(); this.refreshRoomList();
logger.error("Failed to " + step + ": " + err); logger.error("Failed to " + step + ": " + err);
Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Error'), title: _t('Error'),
description: (err && err.message) description: (err && err.message)
? err.message ? err.message
@ -360,7 +353,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
}); });
} catch (e) { } catch (e) {
if (e instanceof GenericError) { if (e instanceof GenericError) {
Modal.createTrackedDialog(e.message, '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: e.message, title: e.message,
description: e.description, description: e.description,
}); });

View file

@ -104,7 +104,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
}; };
private openSpotlight() { private openSpotlight() {
Modal.createTrackedDialog("Spotlight", "", SpotlightDialog, {}, "mx_SpotlightDialog_wrapper", false, true); Modal.createDialog(SpotlightDialog, {}, "mx_SpotlightDialog_wrapper", false, true);
} }
private onAction = (payload: ActionPayload) => { private onAction = (payload: ActionPayload) => {

View file

@ -1397,7 +1397,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}); });
}, (error) => { }, (error) => {
logger.error("Search failed", error); logger.error("Search failed", error);
Modal.createTrackedDialog('Search failed', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Search failed"), title: _t("Search failed"),
description: ((error && error.message) ? error.message : description: ((error && error.message) ? error.message :
_t("Server may be unavailable, overloaded, or search timed out :(")), _t("Server may be unavailable, overloaded, or search timed out :(")),
@ -1522,7 +1522,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
logger.error("Failed to reject invite: %s", error); logger.error("Failed to reject invite: %s", error);
const msg = error.message ? error.message : JSON.stringify(error); const msg = error.message ? error.message : JSON.stringify(error);
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to reject invite"), title: _t("Failed to reject invite"),
description: msg, description: msg,
}); });
@ -1555,7 +1555,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
logger.error("Failed to reject invite: %s", error); logger.error("Failed to reject invite: %s", error);
const msg = error.message ? error.message : JSON.stringify(error); const msg = error.message ? error.message : JSON.stringify(error);
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to reject invite"), title: _t("Failed to reject invite"),
description: msg, description: msg,
}); });

View file

@ -234,7 +234,7 @@ const ThreadPanel: React.FC<IProps> = ({
}, [timelineSet, timelinePanel]); }, [timelineSet, timelinePanel]);
const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => { const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => {
Modal.createTrackedDialog("Threads Feedback", "feature_thread", BetaFeedbackDialog, { Modal.createDialog(BetaFeedbackDialog, {
featureId: "feature_thread", featureId: "feature_thread",
}); });
} : null; } : null;

View file

@ -1353,7 +1353,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
); );
} }
Modal.createTrackedDialog('Failed to load timeline position', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to load timeline position"), title: _t("Failed to load timeline position"),
description, description,
onFinished, onFinished,

View file

@ -229,7 +229,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
Modal.createTrackedDialog('Feedback Dialog', '', FeedbackDialog); Modal.createDialog(FeedbackDialog);
this.setState({ contextMenuPosition: null }); // also close the menu this.setState({ contextMenuPosition: null }); // also close the menu
}; };
@ -242,7 +242,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
// log out without user prompt if they have no local megolm sessions // log out without user prompt if they have no local megolm sessions
defaultDispatcher.dispatch({ action: 'logout' }); defaultDispatcher.dispatch({ action: 'logout' });
} else { } else {
Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog); Modal.createDialog(LogoutDialog);
} }
this.setState({ contextMenuPosition: null }); // also close the menu this.setState({ contextMenuPosition: null }); // also close the menu

View file

@ -70,7 +70,7 @@ export default class UserView extends React.Component<IProps, IState> {
try { try {
profileInfo = await cli.getProfileInfo(this.props.userId); profileInfo = await cli.getProfileInfo(this.props.userId);
} catch (err) { } catch (err) {
Modal.createTrackedDialog(_t('Could not load user profile'), '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Could not load user profile'), title: _t('Could not load user profile'),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });

View file

@ -199,7 +199,7 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
} }
if (this.state.logoutDevices) { if (this.state.logoutDevices) {
const { finished } = Modal.createTrackedDialog<[boolean]>('Forgot Password Warning', '', QuestionDialog, { const { finished } = Modal.createDialog<[boolean]>(QuestionDialog, {
title: _t('Warning!'), title: _t('Warning!'),
description: description:
<div> <div>
@ -271,7 +271,7 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
}; };
public showErrorDialog(description: string, title?: string) { public showErrorDialog(description: string, title?: string) {
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title, title,
description, description,
}); });

View file

@ -98,7 +98,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
// We need to call onFinished now to close this dialog, and // We need to call onFinished now to close this dialog, and
// again later to signal that the verification is complete. // again later to signal that the verification is complete.
this.props.onFinished(); this.props.onFinished();
Modal.createTrackedDialog('New Session Verification', 'Starting dialog', VerificationRequestDialog, { Modal.createDialog(VerificationRequestDialog, {
verificationRequestPromise: requestPromise, verificationRequestPromise: requestPromise,
member: cli.getUser(userId), member: cli.getUser(userId),
onFinished: async () => { onFinished: async () => {

View file

@ -101,7 +101,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
} }
private onClearAll = () => { private onClearAll = () => {
Modal.createTrackedDialog('Clear Data', 'Soft Logout', ConfirmWipeDeviceDialog, { Modal.createDialog(ConfirmWipeDeviceDialog, {
onFinished: (wipeData) => { onFinished: (wipeData) => {
if (!wipeData) return; if (!wipeData) return;

View file

@ -126,7 +126,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
if (this.state.email === '') { if (this.state.email === '') {
if (this.showEmail()) { if (this.showEmail()) {
Modal.createTrackedDialog("Email prompt dialog", '', RegistrationEmailPromptDialog, { Modal.createDialog(RegistrationEmailPromptDialog, {
onFinished: async (confirmed: boolean, email?: string) => { onFinished: async (confirmed: boolean, email?: string) => {
if (confirmed) { if (confirmed) {
this.setState({ this.setState({

View file

@ -91,7 +91,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
if (value && feedbackLabel && feedbackSubheading && SdkConfig.get().bug_report_endpoint_url) { if (value && feedbackLabel && feedbackSubheading && SdkConfig.get().bug_report_endpoint_url) {
feedbackButton = <AccessibleButton feedbackButton = <AccessibleButton
onClick={() => { onClick={() => {
Modal.createTrackedDialog("Beta Feedback", featureId, BetaFeedbackDialog, { featureId }); Modal.createDialog(BetaFeedbackDialog, { featureId });
}} }}
kind="primary" kind="primary"
> >

View file

@ -173,7 +173,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
}; };
private onViewSourceClick = (): void => { private onViewSourceClick = (): void => {
Modal.createTrackedDialog('View Event Source', '', ViewSource, { Modal.createDialog(ViewSource, {
mxEvent: this.props.mxEvent, mxEvent: this.props.mxEvent,
}, 'mx_Dialog_viewsource'); }, 'mx_Dialog_viewsource');
this.closeMenu(); this.closeMenu();
@ -238,7 +238,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
private onShareClick = (e: React.MouseEvent): void => { private onShareClick = (e: React.MouseEvent): void => {
e.preventDefault(); e.preventDefault();
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, { Modal.createDialog(ShareDialog, {
target: this.props.mxEvent, target: this.props.mxEvent,
permalinkCreator: this.props.permalinkCreator, permalinkCreator: this.props.permalinkCreator,
}); });
@ -286,7 +286,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
private onEndPollClick = (): void => { private onEndPollClick = (): void => {
const matrixClient = MatrixClientPeg.get(); const matrixClient = MatrixClientPeg.get();
Modal.createTrackedDialog('End Poll', '', EndPollDialog, { Modal.createDialog(EndPollDialog, {
matrixClient, matrixClient,
event: this.props.mxEvent, event: this.props.mxEvent,
getRelationsForEvent: this.props.getRelationsForEvent, getRelationsForEvent: this.props.getRelationsForEvent,

View file

@ -296,7 +296,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
Modal.createTrackedDialog('Export room dialog', '', ExportDialog, { room }); Modal.createDialog(ExportDialog, { room });
onFinished(); onFinished();
}} }}
label={_t("Export chat")} label={_t("Export chat")}

View file

@ -69,7 +69,7 @@ const WidgetContextMenu: React.FC<IProps> = ({
logger.error("Failed to start livestream", err); logger.error("Failed to start livestream", err);
// XXX: won't i18n well, but looks like widget api only support 'message'? // XXX: won't i18n well, but looks like widget api only support 'message'?
const message = err.message || _t("Unable to start audio streaming."); const message = err.message || _t("Unable to start audio streaming.");
Modal.createTrackedDialog('WidgetContext Menu', 'Livestream failed', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to start livestream'), title: _t('Failed to start livestream'),
description: message, description: message,
}); });
@ -134,7 +134,7 @@ const WidgetContextMenu: React.FC<IProps> = ({
onDeleteClick(); onDeleteClick();
} else { } else {
// Show delete confirmation dialog // Show delete confirmation dialog
Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t("Delete Widget"), title: _t("Delete Widget"),
description: _t( description: _t(
"Deleting a widget removes it for all users in this room." + "Deleting a widget removes it for all users in this room." +

View file

@ -15,14 +15,13 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { Optional } from "matrix-events-sdk";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import DialogButtons from "../elements/DialogButtons"; import DialogButtons from "../elements/DialogButtons";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import { SnakedObject } from "../../../utils/SnakedObject"; import { getPolicyUrl } from "../../../toasts/AnalyticsToast";
export enum ButtonClicked { export enum ButtonClicked {
Primary, Primary,
@ -98,19 +97,13 @@ const AnalyticsLearnMoreDialog: React.FC<IProps> = ({
}; };
export const showDialog = (props: Omit<IProps, "cookiePolicyUrl" | "analyticsOwner">): void => { export const showDialog = (props: Omit<IProps, "cookiePolicyUrl" | "analyticsOwner">): void => {
const piwikConfig = SdkConfig.get("piwik"); const privacyPolicyUrl = getPolicyUrl();
let privacyPolicyUrl: Optional<string>;
if (piwikConfig && typeof piwikConfig === "object") {
privacyPolicyUrl = (new SnakedObject(piwikConfig)).get("policy_url");
}
const analyticsOwner = SdkConfig.get("analytics_owner") ?? SdkConfig.get("brand"); const analyticsOwner = SdkConfig.get("analytics_owner") ?? SdkConfig.get("brand");
Modal.createTrackedDialog( Modal.createDialog(AnalyticsLearnMoreDialog, {
"Analytics Learn More", privacyPolicyUrl,
"", analyticsOwner,
AnalyticsLearnMoreDialog, ...props,
{ privacyPolicyUrl, analyticsOwner, ...props }, }, "mx_AnalyticsLearnMoreDialog_wrapper");
"mx_AnalyticsLearnMoreDialog_wrapper",
);
}; };
export default AnalyticsLearnMoreDialog; export default AnalyticsLearnMoreDialog;

View file

@ -110,8 +110,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
}).then(() => { }).then(() => {
if (!this.unmounted) { if (!this.unmounted) {
this.props.onFinished(false); this.props.onFinished(false);
// N.B. first param is passed to piwik and so doesn't want i18n Modal.createDialog(QuestionDialog, {
Modal.createTrackedDialog('Bug report sent', '', QuestionDialog, {
title: _t('Logs sent'), title: _t('Logs sent'),
description: _t('Thank you!'), description: _t('Thank you!'),
hasCancelButton: false, hasCancelButton: false,

View file

@ -53,7 +53,7 @@ export function createRedactEventDialog({
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
onCloseDialog?: () => void; onCloseDialog?: () => void;
}) { }) {
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, { Modal.createDialog(ConfirmRedactDialog, {
onFinished: async (proceed: boolean, reason?: string) => { onFinished: async (proceed: boolean, reason?: string) => {
if (!proceed) return; if (!proceed) return;
@ -73,7 +73,7 @@ export function createRedactEventDialog({
// detached queue and we show the room status bar to allow retry // detached queue and we show the room status bar to allow retry
if (typeof code !== "undefined") { if (typeof code !== "undefined") {
// display error message stating you couldn't delete this. // display error message stating you couldn't delete this.
Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Error'), title: _t('Error'),
description: _t('You cannot delete this message. (%(code)s)', { code }), description: _t('You cannot delete this message. (%(code)s)', { code }),
}); });

View file

@ -32,7 +32,7 @@ const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const _onLogoutClicked = () => { const _onLogoutClicked = () => {
Modal.createTrackedDialog('Logout e2e db too new', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t("Sign out"), title: _t("Sign out"),
description: _t( description: _t(
"To avoid losing your chat history, you must export your room keys " + "To avoid losing your chat history, you must export your room keys " +

View file

@ -19,7 +19,6 @@ import React from 'react';
import { AuthType, IAuthData } from 'matrix-js-sdk/src/interactive-auth'; import { AuthType, IAuthData } from 'matrix-js-sdk/src/interactive-auth';
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import Analytics from '../../../Analytics';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth"; import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
@ -122,7 +121,6 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
// this isn't done. // this isn't done.
MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => { MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => {
// Deactivation worked - logout & close this dialog // Deactivation worked - logout & close this dialog
Analytics.trackEvent('Account', 'Deactivate Account');
defaultDispatcher.fire(Action.TriggerLogout); defaultDispatcher.fire(Action.TriggerLogout);
this.props.onFinished(true); this.props.onFinished(true);
}).catch(e => { }).catch(e => {

View file

@ -62,16 +62,11 @@ export default class EndPollDialog extends React.Component<IProps> {
this.props.event.getRoomId(), endEvent.type, endEvent.content, this.props.event.getRoomId(), endEvent.type, endEvent.content,
).catch((e: any) => { ).catch((e: any) => {
console.error("Failed to submit poll response event:", e); console.error("Failed to submit poll response event:", e);
Modal.createTrackedDialog( Modal.createDialog(ErrorDialog, {
'Failed to end poll', title: _t("Failed to end poll"),
'', description: _t(
ErrorDialog, "Sorry, the poll did not end. Please try again."),
{ });
title: _t("Failed to end poll"),
description: _t(
"Sorry, the poll did not end. Please try again."),
},
);
}); });
} }
this.props.onFinished(endPoll); this.props.onFinished(endPoll);

View file

@ -16,7 +16,7 @@ limitations under the License.
/* /*
* Usage: * Usage:
* Modal.createTrackedDialog('An Identifier', 'some detail', ErrorDialog, { * Modal.createDialog(ErrorDialog, {
* title: "some text", (default: "Error") * title: "some text", (default: "Error")
* description: "some more text", * description: "some more text",
* button: "Button Text", * button: "Button Text",

View file

@ -47,7 +47,7 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
const onDebugLogsLinkClick = (): void => { const onDebugLogsLinkClick = (): void => {
props.onFinished(); props.onFinished();
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {}); Modal.createDialog(BugReportDialog, {});
}; };
const rageshakeUrl = SdkConfig.get().bug_report_endpoint_url; const rageshakeUrl = SdkConfig.get().bug_report_endpoint_url;
@ -58,7 +58,7 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
submitFeedback(rageshakeUrl, "feedback", comment, canContact); submitFeedback(rageshakeUrl, "feedback", comment, canContact);
} }
Modal.createTrackedDialog('Feedback sent', '', InfoDialog, { Modal.createDialog(InfoDialog, {
title: _t('Feedback sent'), title: _t('Feedback sent'),
description: _t('Thank you!'), description: _t('Thank you!'),
}); });

View file

@ -50,7 +50,7 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
submitFeedback(SdkConfig.get().bug_report_endpoint_url, rageshakeLabel, comment, canContact, rageshakeData); submitFeedback(SdkConfig.get().bug_report_endpoint_url, rageshakeLabel, comment, canContact, rageshakeData);
onFinished(true); onFinished(true);
Modal.createTrackedDialog("Feedback Sent", rageshakeLabel, InfoDialog, { Modal.createDialog(InfoDialog, {
title, title,
description: _t("Feedback sent! Thanks, we appreciate it!"), description: _t("Feedback sent! Thanks, we appreciate it!"),
button: _t("Close"), button: _t("Close"),

View file

@ -904,7 +904,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
if (this.unmounted) return; if (this.unmounted) return;
if (failed.length > 0) { if (failed.length > 0) {
Modal.createTrackedDialog('Invite Paste Fail', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t('Failed to find the following users'), title: _t('Failed to find the following users'),
description: _t( description: _t(
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s", "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s",

View file

@ -80,7 +80,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
} }
private onExportE2eKeysClicked = (): void => { private onExportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Export E2E Keys', '', Modal.createDialogAsync(
import( import(
'../../../async-components/views/dialogs/security/ExportE2eKeysDialog' '../../../async-components/views/dialogs/security/ExportE2eKeysDialog'
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,
@ -103,12 +103,9 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
// A key backup exists for this account, but the creating device is not // A key backup exists for this account, but the creating device is not
// verified, so restore the backup which will give us the keys from it and // verified, so restore the backup which will give us the keys from it and
// allow us to trust it (ie. upload keys to it) // allow us to trust it (ie. upload keys to it)
Modal.createTrackedDialog( Modal.createDialog(RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true);
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
/* priority = */ false, /* static = */ true,
);
} else { } else {
Modal.createTrackedDialogAsync("Key Backup", "Key Backup", Modal.createDialogAsync(
import( import(
"../../../async-components/views/dialogs/security/CreateKeyBackupDialog" "../../../async-components/views/dialogs/security/CreateKeyBackupDialog"
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,

View file

@ -56,7 +56,7 @@ export default class RoomUpgradeDialog extends React.Component<IProps, IState> {
upgradeRoom(this.props.room, this.targetVersion, false, false).then(() => { upgradeRoom(this.props.room, this.targetVersion, false, false).then(() => {
this.props.onFinished(true); this.props.onFinished(true);
}).catch((err) => { }).catch((err) => {
Modal.createTrackedDialog('Failed to upgrade room', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to upgrade room"), title: _t("Failed to upgrade room"),
description: ((err && err.message) ? err.message : _t("The room upgrade could not be completed")), description: ((err && err.message) ? err.message : _t("The room upgrade could not be completed")),
}); });

View file

@ -97,7 +97,7 @@ export default class RoomUpgradeWarningDialog extends React.Component<IProps, IS
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {}); Modal.createDialog(BugReportDialog, {});
}; };
render() { render() {

View file

@ -33,13 +33,13 @@ interface IProps extends IDialogProps {
export default class SessionRestoreErrorDialog extends React.Component<IProps> { export default class SessionRestoreErrorDialog extends React.Component<IProps> {
private sendBugReport = (): void => { private sendBugReport = (): void => {
Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, { Modal.createDialog(BugReportDialog, {
error: this.props.error, error: this.props.error,
}); });
}; };
private onClearStorageClick = (): void => { private onClearStorageClick = (): void => {
Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t("Sign out"), title: _t("Sign out"),
description: description:
<div>{ _t("Sign out and remove encryption keys?") }</div>, <div>{ _t("Sign out and remove encryption keys?") }</div>,

View file

@ -64,7 +64,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
private onSubmit = (): void => { private onSubmit = (): void => {
const emailAddress = this.state.emailAddress; const emailAddress = this.state.emailAddress;
if (!Email.looksValid(emailAddress)) { if (!Email.looksValid(emailAddress)) {
Modal.createTrackedDialog('Invalid Email Address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Invalid Email Address"), title: _t("Invalid Email Address"),
description: _t("This doesn't appear to be a valid email address"), description: _t("This doesn't appear to be a valid email address"),
}); });
@ -72,7 +72,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
} }
this.addThreepid = new AddThreepid(); this.addThreepid = new AddThreepid();
this.addThreepid.addEmailAddress(emailAddress).then(() => { this.addThreepid.addEmailAddress(emailAddress).then(() => {
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t("Verification Pending"), title: _t("Verification Pending"),
description: _t( description: _t(
"Please check your email and click on the link it contains. Once this " + "Please check your email and click on the link it contains. Once this " +
@ -84,7 +84,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
}, (err) => { }, (err) => {
this.setState({ emailBusy: false }); this.setState({ emailBusy: false });
logger.error("Unable to add email address " + emailAddress + " " + err); logger.error("Unable to add email address " + emailAddress + " " + err);
Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to add email address"), title: _t("Unable to add email address"),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
@ -112,7 +112,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
if (err.errcode == 'M_THREEPID_AUTH_FAILED') { if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
const message = _t("Unable to verify email address.") + " " + const message = _t("Unable to verify email address.") + " " +
_t("Please check your email and click on the link it contains. Once this is done, click continue."); _t("Please check your email and click on the link it contains. Once this is done, click continue.");
Modal.createTrackedDialog('Verification Pending', '3pid Auth Failed', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t("Verification Pending"), title: _t("Verification Pending"),
description: message, description: message,
button: _t('Continue'), button: _t('Continue'),
@ -120,7 +120,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
}); });
} else { } else {
logger.error("Unable to verify email address: " + err); logger.error("Unable to verify email address: " + err);
Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify email address."), title: _t("Unable to verify email address."),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });

View file

@ -701,7 +701,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
}; };
const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => { const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => {
Modal.createTrackedDialog("Spotlight Feedback", "feature_spotlight", BetaFeedbackDialog, { Modal.createDialog(BetaFeedbackDialog, {
featureId: "feature_spotlight", featureId: "feature_spotlight",
}); });
} : null; } : null;

View file

@ -30,7 +30,7 @@ interface IProps extends IDialogProps { }
export default class StorageEvictedDialog extends React.Component<IProps> { export default class StorageEvictedDialog extends React.Component<IProps> {
private sendBugReport = (ev: React.MouseEvent): void => { private sendBugReport = (ev: React.MouseEvent): void => {
ev.preventDefault(); ev.preventDefault();
Modal.createTrackedDialog('Storage evicted', 'Send Bug Report Dialog', BugReportDialog, {}); Modal.createDialog(BugReportDialog, {});
}; };
private onSignOutClick = (): void => { private onSignOutClick = (): void => {

View file

@ -233,14 +233,11 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
await cli.bootstrapCrossSigning({ await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => { authUploadDeviceSigningKeys: async (makeRequest) => {
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(InteractiveAuthDialog, {
'Cross-signing keys dialog', '', InteractiveAuthDialog, title: _t("Setting up keys"),
{ matrixClient: cli,
title: _t("Setting up keys"), makeRequest,
matrixClient: cli, });
makeRequest,
},
);
const [confirmed] = await finished; const [confirmed] = await finished;
if (!confirmed) { if (!confirmed) {
throw new Error("Cross-signing key upload auth canceled"); throw new Error("Cross-signing key upload auth canceled");

View file

@ -123,18 +123,15 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
}, },
}; };
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(InteractiveAuthDialog, {
'Cross-signing keys dialog', '', InteractiveAuthDialog, title: _t("Setting up keys"),
{ matrixClient: MatrixClientPeg.get(),
title: _t("Setting up keys"), makeRequest,
matrixClient: MatrixClientPeg.get(), aestheticsForStagePhases: {
makeRequest, [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
aestheticsForStagePhases: { [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
},
}, },
); });
const [confirmed] = await finished; const [confirmed] = await finished;
if (!confirmed) { if (!confirmed) {
throw new Error("Cross-signing key upload auth canceled"); throw new Error("Cross-signing key upload auth canceled");

View file

@ -188,20 +188,16 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
if (removableServers.has(server)) { if (removableServers.has(server)) {
const onClick = async () => { const onClick = async () => {
closeMenu(); closeMenu();
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(QuestionDialog, {
"Network Dropdown", "Remove server", QuestionDialog, title: _t("Are you sure?"),
{ description: _t("Are you sure you want to remove <b>%(serverName)s</b>", {
title: _t("Are you sure?"), serverName: server,
description: _t("Are you sure you want to remove <b>%(serverName)s</b>", { }, {
serverName: server, b: serverName => <b>{ serverName }</b>,
}, { }),
b: serverName => <b>{ serverName }</b>, button: _t("Remove"),
}), fixedWidth: false,
button: _t("Remove"), }, "mx_NetworkDropdown_dialog");
fixedWidth: false,
},
"mx_NetworkDropdown_dialog",
);
const [ok] = await finished; const [ok] = await finished;
if (!ok) return; if (!ok) return;
@ -242,7 +238,7 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
const onClick = async () => { const onClick = async () => {
closeMenu(); closeMenu();
const { finished } = Modal.createTrackedDialog("Network Dropdown", "Add a new server", TextInputDialog, { const { finished } = Modal.createDialog(TextInputDialog, {
title: _t("Add a new server"), title: _t("Add a new server"),
description: _t("Enter the name of a new server you want to explore."), description: _t("Enter the name of a new server you want to explore."),
button: _t("Add"), button: _t("Add"),

View file

@ -1,105 +0,0 @@
/*
Copyright 2017 Vector Creations 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 from 'react';
import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher/dispatcher';
import Analytics from '../../../Analytics';
import Tooltip from './Tooltip';
interface IProps {
size?: string;
tooltip?: boolean;
action: string;
mouseOverAction?: string;
label: string;
iconPath?: string;
className?: string;
children?: JSX.Element;
}
interface IState {
showTooltip: boolean;
}
export default class ActionButton extends React.Component<IProps, IState> {
static defaultProps: Partial<IProps> = {
size: "25",
tooltip: false,
};
constructor(props: IProps) {
super(props);
this.state = {
showTooltip: false,
};
}
private onClick = (ev: React.MouseEvent): void => {
ev.stopPropagation();
Analytics.trackEvent('Action Button', 'click', this.props.action);
dis.dispatch({ action: this.props.action });
};
private onMouseEnter = (): void => {
this.showTooltip();
if (this.props.mouseOverAction) {
dis.dispatch({ action: this.props.mouseOverAction });
}
};
private showTooltip = (): void => {
if (this.props.tooltip) this.setState({ showTooltip: true });
};
private hideTooltip = (): void => {
this.setState({ showTooltip: false });
};
render() {
let tooltip;
if (this.state.showTooltip) {
tooltip = <Tooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
}
const icon = this.props.iconPath ?
(<img src={this.props.iconPath} width={this.props.size} height={this.props.size} />) :
undefined;
const classNames = ["mx_RoleButton"];
if (this.props.className) {
classNames.push(this.props.className);
}
return (
<AccessibleButton
className={classNames.join(" ")}
onClick={this.onClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.hideTooltip}
onFocus={this.showTooltip}
onBlur={this.hideTooltip}
aria-label={this.props.label}
>
{ icon }
{ tooltip }
{ this.props.children }
</AccessibleButton>
);
}
}

View file

@ -68,7 +68,7 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> {
}; };
private onBugReport = (): void => { private onBugReport = (): void => {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, { Modal.createDialog(BugReportDialog, {
label: 'react-soft-crash', label: 'react-soft-crash',
error: this.state.error, error: this.state.error,
}); });

View file

@ -18,7 +18,6 @@ import classNames from 'classnames';
import { EventType } from 'matrix-js-sdk/src/@types/event'; import { EventType } from 'matrix-js-sdk/src/@types/event';
import React, { useContext, useRef, useState, MouseEvent } from 'react'; import React, { useContext, useRef, useState, MouseEvent } from 'react';
import Analytics from "../../../Analytics";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import { useTimeout } from "../../../hooks/useTimeout"; import { useTimeout } from "../../../hooks/useTimeout";
@ -74,7 +73,6 @@ const MiniAvatarUploader: React.FC<IProps> = ({
onChange={async (ev) => { onChange={async (ev) => {
if (!ev.target.files?.length) return; if (!ev.target.files?.length) return;
setBusy(true); setBusy(true);
Analytics.trackEvent("mini_avatar", "upload");
const file = ev.target.files[0]; const file = ev.target.files[0];
const uri = await cli.uploadContent(file); const uri = await cli.uploadContent(file);
await setAvatarUrl(uri); await setAvatarUrl(uri);

View file

@ -172,25 +172,20 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
() => this.props.onFinished(true), () => this.props.onFinished(true),
).catch(e => { ).catch(e => {
console.error("Failed to post poll:", e); console.error("Failed to post poll:", e);
Modal.createTrackedDialog( Modal.createDialog(QuestionDialog, {
'Failed to post poll', title: _t("Failed to post poll"),
'', description: _t(
QuestionDialog, "Sorry, the poll you tried to create was not posted."),
{ button: _t('Try again'),
title: _t("Failed to post poll"), cancelButton: _t('Cancel'),
description: _t( onFinished: (tryAgain: boolean) => {
"Sorry, the poll you tried to create was not posted."), if (!tryAgain) {
button: _t('Try again'), this.cancel();
cancelButton: _t('Cancel'), } else {
onFinished: (tryAgain: boolean) => { this.setState({ busy: false, canSubmit: true });
if (!tryAgain) { }
this.cancel();
} else {
this.setState({ busy: false, canSubmit: true });
}
},
}, },
); });
}); });
} }

View file

@ -37,12 +37,12 @@ const showPickerDialog = (
serverConfig: ValidatedServerConfig, serverConfig: ValidatedServerConfig,
onFinished: (config: ValidatedServerConfig) => void, onFinished: (config: ValidatedServerConfig) => void,
) => { ) => {
Modal.createTrackedDialog("Server Picker", "", ServerPickerDialog, { title, serverConfig, onFinished }); Modal.createDialog(ServerPickerDialog, { title, serverConfig, onFinished });
}; };
const onHelpClick = () => { const onHelpClick = () => {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
Modal.createTrackedDialog('Custom Server Dialog', '', InfoDialog, { Modal.createDialog(InfoDialog, {
title: _t("Server Options"), title: _t("Server Options"),
description: _t("You can use the custom server options to sign into other Matrix servers by specifying " + description: _t("You can use the custom server options to sign into other Matrix servers by specifying " +
"a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on " + "a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on " +

View file

@ -183,15 +183,10 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
// pin drop location without permissions is ok // pin drop location without permissions is ok
if (isSharingOwnLocation(this.props.shareType)) { if (isSharingOwnLocation(this.props.shareType)) {
this.props.onFinished(); this.props.onFinished();
Modal.createTrackedDialog( Modal.createDialog(ErrorDialog, {
'Could not fetch location', title: _t("Could not fetch location"),
'', description: positionFailureMessage(e.code),
ErrorDialog, });
{
title: _t("Could not fetch location"),
description: positionFailureMessage(e.code),
},
);
} }
if (this.geolocate) { if (this.geolocate) {

View file

@ -49,7 +49,6 @@ const handleShareError = (error: Error, openMenu: () => void, shareType: Locatio
"We couldn't start sharing your live location" : "We couldn't start sharing your live location" :
"We couldn't send your location"; "We couldn't send your location";
logger.error(errorMessage, error); logger.error(errorMessage, error);
const analyticsAction = errorMessage;
const params = { const params = {
title: _t("We couldn't send your location"), title: _t("We couldn't send your location"),
description: _t("%(brand)s could not send your location. Please try again later.", { description: _t("%(brand)s could not send your location. Please try again later.", {
@ -63,7 +62,7 @@ const handleShareError = (error: Error, openMenu: () => void, shareType: Locatio
} }
}, },
}; };
Modal.createTrackedDialog(analyticsAction, '', QuestionDialog, params); Modal.createDialog(QuestionDialog, params);
}; };
export const shareLiveLocation = ( export const shareLiveLocation = (

View file

@ -154,7 +154,7 @@ export default class DateSeparator extends React.Component<IProps, IState> {
// detached queue and we show the room status bar to allow retry // detached queue and we show the room status bar to allow retry
if (typeof code !== "undefined") { if (typeof code !== "undefined") {
// display error message stating you couldn't delete this. // display error message stating you couldn't delete this.
Modal.createTrackedDialog('Unable to find event at that date', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Error'), title: _t('Error'),
description: _t('Unable to find event at that date. (%(code)s)', { code }), description: _t('Unable to find event at that date. (%(code)s)', { code }),
}); });

View file

@ -75,13 +75,13 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
const event = this.props.mxEvent; const event = this.props.mxEvent;
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
Modal.createTrackedDialog('Confirm Redact Dialog', 'Edit history', ConfirmAndWaitRedactDialog, { Modal.createDialog(ConfirmAndWaitRedactDialog, {
redact: () => cli.redactEvent(event.getRoomId(), event.getId()), redact: () => cli.redactEvent(event.getRoomId(), event.getId()),
}, 'mx_Dialog_confirmredact'); }, 'mx_Dialog_confirmredact');
}; };
private onViewSourceClick = (): void => { private onViewSourceClick = (): void => {
Modal.createTrackedDialog('View Event Source', 'Edit history', ViewSource, { Modal.createDialog(ViewSource, {
mxEvent: this.props.mxEvent, mxEvent: this.props.mxEvent,
}, 'mx_Dialog_viewsource'); }, 'mx_Dialog_viewsource');
}; };

View file

@ -106,9 +106,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
if (displayStatus !== BeaconDisplayStatus.Active) { if (displayStatus !== BeaconDisplayStatus.Active) {
return; return;
} }
Modal.createTrackedDialog( Modal.createDialog(
'Beacon View',
'',
BeaconViewDialog, BeaconViewDialog,
{ {
roomId: mxEvent.getRoomId(), roomId: mxEvent.getRoomId(),

View file

@ -172,7 +172,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
}); });
} catch (err) { } catch (err) {
logger.warn("Unable to decrypt attachment: ", err); logger.warn("Unable to decrypt attachment: ", err);
Modal.createTrackedDialog('Error decrypting attachment', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error"), title: _t("Error"),
description: _t("Error decrypting attachment"), description: _t("Error decrypting attachment"),
}); });

View file

@ -57,9 +57,7 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
} }
private onClick = () => { private onClick = () => {
Modal.createTrackedDialog( Modal.createDialog(
'Location View',
'',
LocationViewDialog, LocationViewDialog,
{ {
matrixClient: this.context, matrixClient: this.context,

View file

@ -181,9 +181,7 @@ export function pollAlreadyHasVotes(mxEvent: MatrixEvent, getRelationsForEvent?:
export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): void { export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): void {
if (pollAlreadyHasVotes(mxEvent, getRelationsForEvent)) { if (pollAlreadyHasVotes(mxEvent, getRelationsForEvent)) {
Modal.createTrackedDialog( Modal.createDialog(
'Not allowed to edit poll',
'',
ErrorDialog, ErrorDialog,
{ {
title: _t("Can't edit poll"), title: _t("Can't edit poll"),
@ -193,9 +191,7 @@ export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: Ge
}, },
); );
} else { } else {
Modal.createTrackedDialog( Modal.createDialog(
'Polls',
'create',
PollCreateDialog, PollCreateDialog,
{ {
room: MatrixClientPeg.get().getRoom(mxEvent.getRoomId()), room: MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
@ -312,9 +308,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
).catch((e: any) => { ).catch((e: any) => {
console.error("Failed to submit poll response event:", e); console.error("Failed to submit poll response event:", e);
Modal.createTrackedDialog( Modal.createDialog(
'Vote not registered',
'',
ErrorDialog, ErrorDialog,
{ {
title: _t("Vote not registered"), title: _t("Vote not registered"),

View file

@ -468,7 +468,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
scalarClient.connect().then(() => { scalarClient.connect().then(() => {
const completeUrl = scalarClient.getStarterLink(starterLink); const completeUrl = scalarClient.getStarterLink(starterLink);
const integrationsUrl = integrationManager.uiUrl; const integrationsUrl = integrationManager.uiUrl;
Modal.createTrackedDialog('Add an integration', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t("Add an Integration"), title: _t("Add an Integration"),
description: description:
<div> <div>

View file

@ -52,14 +52,14 @@ export default class TileErrorBoundary extends React.Component<IProps, IState> {
} }
private onBugReport = (): void => { private onBugReport = (): void => {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, { Modal.createDialog(BugReportDialog, {
label: 'react-soft-crash-tile', label: 'react-soft-crash-tile',
error: this.state.error, error: this.state.error,
}); });
}; };
private onViewSource = (): void => { private onViewSource = (): void => {
Modal.createTrackedDialog('View Event Source', 'from crash', ViewSource, { Modal.createDialog(ViewSource, {
mxEvent: this.props.mxEvent, mxEvent: this.props.mxEvent,
}, 'mx_Dialog_viewsource'); }, 'mx_Dialog_viewsource');
}; };

View file

@ -84,7 +84,7 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
const changeHandler = useCallback(() => { const changeHandler = useCallback(() => {
// handle transitions -> cancelled for mismatches which fire a modal instead of showing a card // handle transitions -> cancelled for mismatches which fire a modal instead of showing a card
if (request && request.cancelled && MISMATCHES.includes(request.cancellationCode)) { if (request && request.cancelled && MISMATCHES.includes(request.cancellationCode)) {
Modal.createTrackedDialog("Verification failed", "insecure", ErrorDialog, { Modal.createDialog(ErrorDialog, {
headerImage: require("../../../../res/img/e2e/warning.svg").default, headerImage: require("../../../../res/img/e2e/warning.svg").default,
title: _t("Your messages are not secure"), title: _t("Your messages are not secure"),
description: <div> description: <div>

View file

@ -21,7 +21,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import Analytics from '../../../Analytics';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { ButtonEvent } from "../elements/AccessibleButton"; import { ButtonEvent } from "../elements/AccessibleButton";
@ -31,8 +30,6 @@ interface IProps {
isUnread?: boolean; isUnread?: boolean;
// click handler // click handler
onClick: (ev: ButtonEvent) => void; onClick: (ev: ButtonEvent) => void;
// The parameters to track the click event
analytics: Parameters<typeof Analytics.trackEvent>;
// Button name // Button name
name: string; name: string;
@ -42,14 +39,8 @@ interface IProps {
// TODO: replace this, the composer buttons and the right panel buttons with a unified representation // TODO: replace this, the composer buttons and the right panel buttons with a unified representation
export default class HeaderButton extends React.Component<IProps> { export default class HeaderButton extends React.Component<IProps> {
private onClick = (ev: ButtonEvent) => {
Analytics.trackEvent(...this.props.analytics);
this.props.onClick(ev);
};
public render() { public render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars const { isHighlighted, isUnread = false, onClick, name, title, ...props } = this.props;
const { isHighlighted, isUnread = false, onClick, analytics, name, title, ...props } = this.props;
const classes = classNames({ const classes = classNames({
mx_RightPanel_headerButton: true, mx_RightPanel_headerButton: true,
@ -64,7 +55,7 @@ export default class HeaderButton extends React.Component<IProps> {
role="tab" role="tab"
title={title} title={title}
className={classes} className={classes}
onClick={this.onClick} onClick={onClick}
/>; />;
} }
} }

View file

@ -95,7 +95,6 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }: IHeaderBut
isHighlighted={isHighlighted} isHighlighted={isHighlighted}
isUnread={!!unreadIndicator} isUnread={!!unreadIndicator}
onClick={onClick} onClick={onClick}
analytics={["Right Panel", "Pinned Messages Button", "click"]}
> >
{ unreadIndicator } { unreadIndicator }
</HeaderButton>; </HeaderButton>;
@ -115,7 +114,6 @@ const TimelineCardHeaderButton = ({ room, isHighlighted, onClick }: IHeaderButto
title={_t("Chat")} title={_t("Chat")}
isHighlighted={isHighlighted} isHighlighted={isHighlighted}
onClick={onClick} onClick={onClick}
analytics={["Right Panel", "Timeline Panel Button", "click"]}
> >
{ unreadIndicator } { unreadIndicator }
</HeaderButton>; </HeaderButton>;
@ -244,7 +242,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
onClick={this.onThreadsPanelClicked} onClick={this.onThreadsPanelClicked}
isHighlighted={this.isPhase(RoomHeaderButtons.THREAD_PHASES)} isHighlighted={this.isPhase(RoomHeaderButtons.THREAD_PHASES)}
isUnread={this.threadNotificationState.color > 0} isUnread={this.threadNotificationState.color > 0}
analytics={['Right Panel', 'Threads List Button', 'click']}> >
<UnreadIndicator color={this.threadNotificationState.color} /> <UnreadIndicator color={this.threadNotificationState.color} />
</HeaderButton> </HeaderButton>
: null, : null,
@ -256,7 +254,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
title={_t('Notifications')} title={_t('Notifications')}
isHighlighted={this.isPhase(RightPanelPhases.NotificationPanel)} isHighlighted={this.isPhase(RightPanelPhases.NotificationPanel)}
onClick={this.onNotificationsClicked} onClick={this.onNotificationsClicked}
analytics={['Right Panel', 'Notification List Button', 'click']} />, />,
); );
rightPanelPhaseButtons.set(RightPanelPhases.RoomSummary, rightPanelPhaseButtons.set(RightPanelPhases.RoomSummary,
<HeaderButton <HeaderButton
@ -265,7 +263,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
title={_t('Room Info')} title={_t('Room Info')}
isHighlighted={this.isPhase(ROOM_INFO_PHASES)} isHighlighted={this.isPhase(ROOM_INFO_PHASES)}
onClick={this.onRoomSummaryClicked} onClick={this.onRoomSummaryClicked}
analytics={['Right Panel', 'Room Summary Button', 'click']} />, />,
); );
return <> return <>

View file

@ -248,13 +248,13 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const onShareRoomClick = () => { const onShareRoomClick = () => {
Modal.createTrackedDialog('share room dialog', '', ShareDialog, { Modal.createDialog(ShareDialog, {
target: room, target: room,
}); });
}; };
const onRoomExportClick = async () => { const onRoomExportClick = async () => {
Modal.createTrackedDialog('export room dialog', '', ExportDialog, { Modal.createDialog(ExportDialog, {
room, room,
}); });
}; };

View file

@ -365,7 +365,7 @@ const UserOptionsSection: React.FC<{
const isMe = member.userId === cli.getUserId(); const isMe = member.userId === cli.getUserId();
const onShareUserClick = () => { const onShareUserClick = () => {
Modal.createTrackedDialog('share room member dialog', '', ShareDialog, { Modal.createDialog(ShareDialog, {
target: member, target: member,
}); });
}; };
@ -451,7 +451,7 @@ const UserOptionsSection: React.FC<{
} }
}); });
} catch (err) { } catch (err) {
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to invite'), title: _t('Failed to invite'),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
@ -503,7 +503,7 @@ const UserOptionsSection: React.FC<{
}; };
const warnSelfDemote = async (isSpace: boolean) => { const warnSelfDemote = async (isSpace: boolean) => {
const { finished } = Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, { const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("Demote yourself?"), title: _t("Demote yourself?"),
description: description:
<div> <div>
@ -590,9 +590,7 @@ const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBas
if (member.membership !== "invite" && member.membership !== "join") return null; if (member.membership !== "invite" && member.membership !== "join") return null;
const onKick = async () => { const onKick = async () => {
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(
'Confirm User Action Dialog',
'onKick',
room.isSpaceRoom() ? ConfirmSpaceUserActionDialog : ConfirmUserActionDialog, room.isSpaceRoom() ? ConfirmSpaceUserActionDialog : ConfirmUserActionDialog,
{ {
member, member,
@ -632,7 +630,7 @@ const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBas
logger.log("Kick success"); logger.log("Kick success");
}, function(err) { }, function(err) {
logger.error("Kick error: " + err); logger.error("Kick error: " + err);
Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to remove user"), title: _t("Failed to remove user"),
description: ((err && err.message) ? err.message : "Operation failed"), description: ((err && err.message) ? err.message : "Operation failed"),
}); });
@ -661,7 +659,7 @@ const RedactMessagesButton: React.FC<IBaseProps> = ({ member }) => {
const room = cli.getRoom(member.roomId); const room = cli.getRoom(member.roomId);
if (!room) return; if (!room) return;
Modal.createTrackedDialog("Bulk Redact Dialog", "", BulkRedactDialog, { Modal.createDialog(BulkRedactDialog, {
matrixClient: cli, matrixClient: cli,
room, member, room, member,
}); });
@ -681,9 +679,7 @@ const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBa
const isBanned = member.membership === "ban"; const isBanned = member.membership === "ban";
const onBanOrUnban = async () => { const onBanOrUnban = async () => {
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(
'Confirm User Action Dialog',
'onBanOrUnban',
room.isSpaceRoom() ? ConfirmSpaceUserActionDialog : ConfirmUserActionDialog, room.isSpaceRoom() ? ConfirmSpaceUserActionDialog : ConfirmUserActionDialog,
{ {
member, member,
@ -746,7 +742,7 @@ const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBa
logger.log("Ban success"); logger.log("Ban success");
}, function(err) { }, function(err) {
logger.error("Ban error: " + err); logger.error("Ban error: " + err);
Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error"), title: _t("Error"),
description: _t("Failed to ban user"), description: _t("Failed to ban user"),
}); });
@ -827,7 +823,7 @@ const MuteToggleButton: React.FC<IBaseRoomProps> = ({ member, room, powerLevels,
logger.log("Mute toggle success"); logger.log("Mute toggle success");
}, function(err) { }, function(err) {
logger.error("Mute error: " + err); logger.error("Mute error: " + err);
Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error"), title: _t("Error"),
description: _t("Failed to mute user"), description: _t("Failed to mute user"),
}); });
@ -1048,7 +1044,7 @@ const PowerLevelEditor: React.FC<{
logger.log("Power change success"); logger.log("Power change success");
}, function(err) { }, function(err) {
logger.error("Failed to change power level " + err); logger.error("Failed to change power level " + err);
Modal.createTrackedDialog('Failed to change power level', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error"), title: _t("Error"),
description: _t("Failed to change power level"), description: _t("Failed to change power level"),
}); });
@ -1065,7 +1061,7 @@ const PowerLevelEditor: React.FC<{
const myUserId = cli.getUserId(); const myUserId = cli.getUserId();
const myPower = powerLevelEvent.getContent().users[myUserId]; const myPower = powerLevelEvent.getContent().users[myUserId];
if (myPower && parseInt(myPower) <= powerLevel && myUserId !== target) { if (myPower && parseInt(myPower) <= powerLevel && myUserId !== target) {
const { finished } = Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, { const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("Warning!"), title: _t("Warning!"),
description: description:
<div> <div>
@ -1214,7 +1210,7 @@ const BasicUserInfo: React.FC<{
const roomPermissions = useRoomPermissions(cli, room, member as RoomMember); const roomPermissions = useRoomPermissions(cli, room, member as RoomMember);
const onSynapseDeactivate = useCallback(async () => { const onSynapseDeactivate = useCallback(async () => {
const { finished } = Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, { const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("Deactivate user?"), title: _t("Deactivate user?"),
description: description:
<div>{ _t( <div>{ _t(
@ -1234,7 +1230,7 @@ const BasicUserInfo: React.FC<{
logger.error("Failed to deactivate user"); logger.error("Failed to deactivate user");
logger.error(err); logger.error(err);
Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to deactivate user'), title: _t('Failed to deactivate user'),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });

View file

@ -173,7 +173,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
this.context.sendStateEvent(this.props.roomId, "m.room.canonical_alias", this.context.sendStateEvent(this.props.roomId, "m.room.canonical_alias",
eventContent, "").catch((err) => { eventContent, "").catch((err) => {
logger.error(err); logger.error(err);
Modal.createTrackedDialog('Error updating main address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error updating main address"), title: _t("Error updating main address"),
description: _t( description: _t(
"There was an error updating the room's main address. It may not be allowed by the server " + "There was an error updating the room's main address. It may not be allowed by the server " +
@ -211,7 +211,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
.catch((err) => { .catch((err) => {
// TODO: Add error handling based upon server validation // TODO: Add error handling based upon server validation
logger.error(err); logger.error(err);
Modal.createTrackedDialog('Error updating alternative addresses', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error updating main address"), title: _t("Error updating main address"),
description: _t( description: _t(
"There was an error updating the room's alternative addresses. " + "There was an error updating the room's alternative addresses. " +
@ -243,7 +243,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
} }
}).catch((err) => { }).catch((err) => {
logger.error(err); logger.error(err);
Modal.createTrackedDialog('Error creating address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error creating address"), title: _t("Error creating address"),
description: _t( description: _t(
"There was an error creating that address. It may not be allowed by the server " + "There was an error creating that address. It may not be allowed by the server " +
@ -275,7 +275,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
"error occurred.", "error occurred.",
); );
} }
Modal.createTrackedDialog('Error removing address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error removing address"), title: _t("Error removing address"),
description, description,
}); });

View file

@ -315,9 +315,7 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
MatrixClientPeg.get().getUserId(), MatrixClientPeg.get().getUserId(),
); );
if (!canSend) { if (!canSend) {
Modal.createTrackedDialog( Modal.createDialog(
'Polls',
'permissions error: cannot start',
ErrorDialog, ErrorDialog,
{ {
title: _t("Permission Required"), title: _t("Permission Required"),
@ -331,9 +329,7 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
? this.props.relation.event_id ? this.props.relation.event_id
: null; : null;
Modal.createTrackedDialog( Modal.createDialog(
'Polls',
'create',
PollCreateDialog, PollCreateDialog,
{ {
room: this.props.room, room: this.props.room,

View file

@ -22,7 +22,6 @@ import { BreadcrumbsStore } from "../../../stores/BreadcrumbsStore";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import defaultDispatcher from "../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../dispatcher/dispatcher";
import Analytics from "../../../Analytics";
import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
import Toolbar from "../../../accessibility/Toolbar"; import Toolbar from "../../../accessibility/Toolbar";
@ -104,7 +103,6 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
}; };
private viewRoom = (room: Room, index: number, viaKeyboard = false) => { private viewRoom = (room: Room, index: number, viaKeyboard = false) => {
Analytics.trackEvent("Breadcrumbs", "click_node", String(index));
defaultDispatcher.dispatch<ViewRoomPayload>({ defaultDispatcher.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,
room_id: room.roomId, room_id: room.roomId,

View file

@ -66,7 +66,7 @@ export default class RoomUpgradeWarningBar extends React.PureComponent<IProps, I
}; };
private onUpgradeClick = (): void => { private onUpgradeClick = (): void => {
Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room: this.props.room }); Modal.createDialog(RoomUpgradeDialog, { room: this.props.room });
}; };
public render(): JSX.Element { public render(): JSX.Element {

View file

@ -106,7 +106,7 @@ export default class ThirdPartyMemberInfo extends React.Component<IProps, IState
// Revert echo because of error // Revert echo because of error
this.setState({ invited: true }); this.setState({ invited: true });
Modal.createTrackedDialog('Revoke 3pid invite failed', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to revoke invite"), title: _t("Failed to revoke invite"),
description: _t( description: _t(
"Could not revoke the invite. The server may be experiencing a temporary problem or " + "Could not revoke the invite. The server may be experiencing a temporary problem or " +

View file

@ -162,7 +162,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
// The "microphone access error" dialogs are used a lot, so let's functionify them // The "microphone access error" dialogs are used a lot, so let's functionify them
const accessError = () => { const accessError = () => {
Modal.createTrackedDialog('Microphone Access Error', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to access your microphone"), title: _t("Unable to access your microphone"),
description: <> description: <>
<p>{ _t( <p>{ _t(
@ -177,7 +177,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
try { try {
const devices = await MediaDeviceHandler.getDevices(); const devices = await MediaDeviceHandler.getDevices();
if (!devices?.[MediaDeviceKindEnum.AudioInput]?.length) { if (!devices?.[MediaDeviceKindEnum.AudioInput]?.length) {
Modal.createTrackedDialog('No Microphone Error', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("No microphone found"), title: _t("No microphone found"),
description: <> description: <>
<p>{ _t( <p>{ _t(

View file

@ -95,7 +95,7 @@ export default class ChangePassword extends React.Component<IProps, IState> {
if (userHasOtherDevices && !serverSupportsControlOfDevicesLogout && this.props.confirm) { if (userHasOtherDevices && !serverSupportsControlOfDevicesLogout && this.props.confirm) {
// warn about logging out all devices // warn about logging out all devices
const { finished } = Modal.createTrackedDialog<[boolean]>('Change Password', '', QuestionDialog, { const { finished } = Modal.createDialog<[boolean]>(QuestionDialog, {
title: _t("Warning!"), title: _t("Warning!"),
description: description:
<div> <div>
@ -196,14 +196,14 @@ export default class ChangePassword extends React.Component<IProps, IState> {
private optionallySetEmail(): Promise<boolean> { private optionallySetEmail(): Promise<boolean> {
// Ask for an email otherwise the user has no way to reset their password // Ask for an email otherwise the user has no way to reset their password
const modal = Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, { const modal = Modal.createDialog(SetEmailDialog, {
title: _t('Do you want to set an email address?'), title: _t('Do you want to set an email address?'),
}); });
return modal.finished.then(([confirmed]) => confirmed); return modal.finished.then(([confirmed]) => confirmed);
} }
private onExportE2eKeysClicked = (): void => { private onExportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', Modal.createDialogAsync(
import( import(
'../../../async-components/views/dialogs/security/ExportE2eKeysDialog' '../../../async-components/views/dialogs/security/ExportE2eKeysDialog'
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,

View file

@ -75,10 +75,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
private onBootstrapClick = () => { private onBootstrapClick = () => {
if (this.state.crossSigningPrivateKeysInStorage) { if (this.state.crossSigningPrivateKeysInStorage) {
Modal.createTrackedDialog( Modal.createDialog(SetupEncryptionDialog, {}, null, /* priority = */ false, /* static = */ true);
"Verify session", "Verify session", SetupEncryptionDialog,
{}, null, /* priority = */ false, /* static = */ true,
);
} else { } else {
// Trigger the flow to set up secure backup, which is what this will do when in // Trigger the flow to set up secure backup, which is what this will do when in
// the appropriate state. // the appropriate state.
@ -130,14 +127,11 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
await cli.bootstrapCrossSigning({ await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => { authUploadDeviceSigningKeys: async (makeRequest) => {
const { finished } = Modal.createTrackedDialog( const { finished } = Modal.createDialog(InteractiveAuthDialog, {
'Cross-signing keys dialog', '', InteractiveAuthDialog, title: _t("Setting up keys"),
{ matrixClient: cli,
title: _t("Setting up keys"), makeRequest,
matrixClient: cli, });
makeRequest,
},
);
const [confirmed] = await finished; const [confirmed] = await finished;
if (!confirmed) { if (!confirmed) {
throw new Error("Cross-signing key upload auth canceled"); throw new Error("Cross-signing key upload auth canceled");

View file

@ -91,7 +91,7 @@ export default class CryptographyPanel extends React.Component<IProps, IState> {
} }
private onExportE2eKeysClicked = (): void => { private onExportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Export E2E Keys', '', Modal.createDialogAsync(
import( import(
'../../../async-components/views/dialogs/security/ExportE2eKeysDialog' '../../../async-components/views/dialogs/security/ExportE2eKeysDialog'
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,
@ -100,7 +100,7 @@ export default class CryptographyPanel extends React.Component<IProps, IState> {
}; };
private onImportE2eKeysClicked = (): void => { private onImportE2eKeysClicked = (): void => {
Modal.createTrackedDialogAsync('Import E2E Keys', '', Modal.createDialogAsync(
import( import(
'../../../async-components/views/dialogs/security/ImportE2eKeysDialog' '../../../async-components/views/dialogs/security/ImportE2eKeysDialog'
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,

View file

@ -215,7 +215,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
continueKind: "danger", continueKind: "danger",
}, },
}; };
Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, { Modal.createDialog(InteractiveAuthDialog, {
title: _t("Authentication"), title: _t("Authentication"),
matrixClient: MatrixClientPeg.get(), matrixClient: MatrixClientPeg.get(),
authData: error.data, authData: error.data,

View file

@ -84,14 +84,14 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
}; };
private onOwnDeviceSignOut = (): void => { private onOwnDeviceSignOut = (): void => {
Modal.createTrackedDialog('Logout from device list', '', LogoutDialog, Modal.createDialog(LogoutDialog,
/* props= */{}, /* className= */null, /* props= */{}, /* className= */null,
/* isPriority= */false, /* isStatic= */true); /* isPriority= */false, /* isStatic= */true);
}; };
private verify = async () => { private verify = async () => {
if (this.props.isOwnDevice) { if (this.props.isOwnDevice) {
Modal.createTrackedDialog("Verify session", "Verify session", SetupEncryptionDialog, { Modal.createDialog(SetupEncryptionDialog, {
onFinished: this.props.onDeviceChange, onFinished: this.props.onDeviceChange,
}); });
} else { } else {
@ -101,7 +101,7 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
userId, userId,
[this.props.device.device_id], [this.props.device.device_id],
); );
Modal.createTrackedDialog('New Session Verification', 'Starting dialog', VerificationRequestDialog, { Modal.createDialog(VerificationRequestDialog, {
verificationRequestPromise, verificationRequestPromise,
member: cli.getUser(userId), member: cli.getUser(userId),
onFinished: async () => { onFinished: async () => {

View file

@ -108,7 +108,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
} }
private onManage = async () => { private onManage = async () => {
Modal.createTrackedDialogAsync('Message search', 'Message search', Modal.createDialogAsync(
// @ts-ignore: TS doesn't seem to like the type of this now that it // @ts-ignore: TS doesn't seem to like the type of this now that it
// has also been converted to TS as well, but I can't figure out why... // has also been converted to TS as well, but I can't figure out why...
import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'), import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'),

View file

@ -75,7 +75,7 @@ const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeCh
} }
const matrixClient = MatrixClientPeg.get(); const matrixClient = MatrixClientPeg.get();
const { finished } = Modal.createTrackedDialog('Edit restricted', '', ManageRestrictedJoinRuleDialog, { const { finished } = Modal.createDialog(ManageRestrictedJoinRuleDialog, {
matrixClient, matrixClient,
room, room,
selected, selected,
@ -227,7 +227,7 @@ const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeCh
</b>; </b>;
} }
Modal.createTrackedDialog('Restricted join rule upgrade', '', RoomUpgradeWarningDialog, { Modal.createDialog(RoomUpgradeWarningDialog, {
roomId: room.roomId, roomId: room.roomId,
targetVersion, targetVersion,
description: <> description: <>

View file

@ -277,7 +277,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
} }
private showSaveError() { private showSaveError() {
Modal.createTrackedDialog('Error saving notification preferences', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Error saving notification preferences'), title: _t('Error saving notification preferences'),
description: _t('An error occurred whilst saving your notification preferences.'), description: _t('An error occurred whilst saving your notification preferences.'),
}); });

View file

@ -120,7 +120,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
} }
} catch (err) { } catch (err) {
logger.log("Failed to save profile", err); logger.log("Failed to save profile", err);
Modal.createTrackedDialog('Failed to save profile', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Failed to save your profile"), title: _t("Failed to save your profile"),
description: ((err && err.message) ? err.message : _t("The operation could not be completed")), description: ((err && err.message) ? err.message : _t("The operation could not be completed")),
}); });

View file

@ -167,7 +167,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
} }
private startNewBackup = (): void => { private startNewBackup = (): void => {
Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', Modal.createDialogAsync(
import( import(
'../../../async-components/views/dialogs/security/CreateKeyBackupDialog' '../../../async-components/views/dialogs/security/CreateKeyBackupDialog'
) as unknown as Promise<ComponentType<{}>>, ) as unknown as Promise<ComponentType<{}>>,
@ -180,7 +180,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
}; };
private deleteBackup = (): void => { private deleteBackup = (): void => {
Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t('Delete Backup'), title: _t('Delete Backup'),
description: _t( description: _t(
"Are you sure? You will lose your encrypted messages if your " + "Are you sure? You will lose your encrypted messages if your " +
@ -199,10 +199,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
}; };
private restoreBackup = async (): Promise<void> => { private restoreBackup = async (): Promise<void> => {
Modal.createTrackedDialog( Modal.createDialog(RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true);
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
/* priority = */ false, /* static = */ true,
);
}; };
private resetSecretStorage = async (): Promise<void> => { private resetSecretStorage = async (): Promise<void> => {

View file

@ -219,7 +219,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
}; };
private showNoTermsWarning(fullUrl) { private showNoTermsWarning(fullUrl) {
const { finished } = Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, { const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("Identity server has no terms of service"), title: _t("Identity server has no terms of service"),
description: ( description: (
<div> <div>
@ -320,7 +320,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
message = unboundMessage; message = unboundMessage;
} }
const { finished } = Modal.createTrackedDialog('Identity Server Bound Warning', '', QuestionDialog, { const { finished } = Modal.createDialog(QuestionDialog, {
title, title,
description: message, description: message,
button, button,

View file

@ -80,7 +80,7 @@ export class ExistingEmailAddress extends React.Component<IExistingEmailAddressP
return this.props.onRemoved(this.props.email); return this.props.onRemoved(this.props.email);
}).catch((err) => { }).catch((err) => {
logger.error("Unable to remove contact information: " + err); logger.error("Unable to remove contact information: " + err);
Modal.createTrackedDialog('Remove 3pid failed', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to remove contact information"), title: _t("Unable to remove contact information"),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
@ -168,7 +168,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
// TODO: Inline field validation // TODO: Inline field validation
if (!Email.looksValid(email)) { if (!Email.looksValid(email)) {
Modal.createTrackedDialog('Invalid email address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Invalid Email Address"), title: _t("Invalid Email Address"),
description: _t("This doesn't appear to be a valid email address"), description: _t("This doesn't appear to be a valid email address"),
}); });
@ -183,7 +183,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
}).catch((err) => { }).catch((err) => {
logger.error("Unable to add email address " + email + " " + err); logger.error("Unable to add email address " + email + " " + err);
this.setState({ verifying: false, continueDisabled: false, addTask: null }); this.setState({ verifying: false, continueDisabled: false, addTask: null });
Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to add email address"), title: _t("Unable to add email address"),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
@ -215,14 +215,14 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
}).catch((err) => { }).catch((err) => {
this.setState({ continueDisabled: false }); this.setState({ continueDisabled: false });
if (err.errcode === 'M_THREEPID_AUTH_FAILED') { if (err.errcode === 'M_THREEPID_AUTH_FAILED') {
Modal.createTrackedDialog("Email hasn't been verified yet", "", ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Your email address hasn't been verified yet"), title: _t("Your email address hasn't been verified yet"),
description: _t("Click the link in the email you received to verify " + description: _t("Click the link in the email you received to verify " +
"and then click continue again."), "and then click continue again."),
}); });
} else { } else {
logger.error("Unable to verify email address: ", err); logger.error("Unable to verify email address: ", err);
Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify email address."), title: _t("Unable to verify email address."),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });

View file

@ -76,7 +76,7 @@ export class ExistingPhoneNumber extends React.Component<IExistingPhoneNumberPro
return this.props.onRemoved(this.props.msisdn); return this.props.onRemoved(this.props.msisdn);
}).catch((err) => { }).catch((err) => {
logger.error("Unable to remove contact information: " + err); logger.error("Unable to remove contact information: " + err);
Modal.createTrackedDialog('Remove 3pid failed', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to remove contact information"), title: _t("Unable to remove contact information"),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
@ -185,7 +185,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
}).catch((err) => { }).catch((err) => {
logger.error("Unable to add phone number " + phoneNumber + " " + err); logger.error("Unable to add phone number " + phoneNumber + " " + err);
this.setState({ verifying: false, continueDisabled: false, addTask: null }); this.setState({ verifying: false, continueDisabled: false, addTask: null });
Modal.createTrackedDialog('Add Phone Number Error', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Error"), title: _t("Error"),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });
@ -222,7 +222,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
this.setState({ continueDisabled: false }); this.setState({ continueDisabled: false });
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') { if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
logger.error("Unable to verify phone number: " + err); logger.error("Unable to verify phone number: " + err);
Modal.createTrackedDialog('Unable to verify phone number', '', ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify phone number."), title: _t("Unable to verify phone number."),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
}); });

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