Skip to content

Commit

Permalink
Revert "feat(app): remove privacy setting tabs (#14423)"
Browse files Browse the repository at this point in the history
This reverts commit c4660ae.
  • Loading branch information
b-cooper committed Feb 21, 2024
1 parent 5b70a9c commit 0ca4f6b
Show file tree
Hide file tree
Showing 17 changed files with 398 additions and 2 deletions.
7 changes: 6 additions & 1 deletion app/src/App/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ export type RobotSettingsTab =
| 'networking'
| 'advanced'
| 'feature-flags'
| 'privacy'

export type AppSettingsTab = 'general' | 'advanced' | 'feature-flags'
export type AppSettingsTab =
| 'general'
| 'privacy'
| 'advanced'
| 'feature-flags'

export type ProtocolRunDetailsTab = 'setup' | 'module-controls' | 'run-preview'

Expand Down
2 changes: 2 additions & 0 deletions app/src/assets/localization/en/app_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"opentrons_app_update_available": "Opentrons App Update Available",
"opentrons_app_update_available_variation": "An Opentrons App update is available.",
"opentrons_app_will_use_interpreter": "If specified, the Opentrons App will use the Python interpreter at this path instead of the default bundled Python interpreter.",
"opentrons_cares_about_privacy": "Opentrons cares about your privacy. We anonymize all data and only use it to improve our products.",
"opt_in": "Opt in",
"opt_in_description": "Automatically send us anonymous diagnostics and usage data. We only use this information to improve our products.",
"opt_out": "Opt out",
Expand All @@ -66,6 +67,7 @@
"prevent_robot_caching": "Prevent Robot Caching",
"prevent_robot_caching_description": "The app <strong>will immediately clear unavailable robots</strong> and will not remember unavailable robots while this is enabled. On networks with many robots, preventing caching may improve network performance at the expense of slower and less reliable robot discovery on app launch.",
"previous_releases": "View previous Opentrons releases",
"privacy": "Privacy",
"problem_during_update": "This update is taking longer than usual.",
"prompt": "Always show the prompt to choose calibration block or trash bin",
"receive_alert": "Receive an alert when an Opentrons software update is available.",
Expand Down
1 change: 1 addition & 0 deletions app/src/assets/localization/en/device_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
"pipette_offset_calibration_recommended": "Pipette Offset calibration recommended",
"pipette_offset_calibrations_history": "See all Pipette Offset Calibration history",
"pipette_offset_calibrations_title": "Pipette Offset Calibrations",
"privacy": "Privacy",
"problem_during_update": "This update is taking longer than usual.",
"proceed_without_updating": "Proceed without update",
"protocol_run_history": "Protocol run History",
Expand Down
1 change: 1 addition & 0 deletions app/src/molecules/NavTab/NavTab.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const Template: Story<React.ComponentProps<typeof NavTab>> = args => (
>
<MemoryRouter initialEntries={['/general']}>
<NavTab to="/general" tabName="General" />
<NavTab to="/privacy" tabName="Privacy" />
<NavTab to="/advanced" tabName="Advanced" />
<NavTab to="/feature-flags" tabName="Feature flags" />
</MemoryRouter>
Expand Down
71 changes: 71 additions & 0 deletions app/src/organisms/Devices/RobotSettings/RobotSettingsPrivacy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next'

import { getRobotSettings, fetchSettings } from '../../../redux/robot-settings'

import type { State, Dispatch } from '../../../redux/types'
import type {
RobotSettings,
RobotSettingsField,
} from '../../../redux/robot-settings/types'
import { SettingToggle } from './SettingToggle'

interface RobotSettingsPrivacyProps {
robotName: string
}

const PRIVACY_SETTINGS = ['disableLogAggregation']

const INFO_BY_SETTING_ID: {
[id: string]: {
titleKey: string
descriptionKey: string
invert: boolean
}
} = {
disableLogAggregation: {
titleKey: 'share_logs_with_opentrons',
descriptionKey: 'share_logs_with_opentrons_description',
invert: true,
},
}

export function RobotSettingsPrivacy({
robotName,
}: RobotSettingsPrivacyProps): JSX.Element {
const { t } = useTranslation('device_settings')
const settings = useSelector<State, RobotSettings>((state: State) =>
getRobotSettings(state, robotName)
)
const privacySettings = settings.filter(({ id }) =>
PRIVACY_SETTINGS.includes(id)
)
const translatedPrivacySettings: Array<
RobotSettingsField & { invert: boolean }
> = privacySettings.map(s => {
const { titleKey, descriptionKey, invert } = INFO_BY_SETTING_ID[s.id]
return s.id in INFO_BY_SETTING_ID
? {
...s,
title: t(titleKey),
description: t(descriptionKey),
invert,
}
: { ...s, invert: false }
})

const dispatch = useDispatch<Dispatch>()

React.useEffect(() => {
dispatch(fetchSettings(robotName))
}, [dispatch, robotName])

return (
<>
{translatedPrivacySettings.map(field => (
<SettingToggle key={field.id} {...field} robotName={robotName} />
))}
</>
)
}
94 changes: 94 additions & 0 deletions app/src/organisms/RobotSettingsDashboard/Privacy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'

import {
Flex,
SPACING,
DIRECTION_COLUMN,
TYPOGRAPHY,
} from '@opentrons/components'

import { StyledText } from '../../atoms/text'
import { ChildNavigation } from '../../organisms/ChildNavigation'
import { ROBOT_ANALYTICS_SETTING_ID } from '../../pages/RobotDashboard/AnalyticsOptInModal'
import { RobotSettingButton } from '../../pages/RobotSettingsDashboard/RobotSettingButton'
import { OnOffToggle } from '../../pages/RobotSettingsDashboard/RobotSettingsList'
import {
getAnalyticsOptedIn,
toggleAnalyticsOptedIn,
} from '../../redux/analytics'
import { getRobotSettings, updateSetting } from '../../redux/robot-settings'

import type { Dispatch, State } from '../../redux/types'
import type { SetSettingOption } from '../../pages/RobotSettingsDashboard'

interface PrivacyProps {
robotName: string
setCurrentOption: SetSettingOption
}

export function Privacy({
robotName,
setCurrentOption,
}: PrivacyProps): JSX.Element {
const { t } = useTranslation('app_settings')
const dispatch = useDispatch<Dispatch>()

const allRobotSettings = useSelector((state: State) =>
getRobotSettings(state, robotName)
)

const appAnalyticsOptedIn = useSelector(getAnalyticsOptedIn)

const isRobotAnalyticsDisabled =
allRobotSettings.find(({ id }) => id === ROBOT_ANALYTICS_SETTING_ID)
?.value ?? false

return (
<Flex flexDirection={DIRECTION_COLUMN}>
<ChildNavigation
header={t('app_settings:privacy')}
onClickBack={() => setCurrentOption(null)}
/>
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing24}
paddingX={SPACING.spacing40}
marginTop="7.75rem"
>
<StyledText
fontSize={TYPOGRAPHY.fontSize28}
lineHeight={TYPOGRAPHY.lineHeight36}
fontWeight={TYPOGRAPHY.fontWeightRegular}
>
{t('opentrons_cares_about_privacy')}
</StyledText>
<Flex flexDirection={DIRECTION_COLUMN}>
<RobotSettingButton
settingName={t('share_robot_logs')}
settingInfo={t('share_robot_logs_description')}
dataTestId="RobotSettingButton_share_analytics"
rightElement={<OnOffToggle isOn={!isRobotAnalyticsDisabled} />}
onClick={() =>
dispatch(
updateSetting(
robotName,
ROBOT_ANALYTICS_SETTING_ID,
!isRobotAnalyticsDisabled
)
)
}
/>
<RobotSettingButton
settingName={t('share_display_usage')}
settingInfo={t('share_display_usage_description')}
dataTestId="RobotSettingButton_share_app_analytics"
rightElement={<OnOffToggle isOn={appAnalyticsOptedIn} />}
onClick={() => dispatch(toggleAnalyticsOptedIn())}
/>
</Flex>
</Flex>
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react'
import { fireEvent, screen } from '@testing-library/react'
import { renderWithProviders } from '@opentrons/components'

import { i18n } from '../../../i18n'
import { toggleAnalyticsOptedIn } from '../../../redux/analytics'
import { getRobotSettings, updateSetting } from '../../../redux/robot-settings'

import { Privacy } from '../Privacy'

jest.mock('../../../redux/analytics')
jest.mock('../../../redux/robot-settings')

const mockGetRobotSettings = getRobotSettings as jest.MockedFunction<
typeof getRobotSettings
>
const mockUpdateSetting = updateSetting as jest.MockedFunction<
typeof updateSetting
>
const mockToggleAnalyticsOptedIn = toggleAnalyticsOptedIn as jest.MockedFunction<
typeof toggleAnalyticsOptedIn
>

const render = (props: React.ComponentProps<typeof Privacy>) => {
return renderWithProviders(<Privacy {...props} />, {
i18nInstance: i18n,
})
}

describe('Privacy', () => {
let props: React.ComponentProps<typeof Privacy>
beforeEach(() => {
props = {
robotName: 'Otie',
setCurrentOption: jest.fn(),
}
mockGetRobotSettings.mockReturnValue([])
})

afterEach(() => {
jest.clearAllMocks()
})

it('should render text and buttons', () => {
render(props)
screen.getByText('Privacy')
screen.getByText(
'Opentrons cares about your privacy. We anonymize all data and only use it to improve our products.'
)
screen.getByText('Share robot logs')
screen.getByText('Data on actions the robot does, like running protocols.')
screen.getByText('Share display usage')
screen.getByText('Data on how you interact with the touchscreen on Flex.')
})

it('should toggle display usage sharing on click', () => {
render(props)
fireEvent.click(screen.getByText('Share display usage'))
expect(mockToggleAnalyticsOptedIn).toBeCalled()
})

it('should toggle robot logs sharing on click', () => {
render(props)
fireEvent.click(screen.getByText('Share robot logs'))
expect(mockUpdateSetting).toBeCalledWith(
'Otie',
'disableLogAggregation',
true
)
})
})
1 change: 1 addition & 0 deletions app/src/organisms/RobotSettingsDashboard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './NetworkSettings/RobotSettingsSetWifiCred'
export * from './NetworkSettings/RobotSettingsWifi'
export * from './NetworkSettings/RobotSettingsWifiConnect'
export * from './NetworkSettings'
export * from './Privacy'
export * from './RobotName'
export * from './RobotSystemVersion'
export * from './TextSize'
Expand Down
56 changes: 56 additions & 0 deletions app/src/pages/AppSettings/PrivacySettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector, useDispatch } from 'react-redux'

import {
Flex,
Box,
SIZE_2,
TYPOGRAPHY,
JUSTIFY_SPACE_BETWEEN,
SPACING,
} from '@opentrons/components'

import {
toggleAnalyticsOptedIn,
getAnalyticsOptedIn,
} from '../../redux/analytics'
import { ToggleButton } from '../../atoms/buttons'
import { StyledText } from '../../atoms/text'

import type { Dispatch, State } from '../../redux/types'

export function PrivacySettings(): JSX.Element {
const { t } = useTranslation('app_settings')
const dispatch = useDispatch<Dispatch>()
const analyticsOptedIn = useSelector((s: State) => getAnalyticsOptedIn(s))

return (
<Flex
height="calc(100vh - 8.5rem)"
justifyContent={JUSTIFY_SPACE_BETWEEN}
paddingX={SPACING.spacing16}
paddingY={SPACING.spacing24}
gridGap={SPACING.spacing16}
>
<Box width="70%">
<StyledText
css={TYPOGRAPHY.h3SemiBold}
paddingBottom={SPACING.spacing8}
>
{t('share_app_analytics')}
</StyledText>
<StyledText css={TYPOGRAPHY.pRegular} paddingBottom={SPACING.spacing8}>
{t('share_app_analytics_description')}
</StyledText>
</Box>
<ToggleButton
label="analytics_opt_in"
size={SIZE_2}
toggledOn={analyticsOptedIn}
onClick={() => dispatch(toggleAnalyticsOptedIn())}
id="PrivacySettings_analytics"
/>
</Flex>
)
}
7 changes: 7 additions & 0 deletions app/src/pages/AppSettings/__test__/AppSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { renderWithProviders } from '@opentrons/components'
import { i18n } from '../../../i18n'
import * as Config from '../../../redux/config'
import { GeneralSettings } from '../GeneralSettings'
import { PrivacySettings } from '../PrivacySettings'
import { AdvancedSettings } from '../AdvancedSettings'
import { FeatureFlags } from '../../../organisms/AppSettings/FeatureFlags'
import { AppSettings } from '..'

jest.mock('../../../redux/config')
jest.mock('../GeneralSettings')
jest.mock('../PrivacySettings')
jest.mock('../AdvancedSettings')
jest.mock('../../../organisms/AppSettings/FeatureFlags')

Expand All @@ -22,6 +24,9 @@ const getDevtoolsEnabled = Config.getDevtoolsEnabled as jest.MockedFunction<
const mockGeneralSettings = GeneralSettings as jest.MockedFunction<
typeof GeneralSettings
>
const mockPrivacySettings = PrivacySettings as jest.MockedFunction<
typeof PrivacySettings
>
const mockAdvancedSettings = AdvancedSettings as jest.MockedFunction<
typeof AdvancedSettings
>
Expand All @@ -45,6 +50,7 @@ describe('AppSettingsHeader', () => {
beforeEach(() => {
getDevtoolsEnabled.mockReturnValue(false)
mockGeneralSettings.mockReturnValue(<div>Mock General Settings</div>)
mockPrivacySettings.mockReturnValue(<div>Mock Privacy Settings</div>)
mockAdvancedSettings.mockReturnValue(<div>Mock Advanced Settings</div>)
mockFeatureFlags.mockReturnValue(<div>Mock Feature Flags</div>)
})
Expand All @@ -56,6 +62,7 @@ describe('AppSettingsHeader', () => {
const [{ getByText }] = render('/app-settings/general')
getByText('App Settings')
getByText('General')
getByText('Privacy')
getByText('Advanced')
})
it('does not render feature flags link if dev tools disabled', () => {
Expand Down
Loading

0 comments on commit 0ca4f6b

Please sign in to comment.