Analytics opt in for posthog (#6936)
* Add a new flag pseudonymousAnalyticsOptIn replacing analyticsOptIn, stored at account level, so people only need to opt in once. * Show a toast in login to users that have analyticsOptIn set but not yet pseudonymousAnalyticsOptIn prompting them confirm the new method is okay. Update the copy of the existing opt-in toast. Don't notify users that previously opted out. * Update the copy in settings * Add a new learn more dialog * Support a new config flag analyticsOwner which is used in these toasts when explaining which entity the data is sent to ("Help improve %(analyticsOwner)"). If unset, display brand. This allows deployments whose brand differs from the receiver of the analytics to explain the situation to their users (e.g. AcmeCorp badges their app, but explains the data is sent to Element, not them) * The new opt-in and flags are only used when posthog is configured; prior to that there are no changes to UX or tracking behaviour.
This commit is contained in:
parent
961fec9081
commit
5219b6be80
19 changed files with 512 additions and 150 deletions
|
@ -393,16 +393,26 @@ export class Analytics {
|
|||
];
|
||||
|
||||
// FIXME: Using an import will result in test failures
|
||||
const cookiePolicyUrl = SdkConfig.get().piwik?.policyUrl;
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
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">
|
||||
<div>{ _t('The information being sent to us to help make %(brand)s better includes:', {
|
||||
{ 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>{ _t(
|
||||
<td className="mx_AnalyticsModal_label">{ _t(
|
||||
customVariables[row[0]].expl,
|
||||
customVariables[row[0]].getTextVariables ?
|
||||
customVariables[row[0]].getTextVariables() :
|
||||
|
|
|
@ -585,12 +585,13 @@ async function doSetLoggedIn(
|
|||
|
||||
MatrixClientPeg.replaceUsingCreds(credentials);
|
||||
|
||||
PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId);
|
||||
|
||||
setSentryUser(credentials.userId);
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
if (PosthogAnalytics.instance.isEnabled()) {
|
||||
PosthogAnalytics.instance.startListeningToSettingsChanges();
|
||||
}
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
if (credentials.freshLogin && SettingsStore.getValue("feature_dehydration")) {
|
||||
// If we just logged in, try to rehydrate a device instead of using a
|
||||
// new device. If it succeeds, we'll get a new device ID, so make sure
|
||||
|
|
|
@ -17,11 +17,11 @@ limitations under the License.
|
|||
import posthog, { PostHog } from 'posthog-js';
|
||||
import PlatformPeg from './PlatformPeg';
|
||||
import SdkConfig from './SdkConfig';
|
||||
import SettingsStore from './settings/SettingsStore';
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
|
||||
/* Posthog analytics tracking.
|
||||
*
|
||||
|
@ -132,10 +132,10 @@ export class PosthogAnalytics {
|
|||
|
||||
private anonymity = Anonymity.Disabled;
|
||||
// set true during the constructor if posthog config is present, otherwise false
|
||||
private enabled = false;
|
||||
private readonly enabled: boolean = false;
|
||||
private static _instance = null;
|
||||
private platformSuperProperties = {};
|
||||
private static ANALYTICS_ID_EVENT_TYPE = "im.vector.web.analytics_id";
|
||||
private static ANALYTICS_EVENT_TYPE = "im.vector.analytics";
|
||||
|
||||
public static get instance(): PosthogAnalytics {
|
||||
if (!this._instance) {
|
||||
|
@ -197,29 +197,6 @@ export class PosthogAnalytics {
|
|||
return properties;
|
||||
};
|
||||
|
||||
private static getAnonymityFromSettings(): Anonymity {
|
||||
// determine the current anonymity level based on current user settings
|
||||
|
||||
// "Send anonymous usage data which helps us improve Element. This will use a cookie."
|
||||
const analyticsOptIn = SettingsStore.getValue("analyticsOptIn", null, true);
|
||||
|
||||
// (proposed wording) "Send pseudonymous usage data which helps us improve Element. This will use a cookie."
|
||||
//
|
||||
// TODO: Currently, this is only a labs flag, for testing purposes.
|
||||
const pseudonumousOptIn = SettingsStore.getValue("feature_pseudonymous_analytics_opt_in", null, true);
|
||||
|
||||
let anonymity;
|
||||
if (pseudonumousOptIn) {
|
||||
anonymity = Anonymity.Pseudonymous;
|
||||
} else if (analyticsOptIn) {
|
||||
anonymity = Anonymity.Anonymous;
|
||||
} else {
|
||||
anonymity = Anonymity.Disabled;
|
||||
}
|
||||
|
||||
return anonymity;
|
||||
}
|
||||
|
||||
private registerSuperProperties(properties: posthog.Properties) {
|
||||
if (this.enabled) {
|
||||
this.posthog.register(properties);
|
||||
|
@ -279,7 +256,7 @@ export class PosthogAnalytics {
|
|||
// Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows
|
||||
// different devices to send the same ID.
|
||||
try {
|
||||
const accountData = await client.getAccountDataFromServer(PosthogAnalytics.ANALYTICS_ID_EVENT_TYPE);
|
||||
const accountData = await client.getAccountDataFromServer(PosthogAnalytics.ANALYTICS_EVENT_TYPE);
|
||||
let analyticsID = accountData?.id;
|
||||
if (!analyticsID) {
|
||||
// Couldn't retrieve an analytics ID from user settings, so create one and set it on the server.
|
||||
|
@ -288,7 +265,8 @@ export class PosthogAnalytics {
|
|||
// until the next time account data is refreshed and this function is called (most likely on next
|
||||
// page load). This will happen pretty infrequently, so we can tolerate the possibility.
|
||||
analyticsID = analyticsIdGenerator();
|
||||
await client.setAccountData("im.vector.web.analytics_id", { id: analyticsID });
|
||||
await client.setAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE,
|
||||
Object.assign({ id: analyticsID }, accountData));
|
||||
}
|
||||
this.posthog.identify(analyticsID);
|
||||
} catch (e) {
|
||||
|
@ -307,7 +285,7 @@ export class PosthogAnalytics {
|
|||
if (this.enabled) {
|
||||
this.posthog.reset();
|
||||
}
|
||||
this.setAnonymity(Anonymity.Anonymous);
|
||||
this.setAnonymity(Anonymity.Disabled);
|
||||
}
|
||||
|
||||
public async trackPseudonymousEvent<E extends IPseudonymousEvent>(
|
||||
|
@ -351,12 +329,31 @@ export class PosthogAnalytics {
|
|||
this.registerSuperProperties(this.platformSuperProperties);
|
||||
}
|
||||
|
||||
public async updateAnonymityFromSettings(userId?: string): Promise<void> {
|
||||
public async updateAnonymityFromSettings(pseudonymousOptIn: boolean): Promise<void> {
|
||||
// Update this.anonymity based on the user's analytics opt-in settings
|
||||
// Identify the user (via hashed user ID) to posthog if anonymity is pseudonmyous
|
||||
this.setAnonymity(PosthogAnalytics.getAnonymityFromSettings());
|
||||
if (userId && this.getAnonymity() == Anonymity.Pseudonymous) {
|
||||
const anonymity = pseudonymousOptIn ? Anonymity.Pseudonymous : Anonymity.Disabled;
|
||||
this.setAnonymity(anonymity);
|
||||
if (anonymity === Anonymity.Pseudonymous) {
|
||||
await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getRandomAnalyticsId);
|
||||
}
|
||||
|
||||
if (anonymity !== Anonymity.Disabled) {
|
||||
await PosthogAnalytics.instance.updatePlatformSuperProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public startListeningToSettingsChanges(): void {
|
||||
// Listen to account data changes from sync so we can observe changes to relevant flags and update.
|
||||
// This is called -
|
||||
// * On page load, when the account data is first received by sync
|
||||
// * On login
|
||||
// * When another device changes account data
|
||||
// * When the user changes their preferences on this device
|
||||
// Note that for new accounts, pseudonymousAnalyticsOptIn won't be set, so updateAnonymityFromSettings
|
||||
// won't be called (i.e. this.anonymity will be left as the default, until the setting changes)
|
||||
SettingsStore.watchSetting("pseudonymousAnalyticsOptIn", null,
|
||||
(originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue) => {
|
||||
this.updateAnonymityFromSettings(!!newValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import React, { ComponentType, createRef } from 'react';
|
|||
import { createClient } from "matrix-js-sdk/src/matrix";
|
||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { sleep, defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
|
||||
import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
|
||||
|
||||
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
||||
import 'focus-visible';
|
||||
|
@ -59,8 +59,9 @@ import * as StorageManager from "../../utils/StorageManager";
|
|||
import type LoggedInViewType from "./LoggedInView";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import {
|
||||
showToast as showAnalyticsToast,
|
||||
hideToast as hideAnalyticsToast,
|
||||
showAnonymousAnalyticsOptInToast,
|
||||
showPseudonymousAnalyticsOptInToast,
|
||||
} from "../../toasts/AnalyticsToast";
|
||||
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
|
||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||
|
@ -382,13 +383,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
});
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("analyticsOptIn")) {
|
||||
if (SettingsStore.getValue("pseudonymousAnalyticsOptIn")) {
|
||||
Analytics.enable();
|
||||
}
|
||||
|
||||
PosthogAnalytics.instance.updateAnonymityFromSettings();
|
||||
PosthogAnalytics.instance.updatePlatformSuperProperties();
|
||||
|
||||
CountlyAnalytics.instance.enable(/* anonymous = */ true);
|
||||
|
||||
initSentry(SdkConfig.get()["sentry"]);
|
||||
|
@ -500,8 +498,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
} else {
|
||||
dis.dispatch({ action: "view_welcome_page" });
|
||||
}
|
||||
} else if (SettingsStore.getValue("analyticsOptIn")) {
|
||||
CountlyAnalytics.instance.enable(/* anonymous = */ false);
|
||||
}
|
||||
});
|
||||
// Note we don't catch errors from this: we catch everything within
|
||||
|
@ -816,10 +812,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
hideToSRUsers: false,
|
||||
});
|
||||
break;
|
||||
case 'accept_cookies':
|
||||
case Action.AnonymousAnalyticsAccept:
|
||||
hideAnalyticsToast();
|
||||
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, true);
|
||||
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
|
||||
hideAnalyticsToast();
|
||||
if (Analytics.canEnable()) {
|
||||
Analytics.enable();
|
||||
}
|
||||
|
@ -827,10 +823,18 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
CountlyAnalytics.instance.enable(/* anonymous = */ false);
|
||||
}
|
||||
break;
|
||||
case 'reject_cookies':
|
||||
case Action.AnonymousAnalyticsReject:
|
||||
hideAnalyticsToast();
|
||||
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, false);
|
||||
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
|
||||
break;
|
||||
case Action.PseudonymousAnalyticsAccept:
|
||||
hideAnalyticsToast();
|
||||
SettingsStore.setValue("pseudonymousAnalyticsOptIn", null, SettingLevel.ACCOUNT, true);
|
||||
break;
|
||||
case Action.PseudonymousAnalyticsReject:
|
||||
hideAnalyticsToast();
|
||||
SettingsStore.setValue("pseudonymousAnalyticsOptIn", null, SettingLevel.ACCOUNT, false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
@ -1323,13 +1327,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
StorageManager.tryPersistStorage();
|
||||
|
||||
// defer the following actions by 30 seconds to not throw them at the user immediately
|
||||
await sleep(30);
|
||||
if (SettingsStore.getValue("showCookieBar") &&
|
||||
(Analytics.canEnable() || CountlyAnalytics.instance.canEnable())
|
||||
) {
|
||||
showAnalyticsToast(this.props.config.piwik?.policyUrl);
|
||||
if (PosthogAnalytics.instance.isEnabled()) {
|
||||
this.initPosthogAnalyticsToast();
|
||||
} else if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable()) {
|
||||
if (SettingsStore.getValue("showCookieBar") &&
|
||||
(Analytics.canEnable() || CountlyAnalytics.instance.canEnable())
|
||||
) {
|
||||
showAnonymousAnalyticsOptInToast();
|
||||
}
|
||||
}
|
||||
|
||||
if (SdkConfig.get().mobileGuideToast) {
|
||||
// The toast contains further logic to detect mobile platforms,
|
||||
// check if it has been dismissed before, etc.
|
||||
|
@ -1337,6 +1344,34 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
private showPosthogToast(analyticsOptIn: boolean) {
|
||||
showPseudonymousAnalyticsOptInToast(analyticsOptIn);
|
||||
}
|
||||
|
||||
private initPosthogAnalyticsToast() {
|
||||
// Show the analytics toast if necessary
|
||||
if (SettingsStore.getValue("pseudonymousAnalyticsOptIn") === null) {
|
||||
this.showPosthogToast(SettingsStore.getValue("analyticsOptIn", null, true));
|
||||
}
|
||||
|
||||
// Listen to changes in settings and show the toast if appropriate - this is necessary because account
|
||||
// settings can still be changing at this point in app init (due to the initial sync being cached, then
|
||||
// subsequent syncs being received from the server)
|
||||
SettingsStore.watchSetting("pseudonymousAnalyticsOptIn", null,
|
||||
(originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue) => {
|
||||
if (newValue === null) {
|
||||
this.showPosthogToast(SettingsStore.getValue("analyticsOptIn", null, true));
|
||||
} else {
|
||||
// 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
|
||||
// loading the page); so hide the toast.
|
||||
// (this flipping usually happens before first render so the user won't notice it; anyway flicker
|
||||
// on/off is probably better than showing the toast again when the user already dismissed it)
|
||||
hideAnalyticsToast();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showScreenAfterLogin() {
|
||||
// If screenAfterLogin is set, use that, then null it so that a second login will
|
||||
// result in view_home_page, _user_settings or _room_directory
|
||||
|
|
109
src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx
Normal file
109
src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
Copyright 2021 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 BaseDialog from "./BaseDialog";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import React from "react";
|
||||
import Modal from "../../../Modal";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
|
||||
export enum ButtonClicked {
|
||||
Primary,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
onFinished?(buttonClicked?: ButtonClicked): void;
|
||||
analyticsOwner: string;
|
||||
privacyPolicyUrl?: string;
|
||||
primaryButton?: string;
|
||||
cancelButton?: string;
|
||||
hasCancel?: boolean;
|
||||
}
|
||||
|
||||
const AnalyticsLearnMoreDialog: React.FC<IProps> = ({
|
||||
onFinished,
|
||||
analyticsOwner,
|
||||
privacyPolicyUrl,
|
||||
primaryButton,
|
||||
cancelButton,
|
||||
hasCancel,
|
||||
}) => {
|
||||
const onPrimaryButtonClick = () => onFinished && onFinished(ButtonClicked.Primary);
|
||||
const onCancelButtonClick = () => onFinished && onFinished(ButtonClicked.Cancel);
|
||||
const privacyPolicyLink = privacyPolicyUrl ?
|
||||
<span>
|
||||
{
|
||||
_t("You can read all our terms <PrivacyPolicyUrl>here</PrivacyPolicyUrl>", {}, {
|
||||
"PrivacyPolicyUrl": (sub) => {
|
||||
return <a href={privacyPolicyUrl}
|
||||
rel="norefferer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{ sub }
|
||||
<span className="mx_AnalyticsPolicyLink" />
|
||||
</a>;
|
||||
},
|
||||
})
|
||||
}
|
||||
</span> : "";
|
||||
return <BaseDialog
|
||||
className="mx_AnalyticsLearnMoreDialog"
|
||||
contentId="mx_AnalyticsLearnMore"
|
||||
title={_t("Help improve %(analyticsOwner)s", { analyticsOwner })}
|
||||
onFinished={onFinished}
|
||||
>
|
||||
<div className="mx_Dialog_content">
|
||||
<div className="mx_AnalyticsLearnMore_image_holder" />
|
||||
<div className="mx_AnalyticsLearnMore_copy">
|
||||
{ _t("Help us identify issues and improve Element by sharing anonymous usage data. " +
|
||||
"To understand how people use multiple devices, we'll generate a random identifier, " +
|
||||
"shared by your devices.",
|
||||
) }
|
||||
</div>
|
||||
<ul className="mx_AnalyticsLearnMore_bullets">
|
||||
<li>{ _t("We <Bold>don't</Bold> record or profile any account data",
|
||||
{}, { "Bold": (sub) => <b>{ sub }</b> }) }</li>
|
||||
<li>{ _t("We <Bold>don't</Bold> share information with third parties",
|
||||
{}, { "Bold": (sub) => <b>{ sub }</b> }) }</li>
|
||||
<li>{ _t("You can turn this off anytime in settings") }</li>
|
||||
</ul>
|
||||
{ privacyPolicyLink }
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={primaryButton}
|
||||
cancelButton={cancelButton}
|
||||
onPrimaryButtonClick={onPrimaryButtonClick}
|
||||
onCancel={onCancelButtonClick}
|
||||
hasCancel={hasCancel}
|
||||
/>
|
||||
</BaseDialog>;
|
||||
};
|
||||
|
||||
export const showDialog = (props: Omit<IProps, "cookiePolicyUrl" | "analyticsOwner">): void => {
|
||||
const privacyPolicyUrl = SdkConfig.get().piwik?.policyUrl;
|
||||
const analyticsOwner = SdkConfig.get().analyticsOwner ?? SdkConfig.get().brand;
|
||||
Modal.createTrackedDialog(
|
||||
"Analytics Learn More",
|
||||
"",
|
||||
AnalyticsLearnMoreDialog,
|
||||
{ privacyPolicyUrl, analyticsOwner, ...props },
|
||||
"mx_AnalyticsLearnMoreDialog_wrapper",
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsLearnMoreDialog;
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import Analytics from "../../../../../Analytics";
|
||||
|
@ -32,7 +31,6 @@ import { UIFeature } from "../../../../../settings/UIFeature";
|
|||
import E2eAdvancedPanel, { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel";
|
||||
import CountlyAnalytics from "../../../../../CountlyAnalytics";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
|
||||
import { ActionPayload } from "../../../../../dispatcher/payloads";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import CryptographyPanel from "../../CryptographyPanel";
|
||||
|
@ -41,8 +39,10 @@ import SettingsFlag from "../../../elements/SettingsFlag";
|
|||
import CrossSigningPanel from "../../CrossSigningPanel";
|
||||
import EventIndexPanel from "../../EventIndexPanel";
|
||||
import InlineSpinner from "../../../elements/InlineSpinner";
|
||||
import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { showDialog as showAnalyticsLearnMoreDialog } from "../../../dialogs/AnalyticsLearnMoreDialog";
|
||||
|
||||
interface IIgnoredUserProps {
|
||||
userId: string;
|
||||
|
@ -118,7 +118,6 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
private updateAnalytics = (checked: boolean): void => {
|
||||
checked ? Analytics.enable() : Analytics.disable();
|
||||
CountlyAnalytics.instance.enable(/* anonymous = */ !checked);
|
||||
PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId());
|
||||
};
|
||||
|
||||
private onMyMembership = (room: Room, membership: string): void => {
|
||||
|
@ -272,8 +271,6 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
const secureBackup = (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Secure Backup") }</span>
|
||||
|
@ -312,24 +309,41 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
}
|
||||
|
||||
let privacySection;
|
||||
if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable()) {
|
||||
if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable() || PosthogAnalytics.instance.isEnabled()) {
|
||||
const onClickAnalyticsLearnMore = () => {
|
||||
if (PosthogAnalytics.instance.isEnabled()) {
|
||||
showAnalyticsLearnMoreDialog({
|
||||
primaryButton: _t("Okay"),
|
||||
hasCancel: false,
|
||||
});
|
||||
} else {
|
||||
Analytics.showDetailsModal();
|
||||
}
|
||||
};
|
||||
privacySection = <React.Fragment>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Privacy") }</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Analytics") }</span>
|
||||
<div className="mx_SettingsTab_subsectionText">
|
||||
{ _t(
|
||||
"%(brand)s collects anonymous analytics to allow us to improve the application.",
|
||||
{ brand },
|
||||
) }
|
||||
|
||||
{ _t("Privacy is important to us, so we don't collect any personal or " +
|
||||
"identifiable data for our analytics.") }
|
||||
<AccessibleButton className="mx_SettingsTab_linkBtn" onClick={Analytics.showDetailsModal}>
|
||||
{ _t("Learn more about how we use analytics.") }
|
||||
</AccessibleButton>
|
||||
<p>
|
||||
{ _t("Share anonymous data to help us identify issues. Nothing personal. " +
|
||||
"No third parties.") }
|
||||
</p>
|
||||
<p>
|
||||
<AccessibleButton className="mx_SettingsTab_linkBtn" onClick={onClickAnalyticsLearnMore}>
|
||||
{ _t("Learn more") }
|
||||
</AccessibleButton>
|
||||
</p>
|
||||
</div>
|
||||
<SettingsFlag name="analyticsOptIn" level={SettingLevel.DEVICE} onChange={this.updateAnalytics} />
|
||||
{
|
||||
PosthogAnalytics.instance.isEnabled() ?
|
||||
<SettingsFlag name="pseudonymousAnalyticsOptIn"
|
||||
level={SettingLevel.ACCOUNT}
|
||||
onChange={this.updateAnalytics} /> :
|
||||
<SettingsFlag name="analyticsOptIn"
|
||||
level={SettingLevel.DEVICE}
|
||||
onChange={this.updateAnalytics} />
|
||||
}
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
|
|
@ -203,4 +203,29 @@ export enum Action {
|
|||
* Fires when a user starts to edit event (e.g. up arrow in compositor)
|
||||
*/
|
||||
EditEvent = "edit_event",
|
||||
|
||||
/**
|
||||
* The user accepted pseudonymous analytics (i.e. posthog) from the toast
|
||||
* Payload: none
|
||||
*/
|
||||
PseudonymousAnalyticsAccept = "pseudonymous_analytics_accept",
|
||||
|
||||
/**
|
||||
* The user rejected pseudonymous analytics (i.e. posthog) from the toast
|
||||
* Payload: none
|
||||
*/
|
||||
PseudonymousAnalyticsReject = "pseudonymous_analytics_reject",
|
||||
|
||||
/**
|
||||
* The user accepted anonymous analytics (i.e. matomo, pre-posthog) from the toast
|
||||
* (this action and its handler can be removed once posthog is rolled out)
|
||||
* Payload: none
|
||||
*/
|
||||
AnonymousAnalyticsAccept = "anonymous_analytics_accept",
|
||||
|
||||
/**
|
||||
* The user rejected anonymous analytics (i.e. matomo, pre-posthog) from the toast
|
||||
* Payload: none
|
||||
*/
|
||||
AnonymousAnalyticsReject = "anonymous_analytics_reject"
|
||||
}
|
||||
|
|
|
@ -28,8 +28,9 @@
|
|||
"e.g. <CurrentPageURL>": "e.g. <CurrentPageURL>",
|
||||
"Your user agent": "Your user agent",
|
||||
"Your device resolution": "Your device resolution",
|
||||
"Our complete cookie policy can be found <CookiePolicyLink>here</CookiePolicyLink>.": "Our complete cookie policy can be found <CookiePolicyLink>here</CookiePolicyLink>.",
|
||||
"Analytics": "Analytics",
|
||||
"The information being sent to us to help make %(brand)s better includes:": "The information being sent to us to help make %(brand)s better includes:",
|
||||
"Some examples of the information being sent to us to help make %(brand)s better includes:": "Some examples of the information being sent to us to help make %(brand)s better includes:",
|
||||
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.",
|
||||
"Error": "Error",
|
||||
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
|
||||
|
@ -748,8 +749,14 @@
|
|||
"Topic: %(topic)s": "Topic: %(topic)s",
|
||||
"Error fetching file": "Error fetching file",
|
||||
"File Attached": "File Attached",
|
||||
"Help us improve %(brand)s": "Help us improve %(brand)s",
|
||||
"Enable": "Enable",
|
||||
"That's fine": "That's fine",
|
||||
"Stop": "Stop",
|
||||
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.",
|
||||
"Help improve %(analyticsOwner)s": "Help improve %(analyticsOwner)s",
|
||||
"You previously consented to share anonymous usage data with us. We're updating how that works.": "You previously consented to share anonymous usage data with us. We're updating how that works.",
|
||||
"Learn more": "Learn more",
|
||||
"Share anonymous data to help us identify issues. Nothing personal. No third parties. <LearnMoreLink>Learn More</LearnMoreLink>": "Share anonymous data to help us identify issues. Nothing personal. No third parties. <LearnMoreLink>Learn More</LearnMoreLink>",
|
||||
"Yes": "Yes",
|
||||
"No": "No",
|
||||
"You have unverified logins": "You have unverified logins",
|
||||
|
@ -759,7 +766,6 @@
|
|||
"Don't miss a reply": "Don't miss a reply",
|
||||
"Notifications": "Notifications",
|
||||
"Enable desktop notifications": "Enable desktop notifications",
|
||||
"Enable": "Enable",
|
||||
"Unknown caller": "Unknown caller",
|
||||
"Voice call": "Voice call",
|
||||
"Video call": "Video call",
|
||||
|
@ -845,7 +851,6 @@
|
|||
"Show message previews for reactions in DMs": "Show message previews for reactions in DMs",
|
||||
"Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms",
|
||||
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
|
||||
"Send pseudonymous analytics data": "Send pseudonymous analytics data",
|
||||
"Polls (under active development)": "Polls (under active development)",
|
||||
"Show info about bridges in room settings": "Show info about bridges in room settings",
|
||||
"New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)",
|
||||
|
@ -1447,10 +1452,9 @@
|
|||
"Message search": "Message search",
|
||||
"Cross-signing": "Cross-signing",
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
||||
"Okay": "Okay",
|
||||
"Privacy": "Privacy",
|
||||
"%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.",
|
||||
"Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.",
|
||||
"Learn more about how we use analytics.": "Learn more about how we use analytics.",
|
||||
"Share anonymous data to help us identify issues. Nothing personal. No third parties.": "Share anonymous data to help us identify issues. Nothing personal. No third parties.",
|
||||
"Where you're signed in": "Where you're signed in",
|
||||
"Manage your signed-in devices below. A device's name is visible to people you communicate with.": "Manage your signed-in devices below. A device's name is visible to people you communicate with.",
|
||||
"Sidebar": "Sidebar",
|
||||
|
@ -2287,6 +2291,11 @@
|
|||
"Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.",
|
||||
"Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.": "Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.",
|
||||
"Use an identity server to invite by email. Manage in <settings>Settings</settings>.": "Use an identity server to invite by email. Manage in <settings>Settings</settings>.",
|
||||
"You can read all our terms <PrivacyPolicyUrl>here</PrivacyPolicyUrl>": "You can read all our terms <PrivacyPolicyUrl>here</PrivacyPolicyUrl>",
|
||||
"Help us identify issues and improve Element by sharing anonymous usage data. To understand how people use multiple devices, we'll generate a random identifier, shared by your devices.": "Help us identify issues and improve Element by sharing anonymous usage data. To understand how people use multiple devices, we'll generate a random identifier, shared by your devices.",
|
||||
"We <Bold>don't</Bold> record or profile any account data": "We <Bold>don't</Bold> record or profile any account data",
|
||||
"We <Bold>don't</Bold> share information with third parties": "We <Bold>don't</Bold> share information with third parties",
|
||||
"You can turn this off anytime in settings": "You can turn this off anytime in settings",
|
||||
"The following users may not exist": "The following users may not exist",
|
||||
"Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?",
|
||||
"Invite anyway and never warn me again": "Invite anyway and never warn me again",
|
||||
|
@ -2455,7 +2464,6 @@
|
|||
"The export was cancelled successfully": "The export was cancelled successfully",
|
||||
"Your export was successful. Find it in your Downloads folder.": "Your export was successful. Find it in your Downloads folder.",
|
||||
"Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "Are you sure you want to stop exporting your data? If you do, you'll need to start over.",
|
||||
"Stop": "Stop",
|
||||
"Exporting your data": "Exporting your data",
|
||||
"Export Chat": "Export Chat",
|
||||
"Select from the options below to export chats from your timeline": "Select from the options below to export chats from your timeline",
|
||||
|
@ -2656,7 +2664,6 @@
|
|||
"We call the places where you can host your account 'homeservers'.": "We call the places where you can host your account 'homeservers'.",
|
||||
"Other homeserver": "Other homeserver",
|
||||
"Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.",
|
||||
"Learn more": "Learn more",
|
||||
"About homeservers": "About homeservers",
|
||||
"Reset event store?": "Reset event store?",
|
||||
"You most likely do not want to reset your event index store": "You most likely do not want to reset your event index store",
|
||||
|
|
|
@ -40,7 +40,6 @@ import { OrderedMultiController } from "./controllers/OrderedMultiController";
|
|||
import { Layout } from "./enums/Layout";
|
||||
import ReducedMotionController from './controllers/ReducedMotionController';
|
||||
import IncompatibleController from "./controllers/IncompatibleController";
|
||||
import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController';
|
||||
import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController';
|
||||
import { ImageSize } from "./enums/ImageSize";
|
||||
import { MetaSpace } from "../stores/spaces";
|
||||
|
@ -301,14 +300,6 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_pseudonymous_analytics_opt_in": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Analytics,
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
displayName: _td('Send pseudonymous analytics data'),
|
||||
default: false,
|
||||
controller: new PseudonymousAnalyticsController(),
|
||||
},
|
||||
"feature_polls": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Messaging,
|
||||
|
@ -621,6 +612,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
default: true,
|
||||
},
|
||||
"pseudonymousAnalyticsOptIn": {
|
||||
supportedLevels: [SettingLevel.ACCOUNT],
|
||||
displayName: _td('Send analytics data'),
|
||||
default: null,
|
||||
},
|
||||
"autocompleteDelay": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
default: 200,
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 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 SettingController from "./SettingController";
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
import { PosthogAnalytics } from "../../PosthogAnalytics";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
|
||||
export default class PseudonymousAnalyticsController extends SettingController {
|
||||
public onChange(level: SettingLevel, roomId: string, newValue: any) {
|
||||
PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId());
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs";
|
|||
const BREADCRUMBS_EVENT_TYPES = [BREADCRUMBS_LEGACY_EVENT_TYPE, BREADCRUMBS_EVENT_TYPE];
|
||||
const RECENT_EMOJI_EVENT_TYPE = "io.element.recent_emoji";
|
||||
const INTEG_PROVISIONING_EVENT_TYPE = "im.vector.setting.integration_provisioning";
|
||||
const ANALYTICS_EVENT_TYPE = "im.vector.analytics";
|
||||
|
||||
/**
|
||||
* Gets and sets settings at the "account" level for the current user.
|
||||
|
@ -56,7 +57,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
|||
}
|
||||
|
||||
this.watchers.notifyUpdate("urlPreviewsEnabled", null, SettingLevel.ACCOUNT, val);
|
||||
} else if (event.getType() === "im.vector.web.settings") {
|
||||
} else if (event.getType() === "im.vector.web.settings" || event.getType() === ANALYTICS_EVENT_TYPE) {
|
||||
// Figure out what changed and fire those updates
|
||||
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
||||
const changedSettings = objectKeyChanges<Record<string, any>>(prevContent, event.getContent());
|
||||
|
@ -127,6 +128,13 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
|||
return value;
|
||||
}
|
||||
|
||||
if (settingName === "pseudonymousAnalyticsOptIn") {
|
||||
const content = this.getSettings(ANALYTICS_EVENT_TYPE) || {};
|
||||
// Check to make sure that we actually got a boolean
|
||||
if (typeof(content[settingName]) !== "boolean") return null;
|
||||
return content[settingName];
|
||||
}
|
||||
|
||||
const settings = this.getSettings() || {};
|
||||
let preferredValue = settings[settingName];
|
||||
|
||||
|
@ -179,6 +187,14 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
|||
return;
|
||||
}
|
||||
|
||||
// Special case analytics
|
||||
if (settingName === "pseudonymousAnalyticsOptIn") {
|
||||
const content = this.getSettings(ANALYTICS_EVENT_TYPE) || {};
|
||||
content[settingName] = newValue;
|
||||
await MatrixClientPeg.get().setAccountData(ANALYTICS_EVENT_TYPE, content);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = this.getSettings() || {};
|
||||
content[settingName] = newValue;
|
||||
await MatrixClientPeg.get().setAccountData("im.vector.web.settings", content);
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
import { _t } from "../languageHandler";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
|
@ -23,16 +23,52 @@ import Analytics from "../Analytics";
|
|||
import AccessibleButton from "../components/views/elements/AccessibleButton";
|
||||
import GenericToast from "../components/views/toasts/GenericToast";
|
||||
import ToastStore from "../stores/ToastStore";
|
||||
import {
|
||||
ButtonClicked,
|
||||
showDialog as showAnalyticsLearnMoreDialog,
|
||||
} from "../components/views/dialogs/AnalyticsLearnMoreDialog";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
|
||||
const onAccept = () => {
|
||||
dis.dispatch({
|
||||
action: 'accept_cookies',
|
||||
action: Action.PseudonymousAnalyticsAccept,
|
||||
});
|
||||
};
|
||||
|
||||
const onReject = () => {
|
||||
dis.dispatch({
|
||||
action: "reject_cookies",
|
||||
action: Action.PseudonymousAnalyticsReject,
|
||||
});
|
||||
};
|
||||
|
||||
const onLearnMoreNoOptIn = () => {
|
||||
showAnalyticsLearnMoreDialog({
|
||||
onFinished: (buttonClicked?: ButtonClicked) => {
|
||||
if (buttonClicked === ButtonClicked.Primary) {
|
||||
// user clicked "Enable"
|
||||
onAccept();
|
||||
}
|
||||
// otherwise, the user either clicked "Cancel", or closed the dialog without making a choice,
|
||||
// leave the toast open
|
||||
},
|
||||
primaryButton: _t("Enable"),
|
||||
});
|
||||
};
|
||||
|
||||
const onLearnMorePreviouslyOptedIn = () => {
|
||||
showAnalyticsLearnMoreDialog({
|
||||
onFinished: (buttonClicked?: ButtonClicked) => {
|
||||
if (buttonClicked === ButtonClicked.Primary) {
|
||||
// user clicked "That's fine"
|
||||
onAccept();
|
||||
} else if (buttonClicked === ButtonClicked.Cancel) {
|
||||
// user clicked "Stop"
|
||||
onReject();
|
||||
}
|
||||
// otherwise, the user closed the dialog without making a choice, leave the toast open
|
||||
},
|
||||
primaryButton: _t("That's fine"),
|
||||
cancelButton: _t("Stop"),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -42,39 +78,89 @@ const onUsageDataClicked = () => {
|
|||
|
||||
const TOAST_KEY = "analytics";
|
||||
|
||||
export const showToast = (policyUrl?: string) => {
|
||||
const getAnonymousDescription = (): ReactNode => {
|
||||
// get toast description for anonymous tracking (the previous scheme pre-posthog)
|
||||
const brand = SdkConfig.get().brand;
|
||||
const cookiePolicyUrl = SdkConfig.get().piwik?.policyUrl;
|
||||
return _t(
|
||||
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. " +
|
||||
"This will use a <PolicyLink>cookie</PolicyLink>.",
|
||||
{
|
||||
brand,
|
||||
},
|
||||
{
|
||||
"UsageDataLink": (sub) => (
|
||||
<AccessibleButton kind="link" onClick={onUsageDataClicked}>{ sub }</AccessibleButton>
|
||||
),
|
||||
"PolicyLink": (sub) => cookiePolicyUrl ? (
|
||||
<a target="_blank" href={cookiePolicyUrl}>{ sub }</a>
|
||||
) : sub,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const showToast = (props: Omit<React.ComponentProps<typeof GenericToast>, "toastKey">) => {
|
||||
const analyticsOwner = SdkConfig.get().analyticsOwner ?? SdkConfig.get().brand;
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: TOAST_KEY,
|
||||
title: _t("Help us improve %(brand)s", { brand }),
|
||||
props: {
|
||||
description: _t(
|
||||
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. " +
|
||||
"This will use a <PolicyLink>cookie</PolicyLink>.",
|
||||
{
|
||||
brand,
|
||||
},
|
||||
{
|
||||
"UsageDataLink": (sub) => (
|
||||
<AccessibleButton kind="link" onClick={onUsageDataClicked}>{ sub }</AccessibleButton>
|
||||
),
|
||||
// XXX: We need to link to the page that explains our cookies
|
||||
"PolicyLink": (sub) => policyUrl ? (
|
||||
<a target="_blank" href={policyUrl}>{ sub }</a>
|
||||
) : sub,
|
||||
},
|
||||
),
|
||||
acceptLabel: _t("Yes"),
|
||||
onAccept,
|
||||
rejectLabel: _t("No"),
|
||||
onReject,
|
||||
},
|
||||
title: _t("Help improve %(analyticsOwner)s", { analyticsOwner }),
|
||||
props,
|
||||
component: GenericToast,
|
||||
className: "mx_AnalyticsToast",
|
||||
priority: 10,
|
||||
});
|
||||
};
|
||||
|
||||
export const showPseudonymousAnalyticsOptInToast = (analyticsOptIn: boolean): void => {
|
||||
let props;
|
||||
if (analyticsOptIn) {
|
||||
// The user previously opted into our old analytics system - let them know things have changed and ask
|
||||
// them to opt in again.
|
||||
props = {
|
||||
description: _t(
|
||||
"You previously consented to share anonymous usage data with us. We're updating how that works."),
|
||||
acceptLabel: _t("That's fine"),
|
||||
onAccept,
|
||||
rejectLabel: _t("Learn more"),
|
||||
onReject: onLearnMorePreviouslyOptedIn,
|
||||
};
|
||||
} else if (analyticsOptIn === null || analyticsOptIn === undefined) {
|
||||
// The user had no analytics setting previously set, so we just need to prompt to opt-in, rather than
|
||||
// explaining any change.
|
||||
const learnMoreLink = (sub) => (
|
||||
<AccessibleButton kind="link" onClick={onLearnMoreNoOptIn}>{ sub }</AccessibleButton>
|
||||
);
|
||||
props = {
|
||||
description: _t(
|
||||
"Share anonymous data to help us identify issues. Nothing personal. No third parties. " +
|
||||
"<LearnMoreLink>Learn More</LearnMoreLink>", {}, { "LearnMoreLink": learnMoreLink }),
|
||||
acceptLabel: _t("Yes"),
|
||||
onAccept,
|
||||
rejectLabel: _t("No"),
|
||||
onReject,
|
||||
};
|
||||
} else { // false
|
||||
// The user previously opted out of analytics, don't ask again
|
||||
return;
|
||||
}
|
||||
showToast(props);
|
||||
};
|
||||
|
||||
export const showAnonymousAnalyticsOptInToast = (): void => {
|
||||
const props = {
|
||||
description: getAnonymousDescription(),
|
||||
acceptLabel: _t("Yes"),
|
||||
onAccept: () => dis.dispatch({
|
||||
action: Action.AnonymousAnalyticsAccept,
|
||||
}),
|
||||
rejectLabel: _t("No"),
|
||||
onReject: () => dis.dispatch({
|
||||
action: Action.AnonymousAnalyticsReject,
|
||||
}),
|
||||
};
|
||||
showToast(props);
|
||||
};
|
||||
|
||||
export const hideToast = () => {
|
||||
ToastStore.sharedInstance().dismissToast(TOAST_KEY);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue