Skip to content

Commit

Permalink
Add no telemetry option and improve analytics code clarity (#2740)
Browse files Browse the repository at this point in the history
* Refactor analytics configuration to use `configureAnalyticsProperties` instead of `initPlausible` for improved clarity

* format & add comment explaining analytics ping code
if any people reviewing source for their own verification of its purpose, this should make it much clearer what this does

* refactor Setting to use named function so its references can be source-peeked

* add basic support for 'none' telemetry option
changing the option in the Select dropdown breaks things right now, gotta figure out why

* fix usePlausibleEvent sending old telemetry level preference property to submitPlausibleEvent

* Fall back fo 'unknown' instead of '0.1.0' for app version stats

* Better explain the purpose of more things in submitPlausibleEvent

* organize and clean up internationalization file keys

* fix lowercase connect/connecting key names in code

* add no-telemetry option to onboarding

* Only display error report button if telemetry is enabled

* Add explainer to Sentry plugin for Vite

* Fix onboarding selection in both interface and mobile

* Add more explanation and checks to usePlausible to never send data unless allowed

* add support for onLoad to transform data from persisted mutable stores

* update some analytics explainers on mobile

* add telemetry state migration

* add migration for telemetry state from explorer layout key in persisted store

* fix cypress test for onboarding privacy page

* remove some accidentally included console.log statements

* Add localized analytics preference names

* Use localized telemetry preference names in Settings

* use cleaner text wrapping in settings

* Update the telemetry setting title

* fix telemetry typo

* update mobile no-analytics text

* remove telemetry override option
  • Loading branch information
iLynxcat authored Oct 6, 2024
1 parent 7cd0556 commit 09c8f52
Show file tree
Hide file tree
Showing 29 changed files with 419 additions and 195 deletions.
1 change: 1 addition & 0 deletions apps/desktop/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default defineConfig(({ mode }) => {
plugins: [
devtoolsPlugin,
process.env.SENTRY_AUTH_TOKEN &&
// All this plugin does is give Sentry access to source maps and release data for errors that users *choose* to report
sentryVitePlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: 'spacedriveapp',
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
import { useSnapshot } from 'valtio';
import {
ClientContextProvider,
initPlausible,
configureAnalyticsProperties,
LibraryContextProvider,
P2PContextProvider,
RspcProvider,
Expand Down Expand Up @@ -63,7 +63,7 @@ function AppNavigation() {

useEffect(() => {
if (buildInfo?.data) {
initPlausible({ platformType: 'mobile', buildInfo: buildInfo.data });
configureAnalyticsProperties({ platformType: 'mobile', buildInfo: buildInfo.data });
}
}, [buildInfo]);

Expand Down
11 changes: 6 additions & 5 deletions apps/mobile/src/components/context/OnboardingContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ export const useContextValue = () => {
};

export const shareTelemetrySchema = z.union([
z.literal('share-telemetry'),
z.literal('minimal-telemetry')
z.literal('full'),
z.literal('minimal'),
z.literal('none')
]);

const schemas = {
Expand All @@ -58,7 +59,7 @@ const useFormState = () => {
defaultValues: {
NewLibrary: obStore.data?.['new-library'] ?? undefined,
Privacy: obStore.data?.privacy ?? {
shareTelemetry: 'share-telemetry'
shareTelemetry: 'full'
}
},
onData: (data) => (onboardingStore.data = data)
Expand All @@ -81,7 +82,7 @@ const useFormState = () => {

// opted to place this here as users could change their mind before library creation/onboarding finalization
// it feels more fitting to configure it here (once)
telemetryState.shareFullTelemetry = data.Privacy.shareTelemetry === 'share-telemetry';
telemetryState.telemetryLevelPreference = data.Privacy.shareTelemetry;

try {
// show creation screen for a bit for smoothness
Expand All @@ -93,7 +94,7 @@ const useFormState = () => {
new Promise((res) => setTimeout(res, 500))
]);

if (telemetryState.shareFullTelemetry) {
if (telemetryState.telemetryLevelPreference === 'full') {
submitPlausibleEvent({ event: { type: 'libraryCreate' } });
}

Expand Down
26 changes: 15 additions & 11 deletions apps/mobile/src/screens/onboarding/Privacy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,26 @@ const PrivacyScreen = () => {
control={form.control}
render={({ field: { onChange, value } }) => (
<>
<Pressable onPress={() => onChange('share-telemetry')}>
<Pressable onPress={() => onChange('full')}>
<RadioButton
title="Share anonymous usage"
description="Share completely anonymous telemetry data to help the developers improve the app"
isSelected={value === 'share-telemetry'}
title="Share anonymous usage data"
description="This give us a completely anonymous picture of how you use Spacedrive."
isSelected={value === 'full'}
style={tw`mb-3 mt-4`}
/>
</Pressable>
<Pressable
testID="share-minimal"
onPress={() => onChange('minimal-telemetry')}
>
<Pressable testID="share-minimal" onPress={() => onChange('minimal')}>
<RadioButton
title="Share the bare minimum"
description="Only share that I am an active user of Spacedrive and a few technical bits"
isSelected={value === 'minimal-telemetry'}
title="Share minimal data"
description="This just tells us how many people use Spacedrive and device/version details."
isSelected={value === 'minimal'}
/>
</Pressable>
<Pressable testID="share-none" onPress={() => onChange('none')}>
<RadioButton
title="Don't share anything"
description="Sends absolutely no analytics data from the Spacedrive app."
isSelected={value === 'none'}
/>
</Pressable>
</>
Expand Down
14 changes: 10 additions & 4 deletions apps/web/cypress/e2e/1-onboarding.spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,18 @@ describe('Onboarding', () => {
cy.get('h2').should('contain', 'Your Privacy');

// Check we have all privacy options
cy.get('label').contains("Don't share anything").click();
cy.get('#radiofull').should('have.attr', 'data-state', 'unchecked');
cy.get('#radiominimal').should('have.attr', 'data-state', 'unchecked');
cy.get('#radionone').should('have.attr', 'data-state', 'checked');
cy.get('label').contains('Share the bare minimum').click();
cy.get('#radiominimal-telemetry').should('have.attr', 'data-state', 'checked');
cy.get('#radioshare-telemetry').should('have.attr', 'data-state', 'unchecked');
cy.get('#radiofull').should('have.attr', 'data-state', 'unchecked');
cy.get('#radiominimal').should('have.attr', 'data-state', 'checked');
cy.get('#radionone').should('have.attr', 'data-state', 'unchecked');
cy.get('label').contains('Share anonymous usage').click();
cy.get('#radioshare-telemetry').should('have.attr', 'data-state', 'checked');
cy.get('#radiominimal-telemetry').should('have.attr', 'data-state', 'unchecked');
cy.get('#radiofull').should('have.attr', 'data-state', 'checked');
cy.get('#radiominimal').should('have.attr', 'data-state', 'unchecked');
cy.get('#radionone').should('have.attr', 'data-state', 'unchecked');

// Check More info button exists and point to the valid pravacy policy
cy.get('button').contains('More info').click();
Expand Down
31 changes: 17 additions & 14 deletions interface/ErrorFallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
FallbackProps
} from 'react-error-boundary';
import { useRouteError } from 'react-router';
import { useDebugState } from '@sd/client';
import { useDebugState, useTelemetryState } from '@sd/client';
import { Button, Dialogs } from '@sd/ui';

import { showAlertDialog } from './components';
Expand Down Expand Up @@ -68,6 +68,7 @@ export function ErrorPage({
}) {
useTheme();
const debug = useDebugState();
const { telemetryLevelPreference } = useTelemetryState();
const os = useOperatingSystem();
const platform = usePlatform();
const isMacOS = os === 'macOS';
Expand Down Expand Up @@ -130,19 +131,21 @@ export function ErrorPage({
{t('reload')}
</Button>
)}
<Button
variant="gray"
className="mt-2"
onClick={() =>
sendReportBtn
? sendReportBtn()
: sentryBrowserLazy.then(({ captureException }) =>
captureException(message)
)
}
>
{t('send_report')}
</Button>
{telemetryLevelPreference !== 'none' && (
<Button
variant="gray"
className="mt-2"
onClick={() =>
sendReportBtn
? sendReportBtn()
: sentryBrowserLazy.then(({ captureException }) =>
captureException(message)
)
}
>
{t('send_report')}
</Button>
)}
{platform.openLogsDir && (
<Button variant="gray" className="mt-2" onClick={platform.openLogsDir}>
{t('open_logs')}
Expand Down
14 changes: 9 additions & 5 deletions interface/app/$libraryId/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Suspense, useEffect, useMemo, useRef } from 'react';
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
import {
ClientContextProvider,
initPlausible,
configureAnalyticsProperties,
LibraryContextProvider,
useBridgeQuery,
useClientContext,
Expand Down Expand Up @@ -144,16 +144,20 @@ function usePlausible() {
const plausibleEvent = usePlausibleEvent();

useEffect(() => {
initPlausible({
configureAnalyticsProperties({
buildInfo,
platformType: platform === 'tauri' ? 'desktop' : 'web'
});
}, [platform, buildInfo]);

useEffect(() => {
const interval = setInterval(() => {
plausibleEvent({ event: { type: 'ping' } });
}, 600 * 1000); // 10 minutes
const interval = setInterval(
() => {
// ping every 10 minutes -- this just tells us that Spacedrive is running and helps us gauge the amount of active users we have.
plausibleEvent({ event: { type: 'ping' } });
},
10 * 60 * 1_000
);

return () => clearInterval(interval);
}, [plausibleEvent]);
Expand Down
6 changes: 3 additions & 3 deletions interface/app/$libraryId/settings/Setting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface Props {
infoUrl?: string;
}

export default ({ mini, registerName, ...props }: PropsWithChildren<Props>) => {
export default function Setting({ mini, registerName, ...props }: PropsWithChildren<Props>) {
const platform = usePlatform();

if (typeof props.description === 'string')
Expand All @@ -38,7 +38,7 @@ export default ({ mini, registerName, ...props }: PropsWithChildren<Props>) => {
</Tooltip>
)}
</div>
<div className="w-[85%]">{props.description}</div>
<div className="text-balance">{props.description}</div>
{!mini && props.children}
</div>
{mini && props.children}
Expand All @@ -48,4 +48,4 @@ export default ({ mini, registerName, ...props }: PropsWithChildren<Props>) => {
) : null}
</>
);
};
}
35 changes: 26 additions & 9 deletions interface/app/$libraryId/settings/client/privacy.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
import { telemetryState, useTelemetryState } from '@sd/client';
import { Switch } from '@sd/ui';
import { TelemetryLevelPreference, telemetryState, useTelemetryState } from '@sd/client';
import { Select, SelectOption } from '@sd/ui';
import i18n from '~/app/I18n';
import { useLocale } from '~/hooks';

import { Heading } from '../Layout';
import Setting from '../Setting';

export const Component = () => {
const fullTelemetry = useTelemetryState().shareFullTelemetry;
const telemetryPreferenceOptions = [
{ value: 'full', label: i18n.t('telemetry_share_anonymous_short') },
{ value: 'minimal', label: i18n.t('telemetry_share_minimal_short') },
{ value: 'none', label: i18n.t('telemetry_share_none_short') }
] satisfies { value: TelemetryLevelPreference; label: string }[];

export const Component = () => {
const { t } = useLocale();

const { telemetryLevelPreference } = useTelemetryState();

return (
<>
<Heading title={t('privacy')} description="" />

<Setting
mini
toolTipLabel={t('learn_more_about_telemetry')}
infoUrl="https://www.spacedrive.com/docs/product/resources/privacy"
title={t('telemetry_title')}
description={t('telemetry_description')}
>
<Switch
checked={fullTelemetry}
onClick={() => (telemetryState.shareFullTelemetry = !fullTelemetry)}
size="md"
/>
<Select
value={telemetryLevelPreference}
onChange={(newValue) => {
// add "dateFormat" key to localStorage and set it as default date format
telemetryState.telemetryLevelPreference = newValue;
}}
containerClassName="flex h-[30px] gap-2"
>
{telemetryPreferenceOptions.map((format, index) => (
<SelectOption key={index} value={format.value}>
{format.label}
</SelectOption>
))}
</Select>
</Setting>
</>
);
Expand Down
25 changes: 15 additions & 10 deletions interface/app/onboarding/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,21 @@ export const useContextValue = () => {
};

export const shareTelemetry = RadioGroupField.options([
z.literal('share-telemetry'),
z.literal('minimal-telemetry')
z.literal('full'),
z.literal('minimal'),
z.literal('none')
]).details({
'share-telemetry': {
heading: i18n.t('share_anonymous_usage'),
description: i18n.t('share_anonymous_usage_description')
full: {
heading: i18n.t('telemetry_share_anonymous'),
description: i18n.t('telemetry_share_anonymous_description')
},
'minimal-telemetry': {
heading: i18n.t('share_bare_minimum'),
description: i18n.t('share_bare_minimum_description')
minimal: {
heading: i18n.t('telemetry_share_minimal'),
description: i18n.t('telemetry_share_minimal_description')
},
none: {
heading: i18n.t('telemetry_share_none'),
description: i18n.t('telemetry_share_none_description')
}
});

Expand Down Expand Up @@ -104,7 +109,7 @@ const useFormState = () => {

// opted to place this here as users could change their mind before library creation/onboarding finalization
// it feels more fitting to configure it here (once)
telemetryState.shareFullTelemetry = data.privacy.shareTelemetry === 'share-telemetry';
telemetryState.telemetryLevelPreference = data.privacy.shareTelemetry;

try {
// show creation screen for a bit for smoothness
Expand All @@ -119,7 +124,7 @@ const useFormState = () => {

if (platform.refreshMenuBar) platform.refreshMenuBar();

if (telemetryState.shareFullTelemetry) {
if (telemetryState.telemetryLevelPreference === 'full') {
submitPlausibleEvent({ event: { type: 'libraryCreate' } });
}

Expand Down
13 changes: 9 additions & 4 deletions interface/locales/ar/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -629,10 +629,6 @@
"settings": "الإعدادات",
"setup": "إعداد",
"share": "مشاركة",
"share_anonymous_usage": "مشاركة الاستخدام المجهول",
"share_anonymous_usage_description": "مشاركة بيانات التلميح المجهولة تمامًا لمساعدة المطورين في تحسين التطبيق",
"share_bare_minimum": "مشاركة الحد الأدنى",
"share_bare_minimum_description": "مشاركة أنني مستخدم نشط لـ Spacedrive وبعض البيانات الفنية فقط",
"sharing": "المشاركة",
"sharing_description": "إدارة من لديه وصول إلى مكتباتك.",
"show_details": "إظهار التفاصيل",
Expand Down Expand Up @@ -705,6 +701,15 @@
"task_two": "tasks",
"task_zero": "task",
"telemetry_description": "قم بتبديل التشغيل لتزويد المطورين ببيانات مفصلة حول الاستخدام وبيانات التلميح لتحسين التطبيق. قم بتبديل التشغيل لإرسال بيانات أساسية فقط: حالة نشاطك ، إصدار التطبيق ، إصدار النواة ، والمنصة (مثل الجوال أو الويب أو سطح المكتب).",
"telemetry_share_anonymous": "مشاركة الاستخدام المجهول",
"telemetry_share_anonymous_description": "مشاركة بيانات التلميح المجهولة تمامًا لمساعدة المطورين في تحسين التطبيق",
"telemetry_share_anonymous_short": "استخدام مجهول",
"telemetry_share_minimal": "مشاركة الحد الأدنى",
"telemetry_share_minimal_description": "مشاركة أنني مستخدم نشط لـ Spacedrive وبعض البيانات الفنية فقط",
"telemetry_share_minimal_short": "الحد الأدنى",
"telemetry_share_none": "لا تشارك",
"telemetry_share_none_description": "لا يرسل أي بيانات تحليلية على الإطلاق من تطبيق Spacedrive.",
"telemetry_share_none_short": "لا أحد",
"telemetry_title": "مشاركة بيانات التلميح والاستخدام الإضافية",
"temperature": "درجة الحرارة",
"text": "Text",
Expand Down
13 changes: 9 additions & 4 deletions interface/locales/be/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -685,10 +685,6 @@
"settings": "Налады",
"setup": "Наладзіць",
"share": "Падзяліцца",
"share_anonymous_usage": "Падзяляцца ананімнымі дадзенымі пра выкарыстанне",
"share_anonymous_usage_description": "Падзяляйцеся цалкам ананімнымі тэлеметрычнымі дадзенымі, каб дапамагчы распрацоўнікам палепшыць дадатак",
"share_bare_minimum": "Падзяляцца толькі патрэбным",
"share_bare_minimum_description": "Падзяляцца толькі тым, што з'яўляецеся актыўным карыстачом Spacedrive і некалькімі тэхнічнымі момантамі.",
"sharing": "Супольнае выкарыстанне",
"sharing_description": "Кіруйце тым, хто мае доступ да вашых бібліятэк.",
"show_details": "Паказаць падрабязнасці",
Expand Down Expand Up @@ -762,6 +758,15 @@
"task_many": "задач",
"task_one": "задача",
"telemetry_description": "Уключыце, каб падаць распрацоўнікам дэталёвыя дадзеныя пра выкарыстанне і тэлеметрыю для паляпшэння дадатку. Выключыце, каб адпраўляць толькі асноўныя дадзеныя: статус актыўнасці, версію дадатку, версію ядра і платформу (прыкладам, мабільную, ўэб- ці настольную).",
"telemetry_share_anonymous": "Падзяляцца ананімнымі дадзенымі пра выкарыстанне",
"telemetry_share_anonymous_description": "Падзяляйцеся цалкам ананімнымі тэлеметрычнымі дадзенымі, каб дапамагчы распрацоўнікам палепшыць дадатак",
"telemetry_share_anonymous_short": "Ананімнае выкарыстанне",
"telemetry_share_minimal": "Падзяляцца толькі патрэбным",
"telemetry_share_minimal_description": "Падзяляцца толькі тым, што з'яўляецеся актыўным карыстачом Spacedrive і некалькімі тэхнічнымі момантамі.",
"telemetry_share_minimal_short": "Мінімальны",
"telemetry_share_none": "Не дзяліцеся",
"telemetry_share_none_description": "Не адпраўляе абсалютна ніякіх аналітычных даных з праграмы Spacedrive.",
"telemetry_share_none_short": "Няма",
"telemetry_title": "Падаванне дадатковай тэлеметрыi і дадзеных пра выкарыстанне",
"temperature": "Тэмпература",
"text": "Тэкст",
Expand Down
Loading

0 comments on commit 09c8f52

Please sign in to comment.