Skip to content

Commit

Permalink
feat(protocol-designer): add the release notes button to settings (#1…
Browse files Browse the repository at this point in the history
…6344)

re AUTH-856

<!--
Thanks for taking the time to open a Pull Request (PR)! Please make sure
you've read the "Opening Pull Requests" section of our Contributing
Guide:


https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-pull-requests

GitHub provides robust markdown to format your PR. Links, diagrams,
pictures, and videos along with text formatting make it possible to
create a rich and informative PR. For more information on GitHub
markdown, see:


https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax

To ensure your code is reviewed quickly and thoroughly, please fill out
the sections below to the best of your ability!
-->

# Overview

<!--
Describe your PR at a high level. State acceptance criteria and how this
PR fits into other work. Link issues, PRs, and other relevant resources.
-->

Add a `View release notes` button to the Settings page, and display the
`AnnouncementModal` when the button is clicked.

## Test Plan and Hands on Testing

<!--
Describe your testing of the PR. Emphasize testing not reflected in the
code. Attach protocols, logs, screenshots and any other assets that
support your testing.
-->
- Click on the settings icon
- Ensure it matches the
[design](https://www.figma.com/design/WbkiUyU8VhtKz0JSuIFA45/Feature%3A-Protocol-Designer-Phase-1?node-id=3150-116483&node-type=canvas&t=VjSlf85g2lJzT7Ed-0)
- Verify that the Announcement modal appears when the 'View release
notes' button is clicked.

## Changelog

<!--
List changes introduced by this PR considering future developers and the
end user. Give careful thought and clear documentation to breaking
changes.
-->

- Added a button labeled 'View release notes' next to the PD version
with a 12px gap
- Create an `AnnouncementModalProp` interface with optional
`isViewReleaseNotes` and `onClose` props to control the visibility of
the AnnouncementModal, tracking when the user clicks the button and
closes the modal
- Used `useLocation` to check if the user is on the Settings page
- Updated tests 
## Review requests

<!--
- What do you need from reviewers to feel confident this PR is ready to
merge?
- Ask questions.
-->

## Risk assessment

<!--
- Indicate the level of attention this PR needs.
- Provide context to guide reviewers.
- Discuss trade-offs, coupling, and side effects.
- Look for the possibility, even if you think it's small, that your
change may affect some other part of the system.
- For instance, changing return tip behavior may also change the
behavior of labware calibration.
- How do your unit tests and on hands on testing mitigate this PR's
risks and the risk of future regressions?
- Especially in high risk PRs, explain how you know your testing is
enough.
-->

---------

Co-authored-by: shiyaochen <[email protected]>
  • Loading branch information
syao1226 and shiyaochen authored Sep 26, 2024
1 parent 18fda28 commit 1dc23fb
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 120 deletions.
1 change: 1 addition & 0 deletions protocol-designer/src/assets/localization/en/shared.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"user_settings": "User settings",
"uses_standard_namespace": "Opentrons verified labware",
"version": "Version # {{version}}",
"view_release_notes": "View release notes",
"warning": "WARNING:",
"wasteChute": "Waste chute",
"wasteChuteAndStagingArea": "Waste chute and staging area slot",
Expand Down
13 changes: 11 additions & 2 deletions protocol-designer/src/organisms/AnnouncementModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ import {
} from '../../persist'
import { useAnnouncements } from './announcements'

export const AnnouncementModal = (): JSX.Element => {
interface AnnouncementModalProps {
isViewReleaseNotes?: boolean
onClose?: () => void
}

export const AnnouncementModal = (
props: AnnouncementModalProps
): JSX.Element => {
const { onClose, isViewReleaseNotes = false } = props
const { t } = useTranslation(['modal', 'button'])
const announcements = useAnnouncements()

Expand All @@ -28,10 +36,11 @@ export const AnnouncementModal = (): JSX.Element => {
getLocalStorageItem(localStorageAnnouncementKey) !== announcementKey

const [showAnnouncementModal, setShowAnnouncementModal] = useState<boolean>(
userHasNotSeenAnnouncement
isViewReleaseNotes || userHasNotSeenAnnouncement
)

const handleClick = (): void => {
if (onClose != null) onClose()
setLocalStorageItem(localStorageAnnouncementKey, announcementKey)
setShowAnnouncementModal(false)
}
Expand Down
13 changes: 13 additions & 0 deletions protocol-designer/src/pages/Settings/__tests__/Settings.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, vi, beforeEach, expect } from 'vitest'
import { MemoryRouter } from 'react-router-dom'
import { fireEvent, screen } from '@testing-library/react'
import { AnnouncementModal } from '../../../organisms'
import { i18n } from '../../../assets/localization'
import { renderWithProviders } from '../../../__testing-utils__'
import { getHasOptedIn } from '../../../analytics/selectors'
Expand All @@ -11,6 +12,7 @@ import { optIn } from '../../../analytics/actions'
import { setFeatureFlags } from '../../../feature-flags/actions'
import { Settings } from '..'

vi.mock('../../../organisms')
vi.mock('../../../feature-flags/actions')
vi.mock('../../../analytics/actions')
vi.mock('../../../tutorial/actions')
Expand Down Expand Up @@ -40,6 +42,7 @@ describe('Settings', () => {
screen.getByText('App settings')
screen.getByText('Protocol designer version')
screen.getByText('fake_PD_version')
screen.getAllByText('View release notes')
screen.getByText('User settings')
screen.getByText('Hints')
screen.getByText('Reset all hints and tips notifications')
Expand All @@ -50,6 +53,16 @@ describe('Settings', () => {
'We’re working to improve Protocol Designer. Part of the process involves watching real user sessions to understand which parts of the interface are working and which could use improvement. We never share sessions outside of Opentrons.'
)
})
it('renders the announcement modal when view release notes button is clicked', () => {
vi.mocked(AnnouncementModal).mockReturnValue(
<div>mock AnnouncementModal</div>
)
render()
fireEvent.click(
screen.getByTestId('AnnouncementModal_viewReleaseNotesButton')
)
screen.getByText('mock AnnouncementModal')
})
it('renders the hints button and calls to dismiss them when text is pressed', () => {
render()
fireEvent.click(screen.getByText('Reset hints'))
Expand Down
272 changes: 154 additions & 118 deletions protocol-designer/src/pages/Settings/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { css } from 'styled-components'
Expand All @@ -14,6 +15,7 @@ import {
StyledText,
TYPOGRAPHY,
} from '@opentrons/components'
import { AnnouncementModal } from '../../organisms'
import {
actions as analyticsActions,
selectors as analyticsSelectors,
Expand All @@ -22,13 +24,17 @@ import {
actions as tutorialActions,
selectors as tutorialSelectors,
} from '../../tutorial'
import { BUTTON_LINK_STYLE } from '../../atoms'
import { actions as featureFlagActions } from '../../feature-flags'
import { getFeatureFlagData } from '../../feature-flags/selectors'
import type { FlagTypes } from '../../feature-flags'

export function Settings(): JSX.Element {
const dispatch = useDispatch()
const { t } = useTranslation(['feature_flags', 'shared'])
const [showAnnouncementModal, setShowAnnouncementModal] = useState<boolean>(
false
)
const hasOptedIn = useSelector(analyticsSelectors.getHasOptedIn)
const flags = useSelector(getFeatureFlagData)
const canClearHintDismissals = useSelector(
Expand Down Expand Up @@ -94,139 +100,169 @@ export function Settings(): JSX.Element {
const prereleaseFlagRows = allFlags.map(toFlagRow)

return (
<Flex
backgroundColor={COLORS.grey10}
width="100%"
minHeight="calc(100vh - 56px)"
height="100%"
padding={`${SPACING.spacing80} 17rem`}
>
<>
{showAnnouncementModal ? (
<AnnouncementModal
isViewReleaseNotes={showAnnouncementModal}
onClose={() => {
setShowAnnouncementModal(false)
}}
/>
) : null}
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.white}
padding={SPACING.spacing40}
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing40}
backgroundColor={COLORS.grey10}
width="100%"
minHeight="calc(100vh - 56px)"
height="100%"
padding={`${SPACING.spacing80} 17rem`}
>
<StyledText desktopStyle="headingLargeBold">
{t('shared:settings')}
</StyledText>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
{t('shared:app_settings')}
</StyledText>
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
>
<StyledText desktopStyle="bodyDefaultSemiBold">
{t('shared:pd_version')}
</StyledText>
<StyledText desktopStyle="bodyDefaultRegular">
{process.env.OT_PD_VERSION}
</StyledText>
</Flex>
</Flex>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
{t('shared:user_settings')}
<Flex
backgroundColor={COLORS.white}
padding={SPACING.spacing40}
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing40}
borderRadius={BORDERS.borderRadius8}
width="100%"
>
<StyledText desktopStyle="headingLargeBold">
{t('shared:settings')}
</StyledText>
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
alignItems={ALIGN_CENTER}
>
<Flex flexDirection={DIRECTION_COLUMN}>
<StyledText desktopStyle="bodyDefaultSemiBold">
{t('shared:hints')}
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing24}>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
{t('shared:app_settings')}
</StyledText>
<Flex color={COLORS.grey60}>
<StyledText desktopStyle="bodyDefaultRegular">
{t('shared:reset_hints_and_tips')}
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
>
<StyledText desktopStyle="bodyDefaultSemiBold">
{t('shared:pd_version')}
</StyledText>
<Flex gridGap={SPACING.spacing12}>
<StyledText desktopStyle="bodyDefaultRegular">
{process.env.OT_PD_VERSION}
</StyledText>
<Btn
css={BUTTON_LINK_STYLE}
textDecoration={TYPOGRAPHY.textDecorationUnderline}
onClick={() => {
setShowAnnouncementModal(true)
}}
data-testid="AnnouncementModal_viewReleaseNotesButton"
>
<StyledText desktopStyle="bodyDefaultRegular">
{t('shared:view_release_notes')}
</StyledText>
</Btn>
</Flex>
</Flex>
</Flex>
<Btn
disabled={!canClearHintDismissals}
textDecoration={
canClearHintDismissals
? TYPOGRAPHY.textDecorationUnderline
: 'none'
}
onClick={() => dispatch(tutorialActions.clearAllHintDismissals())}
>
<StyledText desktopStyle="bodyDefaultRegular">
{canClearHintDismissals
? t('shared:reset_hints')
: t('shared:no_hints_to_restore')}
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
{t('shared:user_settings')}
</StyledText>
</Btn>
</Flex>
</Flex>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
{t('shared:privacy')}
</StyledText>
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
alignItems={ALIGN_CENTER}
gridGap={SPACING.spacing80}
>
<Flex flexDirection={DIRECTION_COLUMN}>
<StyledText desktopStyle="bodyDefaultSemiBold">
{t('shared:shared_sessions')}
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
alignItems={ALIGN_CENTER}
>
<Flex flexDirection={DIRECTION_COLUMN}>
<StyledText desktopStyle="bodyDefaultSemiBold">
{t('shared:hints')}
</StyledText>
<Flex color={COLORS.grey60}>
<StyledText desktopStyle="bodyDefaultRegular">
{t('shared:reset_hints_and_tips')}
</StyledText>
</Flex>
</Flex>
<Btn
disabled={!canClearHintDismissals}
textDecoration={
canClearHintDismissals
? TYPOGRAPHY.textDecorationUnderline
: 'none'
}
onClick={() =>
dispatch(tutorialActions.clearAllHintDismissals())
}
>
<StyledText desktopStyle="bodyDefaultRegular">
{canClearHintDismissals
? t('shared:reset_hints')
: t('shared:no_hints_to_restore')}
</StyledText>
</Btn>
</Flex>
</Flex>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
{t('shared:privacy')}
</StyledText>
<Flex color={COLORS.grey60}>
<StyledText desktopStyle="bodyDefaultRegular">
{t('shared:we_are_improving')}
</StyledText>
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
alignItems={ALIGN_CENTER}
gridGap={SPACING.spacing80}
>
<Flex flexDirection={DIRECTION_COLUMN}>
<StyledText desktopStyle="bodyDefaultSemiBold">
{t('shared:shared_sessions')}
</StyledText>
<Flex color={COLORS.grey60}>
<StyledText desktopStyle="bodyDefaultRegular">
{t('shared:we_are_improving')}
</StyledText>
</Flex>
</Flex>
<Btn
role="switch"
data-testid="analyticsToggle"
size="2rem"
css={
Boolean(hasOptedIn)
? TOGGLE_ENABLED_STYLES
: TOGGLE_DISABLED_STYLES
}
onClick={() => dispatch(_toggleOptedIn())}
>
<Icon
name={
hasOptedIn ? 'ot-toggle-input-on' : 'ot-toggle-input-off'
}
height="1rem"
/>
</Btn>
</Flex>
</Flex>
<Btn
role="switch"
data-testid="analyticsToggle"
size="2rem"
css={
Boolean(hasOptedIn)
? TOGGLE_ENABLED_STYLES
: TOGGLE_DISABLED_STYLES
}
onClick={() => dispatch(_toggleOptedIn())}
>
<Icon
name={hasOptedIn ? 'ot-toggle-input-on' : 'ot-toggle-input-off'}
height="1rem"
/>
</Btn>
</Flex>
</Flex>
{prereleaseModeEnabled ? (
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
{t('shared:developer_ff')}
</StyledText>
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing16}
>
{prereleaseFlagRows}
{prereleaseModeEnabled ? (
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
{t('shared:developer_ff')}
</StyledText>
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing16}
>
{prereleaseFlagRows}
</Flex>
</Flex>
</Flex>
) : null}
) : null}
</Flex>
</Flex>
</Flex>
</>
)
}

Expand Down

0 comments on commit 1dc23fb

Please sign in to comment.