diff --git a/src/components/Settings/SettingsDialog.tsx b/src/components/Settings/SettingsDialog.tsx index 76189eca..156fb228 100644 --- a/src/components/Settings/SettingsDialog.tsx +++ b/src/components/Settings/SettingsDialog.tsx @@ -10,7 +10,7 @@ import { ProxySettings } from './ProxySettings' import { AppSettingsSchema } from '@/schemas/settings' import { RecorderSettings } from './RecorderSettings' import { AppSettings } from '@/types/settings' -import { UsageReportSettings } from './UsageReportSettings' +import { TelemetrySettings } from './TelemetrySettings' import { ButtonWithTooltip } from '../ButtonWithTooltip' import { AppearanceSettings } from './AppearanceSettings' import { LogsSettings } from './LogsSettings' @@ -25,9 +25,9 @@ const tabs = [ { label: 'Proxy', value: 'proxy', component: ProxySettings }, { label: 'Recorder', value: 'recorder', component: RecorderSettings }, { - label: 'Usage collection', + label: 'Telemetry', value: 'usageReport', - component: UsageReportSettings, + component: TelemetrySettings, }, { label: 'Appearance', diff --git a/src/components/Settings/TelemetrySettings.tsx b/src/components/Settings/TelemetrySettings.tsx new file mode 100644 index 00000000..45c60a2c --- /dev/null +++ b/src/components/Settings/TelemetrySettings.tsx @@ -0,0 +1,61 @@ +import { Flex, Text, Checkbox, Link } from '@radix-ui/themes' +import { Controller, useFormContext } from 'react-hook-form' +import { SettingsSection } from './SettingsSection' +import { AppSettings } from '@/types/settings' + +export const TelemetrySettings = () => { + const { control, register } = useFormContext() + + const handleLinkClick = () => + window.studio.browser.openExternalLink( + 'https://grafana.com/docs/k6-studio/set-up/usage-collection/' + ) + + return ( + + + + k6 Studio collects anonymous telemetry data to improve performance and + user experience.{' '} + + Learn more. + + + + + + ( + + {' '} + Send usage data to Grafana. + + )} + /> + + + + ( + + {' '} + Send crash reports and error data to Grafana. + + )} + /> + + + ) +} diff --git a/src/components/Settings/UsageReportSettings.tsx b/src/components/Settings/UsageReportSettings.tsx deleted file mode 100644 index 3ff2a9aa..00000000 --- a/src/components/Settings/UsageReportSettings.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Flex, Text, Checkbox, Link } from '@radix-ui/themes' -import { Controller, useFormContext } from 'react-hook-form' -import { SettingsSection } from './SettingsSection' -import { AppSettings } from '@/types/settings' - -export const UsageReportSettings = () => { - const { control, register } = useFormContext() - - const handleLinkClick = () => - window.studio.browser.openExternalLink( - 'https://grafana.com/docs/k6-studio/set-up/usage-collection/' - ) - - return ( - - - ( - - {' '} - Send my anonymous usage data to Grafana to aid in development of - k6 Studio.{' '} - - Learn more. - - - )} - /> - - - ) -} diff --git a/src/main.ts b/src/main.ts index 14707198..1b73082e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -48,6 +48,7 @@ import log from 'electron-log/main' import { sendReport } from './usageReport' import { AppSettings } from './types/settings' import { + defaultSettings, getSettings, initSettings, saveSettings, @@ -66,6 +67,14 @@ if (process.env.NODE_ENV !== 'development') { Sentry.init({ dsn: SENTRY_DSN, integrations: [Sentry.electronMinidumpIntegration()], + + // conditionally send the event based on the user's settings + beforeSend: (event) => { + if (appSettings.telemetry.errorReport) { + return event + } + return null + }, }) } @@ -79,7 +88,7 @@ const PROXY_RETRY_LIMIT = 5 let proxyRetryCount = 0 let currentClientRoute = '/' let wasAppClosedByClient = false -export let appSettings: AppSettings +export let appSettings = defaultSettings let currentBrowserProcess: Process | null let currentk6Process: K6Process | null @@ -208,7 +217,7 @@ app.whenReady().then( appSettings = await getSettings() nativeTheme.themeSource = appSettings.appearance.theme - await sendReport(appSettings.usageReport) + await sendReport(appSettings.telemetry.usageReport) await createSplashWindow() await setupProjectStructure() await createWindow() @@ -365,7 +374,7 @@ ipcMain.handle( browserWindow, resolvedScriptPath, appSettings.proxy.port, - appSettings.usageReport.enabled + appSettings.telemetry.usageReport ) } ) @@ -648,8 +657,8 @@ async function applySettings( if (modifiedSettings.recorder) { appSettings.recorder = modifiedSettings.recorder } - if (modifiedSettings.usageReport) { - appSettings.usageReport = modifiedSettings.usageReport + if (modifiedSettings.telemetry) { + appSettings.telemetry = modifiedSettings.telemetry } if (modifiedSettings.appearance) { appSettings.appearance = modifiedSettings.appearance diff --git a/src/schemas/settings/index.test.ts b/src/schemas/settings/index.test.ts index 8bb9fe00..aa52dfb8 100644 --- a/src/schemas/settings/index.test.ts +++ b/src/schemas/settings/index.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest' import { migrate } from '.' import * as v1 from './v1' +import * as v2 from './v2' describe('Settings migration', () => { it('should migrate from v1 to latest', () => { @@ -29,6 +30,41 @@ describe('Settings migration', () => { }, } - expect(migrate(v1Settings).version).toBe('2.0') + const migration = migrate(v1Settings) + + expect(migration.version).toBe('3.0') + expect(migration.telemetry.usageReport).toBe(v1Settings.usageReport.enabled) + }) + + it('should migrate from v2 to latest', () => { + const v2Settings: v2.AppSettings = { + version: '2.0', + proxy: { + mode: 'regular', + port: 6000, + automaticallyFindPort: true, + }, + recorder: { + detectBrowserPath: true, + }, + windowState: { + width: 1200, + height: 800, + x: 0, + y: 0, + isMaximized: true, + }, + usageReport: { + enabled: true, + }, + appearance: { + theme: 'system', + }, + } + + const migration = migrate(v2Settings) + + expect(migration.version).toBe('3.0') + expect(migration.telemetry.usageReport).toBe(v2Settings.usageReport.enabled) }) }) diff --git a/src/schemas/settings/index.ts b/src/schemas/settings/index.ts index 915fb18b..fb1df8ed 100644 --- a/src/schemas/settings/index.ts +++ b/src/schemas/settings/index.ts @@ -1,11 +1,13 @@ import { z } from 'zod' import * as v1 from './v1' import * as v2 from './v2' +import * as v3 from './v3' import { exhaustive } from '../../utils/typescript' const AnySettingSchema = z.discriminatedUnion('version', [ v1.AppSettingsSchema, v2.AppSettingsSchema, + v3.AppSettingsSchema, ]) export function migrate(settings: z.infer) { @@ -13,6 +15,8 @@ export function migrate(settings: z.infer) { case '1.0': return migrate(v1.migrate(settings)) case '2.0': + return migrate(v2.migrate(settings)) + case '3.0': return settings default: return exhaustive(settings) @@ -25,6 +29,6 @@ export { AppearanceSchema, ProxySettingsSchema, RecorderSettingsSchema, - UsageReportSettingsSchema, + TelemetrySchema, WindowStateSchema, -} from './v2' +} from './v3' diff --git a/src/schemas/settings/v2/index.ts b/src/schemas/settings/v2/index.ts index 758adfbb..662d03ea 100644 --- a/src/schemas/settings/v2/index.ts +++ b/src/schemas/settings/v2/index.ts @@ -6,6 +6,7 @@ import { UsageReportSettingsSchema, WindowStateSchema, } from '../v1' +import * as v3 from '../v3' export { AppearanceSchema, @@ -26,7 +27,19 @@ export const AppSettingsSchema = z.object({ export type AppSettings = z.infer -// TODO: Migrate settings to the next version -export function migrate(settings: z.infer) { - return { ...settings } +// Migrate settings to the next version +export function migrate( + settings: z.infer +): v3.AppSettings { + return { + version: '3.0', + proxy: settings.proxy, + recorder: settings.recorder, + windowState: settings.windowState, + telemetry: { + usageReport: settings.usageReport.enabled, + errorReport: true, + }, + appearance: settings.appearance, + } } diff --git a/src/schemas/settings/v3/index.ts b/src/schemas/settings/v3/index.ts new file mode 100644 index 00000000..655559d9 --- /dev/null +++ b/src/schemas/settings/v3/index.ts @@ -0,0 +1,36 @@ +import { z } from 'zod' +import { + AppearanceSchema, + ProxySettingsSchema, + RecorderSettingsSchema, + WindowStateSchema, +} from '../v1' + +const TelemetrySchema = z.object({ + usageReport: z.boolean(), + errorReport: z.boolean(), +}) + +export { + AppearanceSchema, + ProxySettingsSchema, + RecorderSettingsSchema, + TelemetrySchema, + WindowStateSchema, +} + +export const AppSettingsSchema = z.object({ + version: z.literal('3.0'), + proxy: ProxySettingsSchema, + recorder: RecorderSettingsSchema, + windowState: WindowStateSchema, + telemetry: TelemetrySchema, + appearance: AppearanceSchema, +}) + +export type AppSettings = z.infer + +// TODO: Migrate settings to the next version +export function migrate(settings: z.infer) { + return { ...settings } +} diff --git a/src/settings.ts b/src/settings.ts index 1c1722df..ba823a65 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -7,8 +7,8 @@ import { existsSync, readFileSync } from 'fs' import { safeJsonParse } from './utils/json' import log from 'electron-log/main' -const defaultSettings: AppSettings = { - version: '2.0', +export const defaultSettings: AppSettings = { + version: '3.0', proxy: { mode: 'regular', port: 6000, @@ -24,8 +24,9 @@ const defaultSettings: AppSettings = { y: 0, isMaximized: true, }, - usageReport: { - enabled: true, + telemetry: { + usageReport: true, + errorReport: true, }, appearance: { theme: 'system', diff --git a/src/types/settings.ts b/src/types/settings.ts index 599dbd3d..da0b84ee 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -2,11 +2,11 @@ import { AppSettingsSchema, ProxySettingsSchema, RecorderSettingsSchema, - UsageReportSettingsSchema, + TelemetrySchema, } from '@/schemas/settings' import { z } from 'zod' export type AppSettings = z.infer export type ProxySettings = z.infer export type RecorderSettings = z.infer -export type UsageReportSettings = z.infer +export type TelemetrySettings = z.infer diff --git a/src/usageReport.ts b/src/usageReport.ts index fa861aae..da9d7218 100644 --- a/src/usageReport.ts +++ b/src/usageReport.ts @@ -3,7 +3,6 @@ import { getPlatform, getArch } from './utils/electron' import { writeFile, readFile } from 'fs/promises' import path from 'path' import { existsSync } from 'fs' -import { UsageReportSettings } from './types/settings' import log from 'electron-log/main' const URL = 'https://stats.grafana.org/k6-studio-usage-report' @@ -20,8 +19,8 @@ export const getOrSetInstallationId = async () => { return await readFile(filePath, { encoding: 'utf-8' }) } -export const sendReport = async (usageReportSettings: UsageReportSettings) => { - if (!usageReportSettings.enabled) { +export const sendReport = async (usageReportEnabled: boolean) => { + if (!usageReportEnabled) { return }