From 4cfa0e781e4d165e1dd9b020e1f1b286434a2a66 Mon Sep 17 00:00:00 2001 From: Lynx <141365347+iLynxcat@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:00:38 -0700 Subject: [PATCH 1/3] Add no telemetry option and improve analytics code clarity (#2740) * 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 * eslint: disable no-empty-object-type for PlausibleOptions the reason is commented --- apps/desktop/vite.config.ts | 1 + apps/mobile/src/App.tsx | 4 +- .../components/context/OnboardingContext.tsx | 11 +- .../mobile/src/screens/onboarding/Privacy.tsx | 26 +++-- apps/web/cypress/e2e/1-onboarding.spec.cy.ts | 14 ++- interface/ErrorFallback.tsx | 31 +++--- interface/app/$libraryId/Layout/index.tsx | 14 ++- interface/app/$libraryId/settings/Setting.tsx | 6 +- .../$libraryId/settings/client/privacy.tsx | 35 ++++-- interface/app/onboarding/context.tsx | 25 +++-- interface/locales/ar/common.json | 13 ++- interface/locales/be/common.json | 13 ++- interface/locales/cs/common.json | 13 ++- interface/locales/de/common.json | 13 ++- interface/locales/en/common.json | 17 +-- interface/locales/es/common.json | 13 ++- interface/locales/fr/common.json | 13 ++- interface/locales/it/common.json | 13 ++- interface/locales/ja/common.json | 13 ++- interface/locales/nl/common.json | 13 ++- interface/locales/ru/common.json | 13 ++- interface/locales/tr/common.json | 13 ++- interface/locales/zh-CN/common.json | 13 ++- interface/locales/zh-TW/common.json | 13 ++- packages/client/src/hooks/usePlausible.tsx | 73 ++++++------- .../src/solid/createPersistedMutable.ts | 29 ++++- packages/client/src/stores/explorerLayout.ts | 27 ++++- packages/client/src/stores/telemetryState.ts | 102 ++++++++++++++++++ packages/client/src/stores/telemetryState.tsx | 31 ------ 29 files changed, 420 insertions(+), 195 deletions(-) create mode 100644 packages/client/src/stores/telemetryState.ts delete mode 100644 packages/client/src/stores/telemetryState.tsx diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index f80274e06..99af0f343 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -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', diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index 3ec7b8c44..f2e59d269 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -20,7 +20,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useSnapshot } from 'valtio'; import { ClientContextProvider, - initPlausible, + configureAnalyticsProperties, LibraryContextProvider, P2PContextProvider, RspcProvider, @@ -63,7 +63,7 @@ function AppNavigation() { useEffect(() => { if (buildInfo?.data) { - initPlausible({ platformType: 'mobile', buildInfo: buildInfo.data }); + configureAnalyticsProperties({ platformType: 'mobile', buildInfo: buildInfo.data }); } }, [buildInfo]); diff --git a/apps/mobile/src/components/context/OnboardingContext.tsx b/apps/mobile/src/components/context/OnboardingContext.tsx index 31e6259b7..ff7816e14 100644 --- a/apps/mobile/src/components/context/OnboardingContext.tsx +++ b/apps/mobile/src/components/context/OnboardingContext.tsx @@ -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 = { @@ -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) @@ -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 @@ -93,7 +94,7 @@ const useFormState = () => { new Promise((res) => setTimeout(res, 500)) ]); - if (telemetryState.shareFullTelemetry) { + if (telemetryState.telemetryLevelPreference === 'full') { submitPlausibleEvent({ event: { type: 'libraryCreate' } }); } diff --git a/apps/mobile/src/screens/onboarding/Privacy.tsx b/apps/mobile/src/screens/onboarding/Privacy.tsx index de6623d0b..a14beba5f 100644 --- a/apps/mobile/src/screens/onboarding/Privacy.tsx +++ b/apps/mobile/src/screens/onboarding/Privacy.tsx @@ -59,22 +59,26 @@ const PrivacyScreen = () => { control={form.control} render={({ field: { onChange, value } }) => ( <> - onChange('share-telemetry')}> + onChange('full')}> - onChange('minimal-telemetry')} - > + onChange('minimal')}> + + onChange('none')}> + diff --git a/apps/web/cypress/e2e/1-onboarding.spec.cy.ts b/apps/web/cypress/e2e/1-onboarding.spec.cy.ts index 8066ba81e..e0fcdf784 100644 --- a/apps/web/cypress/e2e/1-onboarding.spec.cy.ts +++ b/apps/web/cypress/e2e/1-onboarding.spec.cy.ts @@ -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(); diff --git a/interface/ErrorFallback.tsx b/interface/ErrorFallback.tsx index 237db090c..55bab5131 100644 --- a/interface/ErrorFallback.tsx +++ b/interface/ErrorFallback.tsx @@ -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'; @@ -68,6 +68,7 @@ export function ErrorPage({ }) { useTheme(); const debug = useDebugState(); + const { telemetryLevelPreference } = useTelemetryState(); const os = useOperatingSystem(); const platform = usePlatform(); const isMacOS = os === 'macOS'; @@ -130,19 +131,21 @@ export function ErrorPage({ {t('reload')} )} - + {telemetryLevelPreference !== 'none' && ( + + )} {platform.openLogsDir && (