Skip to content

Commit

Permalink
users: use generic destinations for User Notification Rule List compo…
Browse files Browse the repository at this point in the history
…nent (#3701)

* adding destype to user NR list

* remove console.log

* Update web/src/app/users/UserNotificationRuleListDest.tsx

Co-authored-by: Nathaniel Caza <mastercactapus@gmail.com>

* remove unused values in test data

---------

Co-authored-by: Ethan-Haynes <Ethan.Haynes@target.com>
Co-authored-by: Nathaniel Caza <mastercactapus@gmail.com>
  • Loading branch information
3 people authored Feb 23, 2024
1 parent b7da873 commit 4d43e92
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 4 deletions.
16 changes: 12 additions & 4 deletions web/src/app/users/UserDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import UserContactMethodListDest from './UserContactMethodListDest'
import { AddAlarm, SettingsPhone } from '@mui/icons-material'
import SpeedDial from '../util/SpeedDial'
import UserNotificationRuleList from './UserNotificationRuleList'
import UserNotificationRuleListDest from './UserNotificationRuleListDest'
import { Grid } from '@mui/material'
import UserContactMethodCreateDialog from './UserContactMethodCreateDialog'
import UserNotificationRuleCreateDialog from './UserNotificationRuleCreateDialog'
Expand Down Expand Up @@ -250,10 +251,17 @@ export default function UserDetails(props: {
readOnly={props.readOnly}
/>
)}
<UserNotificationRuleList
userID={userID}
readOnly={props.readOnly}
/>
{hasDestTypesFlag ? (
<UserNotificationRuleListDest
userID={userID}
readOnly={props.readOnly}
/>
) : (
<UserNotificationRuleList
userID={userID}
readOnly={props.readOnly}
/>
)}
{!mobile && (
<Suspense>
<Grid item xs={12}>
Expand Down
177 changes: 177 additions & 0 deletions web/src/app/users/UserNotificationRuleListDest.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import UserNotificationRuleListDest from './UserNotificationRuleListDest'
import { expect, within, userEvent, screen } from '@storybook/test'
import { handleDefaultConfig, handleExpFlags } from '../storybook/graphql'
import { HttpResponse, graphql } from 'msw'

const meta = {
title: 'users/UserNotificationRuleListDest',
component: UserNotificationRuleListDest,
tags: ['autodocs'],
parameters: {
msw: {
handlers: [
handleDefaultConfig,
handleExpFlags('dest-types'),
graphql.query('nrList', ({ variables: vars }) => {
return HttpResponse.json({
data:
vars.id === '00000000-0000-0000-0000-000000000000'
? {
user: {
id: '00000000-0000-0000-0000-000000000000',
contactMethods: [
{
id: '12345',
},
],
notificationRules: [
{
id: '123',
delayMinutes: 33,
contactMethod: {
id: '12345',
name: 'Josiah',
dest: {
type: 'single-field',
displayInfo: {
text: '+1 555-555-5555',
iconAltText: 'Voice Call',
},
},
},
},
],
},
}
: {
user: {
id: '00000000-0000-0000-0000-000000000001',
contactMethods: [
{
id: '67890',
},
{
id: '1111',
},
],
notificationRules: [
{
id: '96814869-1199-4477-832d-e714e7d94aea',
delayMinutes: 71,
contactMethod: {
id: '67890',
name: 'Bridget',
dest: {
type: 'builtin-twilio-voice',
displayInfo: {
text: '+1 763-351-1103',
iconAltText: 'Voice Call',
},
},
},
},
{
id: 'eea77488-3748-4af8-99ba-18855f9a540d',
delayMinutes: 247,
contactMethod: {
id: '1111',
name: 'Dewayne',
dest: {
type: 'builtin-twilio-sms',
displayInfo: {
text: '+1 763-346-2643',
iconAltText: 'Text Message',
},
},
},
},
],
},
},
})
}),
],
},
},
render: function Component(args) {
return <UserNotificationRuleListDest {...args} />
},
} satisfies Meta<typeof UserNotificationRuleListDest>

export default meta
type Story = StoryObj<typeof meta>

export const SingleContactMethod: Story = {
args: {
userID: '00000000-0000-0000-0000-000000000000',
readOnly: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)

// ensure correct info is displayed for single-field NR
await expect(
await canvas.findByText(
'After 33 minutes notify me via Voice Call at +1 555-555-5555 (Josiah)',
),
).toBeVisible()

await expect(await screen.findByText('Add Rule')).toHaveAttribute(
'type',
'button',
)
await userEvent.click(
await screen.findByLabelText('Delete notification rule'),
)
await userEvent.click(await screen.findByText('Cancel'))
},
}

export const MultiContactMethods: Story = {
args: {
userID: '00000000-0000-0000-0000-000000000001',
readOnly: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)

await expect(
await canvas.findByText(
'After 71 minutes notify me via Voice Call at +1 763-351-1103 (Bridget)',
),
).toBeVisible()
await expect(
await canvas.findByText(
'After 247 minutes notify me via Text Message at +1 763-346-2643 (Dewayne)',
),
).toBeVisible()

await expect(await screen.findByText('Add Rule')).toHaveAttribute(
'type',
'button',
)
const deleteButtons = await screen.findAllByLabelText(
'Delete notification rule',
)
expect(deleteButtons).toHaveLength(2)
await userEvent.click(deleteButtons[0])
await userEvent.click(await screen.findByText('Cancel'))
},
}

export const SingleReadOnlyContactMethods: Story = {
args: {
userID: '00000000-0000-0000-0000-000000000000',
readOnly: true,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)

// ensure no edit icons exist for read-only NR
await expect(
await canvas.queryByLabelText('Delete notification rule'),
).not.toBeInTheDocument()
},
}
142 changes: 142 additions & 0 deletions web/src/app/users/UserNotificationRuleListDest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React, { useState, ReactNode, Suspense } from 'react'
import { gql, useQuery } from 'urql'
import {
Button,
Card,
CardHeader,
Grid,
IconButton,
Theme,
} from '@mui/material'
import makeStyles from '@mui/styles/makeStyles'
import { Add, Delete } from '@mui/icons-material'
import FlatList from '../lists/FlatList'
import { formatNotificationRule, sortNotificationRules } from './util'
import UserNotificationRuleDeleteDialog from './UserNotificationRuleDeleteDialog'
import { styles as globalStyles } from '../styles/materialStyles'
import UserNotificationRuleCreateDialog from './UserNotificationRuleCreateDialog'
import { useIsWidthDown } from '../util/useWidth'
import { ObjectNotFound, GenericError } from '../error-pages'
import { User } from '../../schema'

const query = gql`
query nrList($id: ID!) {
user(id: $id) {
id
contactMethods {
id
}
notificationRules {
id
delayMinutes
contactMethod {
id
name
dest {
type
displayInfo {
text
iconAltText
}
}
}
}
}
}
`

const useStyles = makeStyles((theme: Theme) => {
const { cardHeader } = globalStyles(theme)
return {
cardHeader,
}
})

export default function UserNotificationRuleListDest(props: {
userID: string
readOnly: boolean
}): ReactNode {
const classes = useStyles()
const mobile = useIsWidthDown('md')
const [showAddDialog, setShowAddDialog] = useState(false)
const [deleteID, setDeleteID] = useState(null)

const [{ data, error }] = useQuery({
query,
variables: { id: props.userID },
})

if (data && !data.user)
return <ObjectNotFound type='notifcation rules list' />
if (error) return <GenericError error={error.message} />

const { user }: { user: User } = data

return (
<Grid item xs={12}>
<Card>
<CardHeader
className={classes.cardHeader}
titleTypographyProps={{ component: 'h2', variant: 'h5' }}
title='Notification Rules'
action={
!mobile ? (
<Button
title='Add Notification Rule'
variant='contained'
onClick={() => setShowAddDialog(true)}
startIcon={<Add />}
disabled={user?.contactMethods.length === 0}
>
Add Rule
</Button>
) : null
}
/>
<FlatList
data-cy='notification-rules'
items={sortNotificationRules(user?.notificationRules ?? []).map(
(nr) => {
const formattedValue =
nr.contactMethod.dest.displayInfo.text || 'Unknown Label'
const name = nr.contactMethod.name || 'Unknown User'
const type =
nr.contactMethod.dest.displayInfo.iconAltText || 'Unknown Type'
return {
title: formatNotificationRule(nr.delayMinutes, {
type,
name,
formattedValue,
}),
secondaryAction: props.readOnly ? null : (
<IconButton
aria-label='Delete notification rule'
onClick={() => setDeleteID(nr.id)}
color='secondary'
>
<Delete />
</IconButton>
),
}
},
)}
emptyMessage='No notification rules'
/>
</Card>
<Suspense>
{showAddDialog && (
<UserNotificationRuleCreateDialog
userID={props.userID}
onClose={() => setShowAddDialog(false)}
/>
)}
{deleteID && (
<UserNotificationRuleDeleteDialog
ruleID={deleteID}
onClose={() => setDeleteID(null)}
/>
)}
</Suspense>
</Grid>
)
}

0 comments on commit 4d43e92

Please sign in to comment.