From 54f9925f10919102c2c6b993397f0853d7ed57d0 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Tue, 13 Feb 2024 16:51:42 -0600 Subject: [PATCH 01/17] add user CM list dest component --- graphql2/graphqlapp/contactmethod.go | 2 +- .../app/users/UserContactMethodListDest.tsx | 270 ++++++++++++++++++ web/src/app/users/UserDetails.tsx | 15 +- 3 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 web/src/app/users/UserContactMethodListDest.tsx diff --git a/graphql2/graphqlapp/contactmethod.go b/graphql2/graphqlapp/contactmethod.go index f811436be4..68aed546be 100644 --- a/graphql2/graphqlapp/contactmethod.go +++ b/graphql2/graphqlapp/contactmethod.go @@ -56,7 +56,7 @@ func (a *ContactMethod) Dest(ctx context.Context, obj *contactmethod.ContactMeth return &graphql2.Destination{ Type: destSlackDM, Values: []graphql2.FieldValuePair{ - {FieldID: fieldSlackUserID, Value: obj.Value, Label: a.FormatDestFunc(ctx, notification.DestTypeSlackChannel, obj.Value)}, + {FieldID: fieldSlackUserID, Value: obj.Value, Label: a.FormatDestFunc(ctx, notification.ScannableDestType{CM: obj.Type}.DestType(), obj.Value)}, }, }, nil } diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx new file mode 100644 index 0000000000..bcff8c3e80 --- /dev/null +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -0,0 +1,270 @@ +import React, { useState, ReactNode } from 'react' +import { gql, useQuery } from 'urql' +import FlatList from '../lists/FlatList' +import { + Button, + Card, + CardHeader, + Grid, + IconButton, + Typography, +} from '@mui/material' +import makeStyles from '@mui/styles/makeStyles' +import { Theme } from '@mui/material/styles' +import { Add } from '@mui/icons-material' +import { sortContactMethods } from './util' +import OtherActions from '../util/OtherActions' +import UserContactMethodDeleteDialog from './UserContactMethodDeleteDialog' +import UserContactMethodEditDialog from './UserContactMethodEditDialog' +import { Warning } from '../icons' +import UserContactMethodVerificationDialog from './UserContactMethodVerificationDialog' +import { useIsWidthDown } from '../util/useWidth' +import { GenericError, ObjectNotFound } from '../error-pages' +import SendTestDialog from './SendTestDialog' +import AppLink from '../util/AppLink' +import { styles as globalStyles } from '../styles/materialStyles' +import { FieldValuePair, UserContactMethod } from '../../schema' +import UserContactMethodCreateDialog from './UserContactMethodCreateDialog' +import { useSessionInfo } from '../util/RequireConfig' + +const query = gql` + query cmList($id: ID!) { + user(id: $id) { + id + contactMethods { + id + name + dest { + type + values { + fieldID + value + label + } + } + disabled + pending + } + } + } +` + +interface ListItemAction { + label: string + onClick: () => void + disabled?: boolean + tooltip?: string +} + +interface UserContactMethodListProps { + userID: string + readOnly?: boolean +} + +const useStyles = makeStyles((theme: Theme) => ({ + cardHeader: globalStyles(theme).cardHeader, +})) + +export default function UserContactMethodListDest( + props: UserContactMethodListProps, +): ReactNode { + const classes = useStyles() + const mobile = useIsWidthDown('md') + + const [showAddDialog, setShowAddDialog] = useState(false) + const [showVerifyDialogByID, setShowVerifyDialogByID] = useState('') + const [showEditDialogByID, setShowEditDialogByID] = useState('') + const [showDeleteDialogByID, setShowDeleteDialogByID] = useState('') + const [showSendTestByID, setShowSendTestByID] = useState('') + + const [{ error, data }] = useQuery({ + query, + variables: { + id: props.userID, + }, + }) + + const { userID: currentUserID } = useSessionInfo() + const isCurrentUser = props.userID === currentUserID + + if (!data?.user) return + if (error) return + + const contactMethods = data.user.contactMethods + console.log(data) + + const getIcon = (cm: UserContactMethod): JSX.Element | null => { + if (!cm.disabled) return null + if (props.readOnly) { + return + } + + return ( + setShowVerifyDialogByID(cm.id)} + disabled={props.readOnly} + size='large' + > + + + ) + } + + function getActionMenuItems(cm: UserContactMethod): ListItemAction[] { + const actions = [ + { + label: 'Edit', + onClick: () => setShowEditDialogByID(cm.id), + disabled: false, + tooltip: '', + }, + { + label: 'Delete', + onClick: () => setShowDeleteDialogByID(cm.id), + disabled: false, + tooltip: '', + }, + ] + + // disable send test and reactivate if not current user + if (!cm.disabled) { + actions.push({ + label: 'Send Test', + onClick: () => setShowSendTestByID(cm.id), + disabled: !isCurrentUser, + tooltip: !isCurrentUser + ? 'Send Test only available for your own contact methods' + : '', + }) + } else { + actions.push({ + label: 'Reactivate', + onClick: () => setShowVerifyDialogByID(cm.id), + disabled: !isCurrentUser, + tooltip: !isCurrentUser + ? 'Reactivate only available for your own contact methods' + : '', + }) + } + return actions + } + + function getSecondaryAction(cm: UserContactMethod): JSX.Element { + return ( + + {cm.disabled && !props.readOnly && !mobile && isCurrentUser && ( + + + + )} + {!props.readOnly && ( + + + + )} + + ) + } + + function getSubText(v: FieldValuePair, cm: UserContactMethod): JSX.Element { + let cmText = v.label + if (cm.pending) { + cmText = `${cmText} - this contact method will be automatically deleted if not verified` + } + if (v.fieldID === 'webhook-url') { + return ( + + {`${cmText} (`} + docs) + + ) + } + + return {cmText} + } + + function getSubTexts(cm: UserContactMethod): JSX.Element { + return ( + + {cm.dest.values.map((v) => { + return getSubText(v, cm) + })} + + ) + } + + return ( + + + setShowAddDialog(true)} + startIcon={} + > + Create Method + + ) : null + } + /> + ({ + title: `${cm.name} (${cm.dest.type})${cm.disabled ? ' - Disabled' : ''}`, + subText: getSubTexts(cm), + secondaryAction: getSecondaryAction(cm), + icon: getIcon(cm), + }))} + emptyMessage='No contact methods' + /> + {showAddDialog && ( + { + setShowAddDialog(false) + setShowVerifyDialogByID(contactMethodID) + }} + /> + )} + {showVerifyDialogByID && ( + setShowVerifyDialogByID('')} + /> + )} + {showEditDialogByID && ( + setShowEditDialogByID('')} + /> + )} + {showDeleteDialogByID && ( + setShowDeleteDialogByID('')} + /> + )} + {showSendTestByID && ( + setShowSendTestByID('')} + /> + )} + + + ) +} diff --git a/web/src/app/users/UserDetails.tsx b/web/src/app/users/UserDetails.tsx index 1b89ac6831..47df34c945 100644 --- a/web/src/app/users/UserDetails.tsx +++ b/web/src/app/users/UserDetails.tsx @@ -5,6 +5,7 @@ import LockOpenIcon from '@mui/icons-material/LockOpen' import DetailsPage from '../details/DetailsPage' import { UserAvatar } from '../util/avatars' import UserContactMethodList from './UserContactMethodList' +import UserContactMethodListDest from './UserContactMethodListDest' import { AddAlarm, SettingsPhone } from '@mui/icons-material' import SpeedDial from '../util/SpeedDial' import UserNotificationRuleList from './UserNotificationRuleList' @@ -21,6 +22,7 @@ import { QuerySetFavoriteButton } from '../util/QuerySetFavoriteButton' import { EscalationPolicyStep } from '../../schema' import { useIsWidthDown } from '../util/useWidth' import UserShiftsCalendar from './UserShiftsCalendar' +import { useExpFlag } from '../util/useExpFlag' const userQuery = gql` query userInfo($id: ID!) { @@ -88,6 +90,7 @@ export default function UserDetails(props: { userID: string readOnly: boolean }): JSX.Element { + const hasDestTypesFlag = useExpFlag('dest-types') const userID = props.userID const { userID: currentUserID, isAdmin } = useSessionInfo() @@ -236,7 +239,17 @@ export default function UserDetails(props: { subheader={user.email} pageContent={ - + {hasDestTypesFlag ? ( + + ) : ( + + )} Date: Tue, 13 Feb 2024 16:52:07 -0600 Subject: [PATCH 02/17] [WIP] add component tests for user CM list dest --- web/src/app/storybook/defaultDestTypes.ts | 28 ++++ .../UserContactMethodListDest.stories.tsx | 131 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 web/src/app/users/UserContactMethodListDest.stories.tsx diff --git a/web/src/app/storybook/defaultDestTypes.ts b/web/src/app/storybook/defaultDestTypes.ts index eca300d23f..6bcdbfe09e 100644 --- a/web/src/app/storybook/defaultDestTypes.ts +++ b/web/src/app/storybook/defaultDestTypes.ts @@ -165,4 +165,32 @@ export const destTypes: DestinationTypeInfo[] = [ }, ], }, + { + type: 'webhook-field', + name: 'Webhook Field Destination Type', + enabled: true, + disabledMessage: 'Webhook field destination type must be configured.', + userDisclaimer: '', + isContactMethod: true, + isEPTarget: false, + isSchedOnCallNotify: false, + iconURL: '', + iconAltText: '', + supportsStatusUpdates: true, + statusUpdatesRequired: false, + requiredFields: [ + { + fieldID: 'webhook-url', + labelSingular: 'Webhook Url', + labelPlural: '', + hint: 'Some hint text', + hintURL: '', + placeholderText: 'https://target.com', + prefix: '', + inputType: 'url', + isSearchSelectable: false, + supportsValidation: false, + }, + ], + }, ] diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx new file mode 100644 index 0000000000..297cb3dd06 --- /dev/null +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -0,0 +1,131 @@ +import React from 'react' +import type { Meta, StoryObj } from '@storybook/react' +import UserContactMethodListDest from './UserContactMethodListDest' +import { expect, within } from '@storybook/test' +import { handleDefaultConfig, handleExpFlags } from '../storybook/graphql' +import { HttpResponse, graphql } from 'msw' + +const meta = { + title: 'users/UserContactMethodListDest', + component: UserContactMethodListDest, + tags: ['autodocs'], + parameters: { + msw: { + handlers: [ + handleDefaultConfig, + handleExpFlags('dest-types'), + graphql.query('cmList', ({ variables: vars }) => { + return HttpResponse.json({ + data: + vars.id === '00000000-0000-0000-0000-000000000001' + ? { + user: { + id: '00000000-0000-0000-0000-000000000001', + contactMethods: [ + { + id: '12345', + name: 'Josiah', + dest: { + type: 'single-field', + values: [ + { + fieldID: 'phone-number', + value: '+15555555555', + label: '+1 555-555-5555', + }, + ], + }, + disabled: false, + pending: false, + }, + ], + }, + } + : { + user: { + id: '00000000-0000-0000-0000-000000000002', + contactMethods: [ + { + id: '67890', + name: 'triple contact method', + dest: { + type: 'triple-field', + values: [ + { + fieldID: 'first-field', + value: '+15555555555', + label: '+1 555-555-5555', + }, + { + fieldID: 'second-field', + value: 'test_user@target.com', + label: 'test_user@target.com', + }, + { + fieldID: 'third-field', + value: 'U03SM0U8TPE', + label: '@TestUser', + }, + ], + }, + disabled: false, + pending: false, + }, + { + id: '1111', + name: 'my_webhook', + dest: { + type: 'webhook-field', + values: [ + { + fieldID: 'webhook-url', + value: 'https://target.com', + label: 'https://target.com', + }, + ], + }, + disabled: false, + pending: false, + }, + ], + }, + }, + }) + }), + ], + }, + }, + render: function Component(args) { + return + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const MultiContactMethods: Story = { + args: { + userID: '00000000-0000-0000-0000-000000000002', + readOnly: true, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + + // ensure correct info is displayed for webhook CM + await expect( + await canvas.findByText('my_webhook (webhook-field)'), + ).toBeVisible() + await expect(canvas.getByText('docs')).toHaveAttribute( + 'href', + '/docs#webhooks', + ) + + // ensure correct info is displayed for triple-field CM + await expect( + await canvas.findByText('triple contact method (triple-field)'), + ).toBeVisible() + await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() + await expect(await canvas.findByText('test_user@target.com')).toBeVisible() + await expect(await canvas.findByText('@TestUser')).toBeVisible() + }, +} From 2d70fc2f587b93c067a951092e07c763815c9412 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Wed, 14 Feb 2024 13:25:55 -0600 Subject: [PATCH 03/17] add component tests for user CM list --- .../UserContactMethodListDest.stories.tsx | 276 +++++++++++++----- 1 file changed, 200 insertions(+), 76 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index 297cb3dd06..b5014cf655 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -1,10 +1,134 @@ import React from 'react' import type { Meta, StoryObj } from '@storybook/react' import UserContactMethodListDest from './UserContactMethodListDest' -import { expect, within } from '@storybook/test' +import { expect, within, userEvent, screen } from '@storybook/test' import { handleDefaultConfig, handleExpFlags } from '../storybook/graphql' import { HttpResponse, graphql } from 'msw' +interface UserCMResponse { + user: { + id: string + contactMethods: { + id: string + name: string + dest: { + type: string + values: { + fieldID: string + value: string + label: string + }[] + } + disabled: boolean + pending: boolean + }[] + } +} + +function getUserCMData(userID: string): UserCMResponse | undefined { + if (userID === '00000000-0000-0000-0000-000000000000') { + return { + user: { + id: '00000000-0000-0000-0000-000000000000', + contactMethods: [ + { + id: '12345', + name: 'Josiah', + dest: { + type: 'single-field', + values: [ + { + fieldID: 'phone-number', + value: '+15555555555', + label: '+1 555-555-5555', + }, + ], + }, + disabled: false, + pending: false, + }, + ], + }, + } + } + if (userID === '00000000-0000-0000-0000-000000000001') { + return { + user: { + id: '00000000-0000-0000-0000-000000000001', + contactMethods: [ + { + id: '12345', + name: 'non_current_user', + dest: { + type: 'single-field', + values: [ + { + fieldID: 'phone-number', + value: '+15555555555', + label: '+1 555-555-5555', + }, + ], + }, + disabled: false, + pending: false, + }, + ], + }, + } + } + if (userID === '00000000-0000-0000-0000-000000000002') { + return { + user: { + id: '00000000-0000-0000-0000-000000000002', + contactMethods: [ + { + id: '67890', + name: 'triple contact method', + dest: { + type: 'triple-field', + values: [ + { + fieldID: 'first-field', + value: '+15555555555', + label: '+1 555-555-5555', + }, + { + fieldID: 'second-field', + value: 'test_user@target.com', + label: 'test_user@target.com', + }, + { + fieldID: 'third-field', + value: 'U03SM0U8TPE', + label: '@TestUser', + }, + ], + }, + disabled: false, + pending: false, + }, + { + id: '1111', + name: 'my_webhook', + dest: { + type: 'webhook-field', + values: [ + { + fieldID: 'webhook-url', + value: 'https://target.com', + label: 'https://target.com', + }, + ], + }, + disabled: false, + pending: false, + }, + ], + }, + } + } +} + const meta = { title: 'users/UserContactMethodListDest', component: UserContactMethodListDest, @@ -16,80 +140,7 @@ const meta = { handleExpFlags('dest-types'), graphql.query('cmList', ({ variables: vars }) => { return HttpResponse.json({ - data: - vars.id === '00000000-0000-0000-0000-000000000001' - ? { - user: { - id: '00000000-0000-0000-0000-000000000001', - contactMethods: [ - { - id: '12345', - name: 'Josiah', - dest: { - type: 'single-field', - values: [ - { - fieldID: 'phone-number', - value: '+15555555555', - label: '+1 555-555-5555', - }, - ], - }, - disabled: false, - pending: false, - }, - ], - }, - } - : { - user: { - id: '00000000-0000-0000-0000-000000000002', - contactMethods: [ - { - id: '67890', - name: 'triple contact method', - dest: { - type: 'triple-field', - values: [ - { - fieldID: 'first-field', - value: '+15555555555', - label: '+1 555-555-5555', - }, - { - fieldID: 'second-field', - value: 'test_user@target.com', - label: 'test_user@target.com', - }, - { - fieldID: 'third-field', - value: 'U03SM0U8TPE', - label: '@TestUser', - }, - ], - }, - disabled: false, - pending: false, - }, - { - id: '1111', - name: 'my_webhook', - dest: { - type: 'webhook-field', - values: [ - { - fieldID: 'webhook-url', - value: 'https://target.com', - label: 'https://target.com', - }, - ], - }, - disabled: false, - pending: false, - }, - ], - }, - }, + data: getUserCMData(vars.id), }) }), ], @@ -103,10 +154,44 @@ const meta = { export default meta type Story = StoryObj +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 CM + await expect(await canvas.findByText('Josiah (single-field)')).toBeVisible() + await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() + + // ensure CM is editable + await expect( + await canvas.queryByTestId('MoreHorizIcon'), + ).toBeInTheDocument() + + // ensure all edit options are available + await userEvent.click(await canvas.findByTestId('MoreHorizIcon')) + await expect(await screen.findByText('Edit')).toHaveAttribute( + 'role', + 'menuitem', + ) + await expect(await screen.findByText('Delete')).toHaveAttribute( + 'role', + 'menuitem', + ) + await expect(await screen.findByText('Send Test')).toHaveAttribute( + 'role', + 'menuitem', + ) + }, +} + export const MultiContactMethods: Story = { args: { userID: '00000000-0000-0000-0000-000000000002', - readOnly: true, + readOnly: false, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement) @@ -127,5 +212,44 @@ export const MultiContactMethods: Story = { await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() await expect(await canvas.findByText('test_user@target.com')).toBeVisible() await expect(await canvas.findByText('@TestUser')).toBeVisible() + + // ensure all edit icons exists + await expect(await canvas.findAllByTestId('MoreHorizIcon')).toHaveLength(2) + }, +} + +export const SingleReadOnlyContactMethods: Story = { + args: { + userID: '00000000-0000-0000-0000-000000000000', + readOnly: true, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + + // ensure correct info is displayed for single-field CM + await expect(await canvas.findByText('Josiah (single-field)')).toBeVisible() + await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() + + // ensure no edit icons exist for read-only CM + await expect( + await canvas.queryByTestId('MoreHorizIcon'), + ).not.toBeInTheDocument() + }, +} + +export const NonCurrentUser: Story = { + args: { + userID: '00000000-0000-0000-0000-000000000001', + readOnly: false, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + + // ensure non-current user cannot send test to user CM + await userEvent.click(await canvas.findByTestId('MoreHorizIcon')) + await expect(await screen.findByText('Send Test')).toHaveAttribute( + 'aria-disabled', + 'true', + ) }, } From bd2baeb5f355e7dc7f3db610e200c284e3d7bba4 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Wed, 14 Feb 2024 13:52:57 -0600 Subject: [PATCH 04/17] remove console log --- web/src/app/users/UserContactMethodListDest.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index bcff8c3e80..7b2f355972 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -91,7 +91,6 @@ export default function UserContactMethodListDest( if (error) return const contactMethods = data.user.contactMethods - console.log(data) const getIcon = (cm: UserContactMethod): JSX.Element | null => { if (!cm.disabled) return null From 196766c1efc2d37615d3318a3dae3cfb192d22a2 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Wed, 14 Feb 2024 13:55:04 -0600 Subject: [PATCH 05/17] unify into one function --- .../app/users/UserContactMethodListDest.tsx | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index 7b2f355972..147ca5ab37 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -173,28 +173,24 @@ export default function UserContactMethodListDest( ) } - function getSubText(v: FieldValuePair, cm: UserContactMethod): JSX.Element { - let cmText = v.label - if (cm.pending) { - cmText = `${cmText} - this contact method will be automatically deleted if not verified` - } - if (v.fieldID === 'webhook-url') { - return ( - - {`${cmText} (`} - docs) - - ) - } - - return {cmText} - } - - function getSubTexts(cm: UserContactMethod): JSX.Element { + function getSubText(cm: UserContactMethod): JSX.Element { return ( {cm.dest.values.map((v) => { - return getSubText(v, cm) + let cmText = v.label + if (cm.pending) { + cmText = `${cmText} - this contact method will be automatically deleted if not verified` + } + if (v.fieldID === 'webhook-url') { + return ( + + {`${cmText} (`} + docs) + + ) + } + + return {cmText} })} ) @@ -224,7 +220,7 @@ export default function UserContactMethodListDest( data-cy='contact-methods' items={sortContactMethods(contactMethods).map((cm) => ({ title: `${cm.name} (${cm.dest.type})${cm.disabled ? ' - Disabled' : ''}`, - subText: getSubTexts(cm), + subText: getSubText(cm), secondaryAction: getSecondaryAction(cm), icon: getIcon(cm), }))} From 02f6659ceb9c345628ad5eda0f7e36897bd2aa2a Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Wed, 14 Feb 2024 14:03:43 -0600 Subject: [PATCH 06/17] remove non-current user test --- .../UserContactMethodListDest.stories.tsx | 218 ++++++------------ .../app/users/UserContactMethodListDest.tsx | 2 +- 2 files changed, 76 insertions(+), 144 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index b5014cf655..abc4f310df 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -5,130 +5,6 @@ import { expect, within, userEvent, screen } from '@storybook/test' import { handleDefaultConfig, handleExpFlags } from '../storybook/graphql' import { HttpResponse, graphql } from 'msw' -interface UserCMResponse { - user: { - id: string - contactMethods: { - id: string - name: string - dest: { - type: string - values: { - fieldID: string - value: string - label: string - }[] - } - disabled: boolean - pending: boolean - }[] - } -} - -function getUserCMData(userID: string): UserCMResponse | undefined { - if (userID === '00000000-0000-0000-0000-000000000000') { - return { - user: { - id: '00000000-0000-0000-0000-000000000000', - contactMethods: [ - { - id: '12345', - name: 'Josiah', - dest: { - type: 'single-field', - values: [ - { - fieldID: 'phone-number', - value: '+15555555555', - label: '+1 555-555-5555', - }, - ], - }, - disabled: false, - pending: false, - }, - ], - }, - } - } - if (userID === '00000000-0000-0000-0000-000000000001') { - return { - user: { - id: '00000000-0000-0000-0000-000000000001', - contactMethods: [ - { - id: '12345', - name: 'non_current_user', - dest: { - type: 'single-field', - values: [ - { - fieldID: 'phone-number', - value: '+15555555555', - label: '+1 555-555-5555', - }, - ], - }, - disabled: false, - pending: false, - }, - ], - }, - } - } - if (userID === '00000000-0000-0000-0000-000000000002') { - return { - user: { - id: '00000000-0000-0000-0000-000000000002', - contactMethods: [ - { - id: '67890', - name: 'triple contact method', - dest: { - type: 'triple-field', - values: [ - { - fieldID: 'first-field', - value: '+15555555555', - label: '+1 555-555-5555', - }, - { - fieldID: 'second-field', - value: 'test_user@target.com', - label: 'test_user@target.com', - }, - { - fieldID: 'third-field', - value: 'U03SM0U8TPE', - label: '@TestUser', - }, - ], - }, - disabled: false, - pending: false, - }, - { - id: '1111', - name: 'my_webhook', - dest: { - type: 'webhook-field', - values: [ - { - fieldID: 'webhook-url', - value: 'https://target.com', - label: 'https://target.com', - }, - ], - }, - disabled: false, - pending: false, - }, - ], - }, - } - } -} - const meta = { title: 'users/UserContactMethodListDest', component: UserContactMethodListDest, @@ -140,7 +16,80 @@ const meta = { handleExpFlags('dest-types'), graphql.query('cmList', ({ variables: vars }) => { return HttpResponse.json({ - data: getUserCMData(vars.id), + data: + vars.id === '00000000-0000-0000-0000-000000000000' + ? { + user: { + id: '00000000-0000-0000-0000-000000000000', + contactMethods: [ + { + id: '12345', + name: 'Josiah', + dest: { + type: 'single-field', + values: [ + { + fieldID: 'phone-number', + value: '+15555555555', + label: '+1 555-555-5555', + }, + ], + }, + disabled: false, + pending: false, + }, + ], + }, + } + : { + user: { + id: '00000000-0000-0000-0000-000000000001', + contactMethods: [ + { + id: '67890', + name: 'triple contact method', + dest: { + type: 'triple-field', + values: [ + { + fieldID: 'first-field', + value: '+15555555555', + label: '+1 555-555-5555', + }, + { + fieldID: 'second-field', + value: 'test_user@target.com', + label: 'test_user@target.com', + }, + { + fieldID: 'third-field', + value: 'U03SM0U8TPE', + label: '@TestUser', + }, + ], + }, + disabled: false, + pending: false, + }, + { + id: '1111', + name: 'my_webhook', + dest: { + type: 'webhook-field', + values: [ + { + fieldID: 'webhook-url', + value: 'https://target.com', + label: 'https://target.com', + }, + ], + }, + disabled: false, + pending: false, + }, + ], + }, + }, }) }), ], @@ -190,7 +139,7 @@ export const SingleContactMethod: Story = { export const MultiContactMethods: Story = { args: { - userID: '00000000-0000-0000-0000-000000000002', + userID: '00000000-0000-0000-0000-000000000001', readOnly: false, }, play: async ({ canvasElement }) => { @@ -236,20 +185,3 @@ export const SingleReadOnlyContactMethods: Story = { ).not.toBeInTheDocument() }, } - -export const NonCurrentUser: Story = { - args: { - userID: '00000000-0000-0000-0000-000000000001', - readOnly: false, - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement) - - // ensure non-current user cannot send test to user CM - await userEvent.click(await canvas.findByTestId('MoreHorizIcon')) - await expect(await screen.findByText('Send Test')).toHaveAttribute( - 'aria-disabled', - 'true', - ) - }, -} diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index 147ca5ab37..4c75880f4c 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -23,7 +23,7 @@ import { GenericError, ObjectNotFound } from '../error-pages' import SendTestDialog from './SendTestDialog' import AppLink from '../util/AppLink' import { styles as globalStyles } from '../styles/materialStyles' -import { FieldValuePair, UserContactMethod } from '../../schema' +import { UserContactMethod } from '../../schema' import UserContactMethodCreateDialog from './UserContactMethodCreateDialog' import { useSessionInfo } from '../util/RequireConfig' From a8535fa35dfe1bb9f333d48fa69b90477a2ce183 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 13:25:55 -0600 Subject: [PATCH 07/17] remove hard coded dest usage --- web/src/app/storybook/defaultDestTypes.ts | 4 +- .../UserContactMethodListDest.stories.tsx | 10 ++-- .../app/users/UserContactMethodListDest.tsx | 47 ++++++++++++++----- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/web/src/app/storybook/defaultDestTypes.ts b/web/src/app/storybook/defaultDestTypes.ts index 6bcdbfe09e..ec1ea036e7 100644 --- a/web/src/app/storybook/defaultDestTypes.ts +++ b/web/src/app/storybook/defaultDestTypes.ts @@ -183,8 +183,8 @@ export const destTypes: DestinationTypeInfo[] = [ fieldID: 'webhook-url', labelSingular: 'Webhook Url', labelPlural: '', - hint: 'Some hint text', - hintURL: '', + hint: 'Webhook Documentation', + hintURL: '/docs#webhooks', placeholderText: 'https://target.com', prefix: '', inputType: 'url', diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index abc4f310df..d4f9ccfd0d 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -112,7 +112,7 @@ export const SingleContactMethod: Story = { const canvas = within(canvasElement) // ensure correct info is displayed for single-field CM - await expect(await canvas.findByText('Josiah (single-field)')).toBeVisible() + await expect(await canvas.findByText('Josiah (Phone Number)')).toBeVisible() await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() // ensure CM is editable @@ -147,16 +147,16 @@ export const MultiContactMethods: Story = { // ensure correct info is displayed for webhook CM await expect( - await canvas.findByText('my_webhook (webhook-field)'), + await canvas.findByText('my_webhook (Webhook Url)'), ).toBeVisible() - await expect(canvas.getByText('docs')).toHaveAttribute( + await expect(canvas.getByText('Webhook Documentation')).toHaveAttribute( 'href', '/docs#webhooks', ) // ensure correct info is displayed for triple-field CM await expect( - await canvas.findByText('triple contact method (triple-field)'), + await canvas.findByText('triple contact method (First Item)'), ).toBeVisible() await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() await expect(await canvas.findByText('test_user@target.com')).toBeVisible() @@ -176,7 +176,7 @@ export const SingleReadOnlyContactMethods: Story = { const canvas = within(canvasElement) // ensure correct info is displayed for single-field CM - await expect(await canvas.findByText('Josiah (single-field)')).toBeVisible() + await expect(await canvas.findByText('Josiah (Phone Number)')).toBeVisible() await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() // ensure no edit icons exist for read-only CM diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index 4c75880f4c..289073d495 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -23,9 +23,13 @@ import { GenericError, ObjectNotFound } from '../error-pages' import SendTestDialog from './SendTestDialog' import AppLink from '../util/AppLink' import { styles as globalStyles } from '../styles/materialStyles' -import { UserContactMethod } from '../../schema' +import { + DestinationFieldConfig, + FieldValuePair, + UserContactMethod, +} from '../../schema' import UserContactMethodCreateDialog from './UserContactMethodCreateDialog' -import { useSessionInfo } from '../util/RequireConfig' +import { useSessionInfo, useContactMethodTypes } from '../util/RequireConfig' const query = gql` query cmList($id: ID!) { @@ -41,6 +45,12 @@ const query = gql` value label } + displayInfo { + text + iconURL + iconAltText + linkURL + } } disabled pending @@ -76,6 +86,7 @@ export default function UserContactMethodListDest( const [showEditDialogByID, setShowEditDialogByID] = useState('') const [showDeleteDialogByID, setShowDeleteDialogByID] = useState('') const [showSendTestByID, setShowSendTestByID] = useState('') + const destinationTypes = useContactMethodTypes() const [{ error, data }] = useQuery({ query, @@ -173,7 +184,10 @@ export default function UserContactMethodListDest( ) } - function getSubText(cm: UserContactMethod): JSX.Element { + function getSubText( + cm: UserContactMethod, + fieldInfo: DestinationFieldConfig | undefined, + ): JSX.Element { return ( {cm.dest.values.map((v) => { @@ -181,11 +195,11 @@ export default function UserContactMethodListDest( if (cm.pending) { cmText = `${cmText} - this contact method will be automatically deleted if not verified` } - if (v.fieldID === 'webhook-url') { + if (fieldInfo?.hintURL) { return ( {`${cmText} (`} - docs) + {fieldInfo.hint}) ) } @@ -218,12 +232,23 @@ export default function UserContactMethodListDest( /> ({ - title: `${cm.name} (${cm.dest.type})${cm.disabled ? ' - Disabled' : ''}`, - subText: getSubText(cm), - secondaryAction: getSecondaryAction(cm), - icon: getIcon(cm), - }))} + items={sortContactMethods(contactMethods).map((cm) => { + const destType = destinationTypes.find( + (d) => d.type === cm.dest.type, + ) + const fieldInfo = destType?.requiredFields.find((rf) => + cm.dest.values.find( + (f: FieldValuePair) => f.fieldID === rf.fieldID, + ), + ) + + return { + title: `${cm.name} (${fieldInfo?.labelSingular})${cm.disabled ? ' - Disabled' : ''}`, + subText: getSubText(cm, fieldInfo), + secondaryAction: getSecondaryAction(cm), + icon: getIcon(cm), + } + })} emptyMessage='No contact methods' /> {showAddDialog && ( From 598065768e7e5b25de1cb0d7d91a167746ba4632 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 13:59:49 -0600 Subject: [PATCH 08/17] fix issue where hint url not displaying for mult field dests --- .../app/users/UserContactMethodListDest.tsx | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index 289073d495..ab8e760aa0 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -184,13 +184,23 @@ export default function UserContactMethodListDest( ) } - function getSubText( - cm: UserContactMethod, - fieldInfo: DestinationFieldConfig | undefined, - ): JSX.Element { + function getFieldInfo( + destType: string, + fieldID: string, + ): DestinationFieldConfig | undefined { + const destInfo = destinationTypes.find((d) => d.type === destType) + const fieldInfo = destInfo?.requiredFields.find( + (rf) => fieldID === rf.fieldID, + ) + return fieldInfo + } + + function getSubText(cm: UserContactMethod): JSX.Element { return ( {cm.dest.values.map((v) => { + const fieldInfo = getFieldInfo(cm.dest.type, v.fieldID) + let cmText = v.label if (cm.pending) { cmText = `${cmText} - this contact method will be automatically deleted if not verified` @@ -233,18 +243,18 @@ export default function UserContactMethodListDest( { - const destType = destinationTypes.find( - (d) => d.type === cm.dest.type, - ) - const fieldInfo = destType?.requiredFields.find((rf) => - cm.dest.values.find( - (f: FieldValuePair) => f.fieldID === rf.fieldID, - ), - ) + // get field info for first matched required field value for contact method (for CM title only) + const fieldInfo = destinationTypes + .find((d) => d.type === cm.dest.type) + ?.requiredFields.find((rf) => + cm.dest.values.find( + (f: FieldValuePair) => f.fieldID === rf.fieldID, + ), + ) return { title: `${cm.name} (${fieldInfo?.labelSingular})${cm.disabled ? ' - Disabled' : ''}`, - subText: getSubText(cm, fieldInfo), + subText: getSubText(cm), secondaryAction: getSecondaryAction(cm), icon: getIcon(cm), } From c67fc45ff39d40abbf94fcde3af5d27ba30655f1 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 13:59:56 -0600 Subject: [PATCH 09/17] combine test assertions --- web/src/app/storybook/defaultDestTypes.ts | 32 ++----------------- .../UserContactMethodListDest.stories.tsx | 24 +++++++------- 2 files changed, 13 insertions(+), 43 deletions(-) diff --git a/web/src/app/storybook/defaultDestTypes.ts b/web/src/app/storybook/defaultDestTypes.ts index ec1ea036e7..3a583ae75d 100644 --- a/web/src/app/storybook/defaultDestTypes.ts +++ b/web/src/app/storybook/defaultDestTypes.ts @@ -71,8 +71,8 @@ export const destTypes: DestinationTypeInfo[] = [ fieldID: 'third-field', labelSingular: 'Third Item', labelPlural: 'Third Items', - hint: '', - hintURL: '', + hint: 'docs', + hintURL: '/docs', placeholderText: 'slack user ID', prefix: '', inputType: 'string', @@ -165,32 +165,4 @@ export const destTypes: DestinationTypeInfo[] = [ }, ], }, - { - type: 'webhook-field', - name: 'Webhook Field Destination Type', - enabled: true, - disabledMessage: 'Webhook field destination type must be configured.', - userDisclaimer: '', - isContactMethod: true, - isEPTarget: false, - isSchedOnCallNotify: false, - iconURL: '', - iconAltText: '', - supportsStatusUpdates: true, - statusUpdatesRequired: false, - requiredFields: [ - { - fieldID: 'webhook-url', - labelSingular: 'Webhook Url', - labelPlural: '', - hint: 'Webhook Documentation', - hintURL: '/docs#webhooks', - placeholderText: 'https://target.com', - prefix: '', - inputType: 'url', - isSearchSelectable: false, - supportsValidation: false, - }, - ], - }, ] diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index d4f9ccfd0d..fe90e80679 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -73,14 +73,14 @@ const meta = { }, { id: '1111', - name: 'my_webhook', + name: 'single field CM', dest: { - type: 'webhook-field', + type: 'single-field', values: [ { - fieldID: 'webhook-url', - value: 'https://target.com', - label: 'https://target.com', + fieldID: 'phone-number', + value: '+15555555556', + label: '+1 555-555-5556', }, ], }, @@ -145,22 +145,20 @@ export const MultiContactMethods: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement) - // ensure correct info is displayed for webhook CM + // ensure correct info is displayed for first CM await expect( - await canvas.findByText('my_webhook (Webhook Url)'), + await canvas.findByText('single field CM (Phone Number)'), ).toBeVisible() - await expect(canvas.getByText('Webhook Documentation')).toHaveAttribute( - 'href', - '/docs#webhooks', - ) // ensure correct info is displayed for triple-field CM await expect( await canvas.findByText('triple contact method (First Item)'), ).toBeVisible() - await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() + await expect(await canvas.findByText('+1 555-555-5556')).toBeVisible() await expect(await canvas.findByText('test_user@target.com')).toBeVisible() - await expect(await canvas.findByText('@TestUser')).toBeVisible() + + // ensure has link when hint url exists + await expect(canvas.getByText('docs')).toHaveAttribute('href', '/docs') // ensure all edit icons exists await expect(await canvas.findAllByTestId('MoreHorizIcon')).toHaveLength(2) From 1141ad63b0cf24d029bbeaabd0f82615424d286f Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 14:01:44 -0600 Subject: [PATCH 10/17] remove repeated assertions --- web/src/app/users/UserContactMethodListDest.stories.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index fe90e80679..378a4e09b5 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -173,10 +173,6 @@ export const SingleReadOnlyContactMethods: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement) - // ensure correct info is displayed for single-field CM - await expect(await canvas.findByText('Josiah (Phone Number)')).toBeVisible() - await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() - // ensure no edit icons exist for read-only CM await expect( await canvas.queryByTestId('MoreHorizIcon'), From e2573e68815f21ab0ee7e9ec94445bc35c86e71e Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 14:08:14 -0600 Subject: [PATCH 11/17] update comment to trigger github action --- web/src/app/users/UserContactMethodListDest.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index 378a4e09b5..035339ff2c 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -145,7 +145,7 @@ export const MultiContactMethods: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement) - // ensure correct info is displayed for first CM + // ensure correct info is displayed for single field CM await expect( await canvas.findByText('single field CM (Phone Number)'), ).toBeVisible() From c90b82b156512833f371664f279c4da2dc76576f Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 14:10:31 -0600 Subject: [PATCH 12/17] remove lines to meet pr size limit --- web/src/app/users/UserContactMethodListDest.stories.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index 035339ff2c..eee2703648 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -114,12 +114,10 @@ export const SingleContactMethod: Story = { // ensure correct info is displayed for single-field CM await expect(await canvas.findByText('Josiah (Phone Number)')).toBeVisible() await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() - // ensure CM is editable await expect( await canvas.queryByTestId('MoreHorizIcon'), ).toBeInTheDocument() - // ensure all edit options are available await userEvent.click(await canvas.findByTestId('MoreHorizIcon')) await expect(await screen.findByText('Edit')).toHaveAttribute( @@ -149,17 +147,14 @@ export const MultiContactMethods: Story = { await expect( await canvas.findByText('single field CM (Phone Number)'), ).toBeVisible() - // ensure correct info is displayed for triple-field CM await expect( await canvas.findByText('triple contact method (First Item)'), ).toBeVisible() await expect(await canvas.findByText('+1 555-555-5556')).toBeVisible() await expect(await canvas.findByText('test_user@target.com')).toBeVisible() - // ensure has link when hint url exists await expect(canvas.getByText('docs')).toHaveAttribute('href', '/docs') - // ensure all edit icons exists await expect(await canvas.findAllByTestId('MoreHorizIcon')).toHaveLength(2) }, From 1265707f5cc710a53ab6aaee43c10b074a7a2efd Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 14:18:56 -0600 Subject: [PATCH 13/17] remove function --- web/src/app/users/UserContactMethodListDest.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index ab8e760aa0..b0fd36fbec 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -184,22 +184,13 @@ export default function UserContactMethodListDest( ) } - function getFieldInfo( - destType: string, - fieldID: string, - ): DestinationFieldConfig | undefined { - const destInfo = destinationTypes.find((d) => d.type === destType) - const fieldInfo = destInfo?.requiredFields.find( - (rf) => fieldID === rf.fieldID, - ) - return fieldInfo - } - function getSubText(cm: UserContactMethod): JSX.Element { return ( {cm.dest.values.map((v) => { - const fieldInfo = getFieldInfo(cm.dest.type, v.fieldID) + const fieldInfo = destinationTypes + .find((d) => d.type === cm.dest.type) + ?.requiredFields.find((rf) => v.fieldID === rf.fieldID) let cmText = v.label if (cm.pending) { From 7b9a86a66b59ceac5797488322cb022097b83607 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 14:25:33 -0600 Subject: [PATCH 14/17] fix CM title label --- .../UserContactMethodListDest.stories.tsx | 4 ++- .../app/users/UserContactMethodListDest.tsx | 28 +++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index eee2703648..7c57aa11ec 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -149,7 +149,9 @@ export const MultiContactMethods: Story = { ).toBeVisible() // ensure correct info is displayed for triple-field CM await expect( - await canvas.findByText('triple contact method (First Item)'), + await canvas.findByText( + 'triple contact method (Multi Field Destination Type)', + ), ).toBeVisible() await expect(await canvas.findByText('+1 555-555-5556')).toBeVisible() await expect(await canvas.findByText('test_user@target.com')).toBeVisible() diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index b0fd36fbec..3d3379b412 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -23,11 +23,7 @@ import { GenericError, ObjectNotFound } from '../error-pages' import SendTestDialog from './SendTestDialog' import AppLink from '../util/AppLink' import { styles as globalStyles } from '../styles/materialStyles' -import { - DestinationFieldConfig, - FieldValuePair, - UserContactMethod, -} from '../../schema' +import { FieldValuePair, UserContactMethod } from '../../schema' import UserContactMethodCreateDialog from './UserContactMethodCreateDialog' import { useSessionInfo, useContactMethodTypes } from '../util/RequireConfig' @@ -234,17 +230,21 @@ export default function UserContactMethodListDest( { - // get field info for first matched required field value for contact method (for CM title only) - const fieldInfo = destinationTypes - .find((d) => d.type === cm.dest.type) - ?.requiredFields.find((rf) => - cm.dest.values.find( - (f: FieldValuePair) => f.fieldID === rf.fieldID, - ), - ) + const destType = destinationTypes.find( + (d) => d.type === cm.dest.type, + ) + + const labelTitle = + destType?.requiredFields.length === 1 + ? destType?.requiredFields.find((rf) => + cm.dest.values.find( + (f: FieldValuePair) => f.fieldID === rf.fieldID, + ), + )?.labelSingular + : destType?.name return { - title: `${cm.name} (${fieldInfo?.labelSingular})${cm.disabled ? ' - Disabled' : ''}`, + title: `${cm.name} (${labelTitle})${cm.disabled ? ' - Disabled' : ''}`, subText: getSubText(cm), secondaryAction: getSecondaryAction(cm), icon: getIcon(cm), From 5009233a0d8ac0bcb33961bda51f8c84f520cc70 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Thu, 15 Feb 2024 14:26:26 -0600 Subject: [PATCH 15/17] rename variable --- web/src/app/users/UserContactMethodListDest.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index 3d3379b412..bb9969a3a1 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -234,7 +234,7 @@ export default function UserContactMethodListDest( (d) => d.type === cm.dest.type, ) - const labelTitle = + const label = destType?.requiredFields.length === 1 ? destType?.requiredFields.find((rf) => cm.dest.values.find( @@ -244,7 +244,7 @@ export default function UserContactMethodListDest( : destType?.name return { - title: `${cm.name} (${labelTitle})${cm.disabled ? ' - Disabled' : ''}`, + title: `${cm.name} (${label})${cm.disabled ? ' - Disabled' : ''}`, subText: getSubText(cm), secondaryAction: getSecondaryAction(cm), icon: getIcon(cm), From 15b072b4b5516e25585669753d8d75e444f420c3 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 19 Feb 2024 15:18:51 -0600 Subject: [PATCH 16/17] use destType.name --- web/src/app/users/UserContactMethodListDest.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.tsx b/web/src/app/users/UserContactMethodListDest.tsx index bb9969a3a1..c058a83db0 100644 --- a/web/src/app/users/UserContactMethodListDest.tsx +++ b/web/src/app/users/UserContactMethodListDest.tsx @@ -23,7 +23,7 @@ import { GenericError, ObjectNotFound } from '../error-pages' import SendTestDialog from './SendTestDialog' import AppLink from '../util/AppLink' import { styles as globalStyles } from '../styles/materialStyles' -import { FieldValuePair, UserContactMethod } from '../../schema' +import { UserContactMethod } from '../../schema' import UserContactMethodCreateDialog from './UserContactMethodCreateDialog' import { useSessionInfo, useContactMethodTypes } from '../util/RequireConfig' @@ -234,14 +234,7 @@ export default function UserContactMethodListDest( (d) => d.type === cm.dest.type, ) - const label = - destType?.requiredFields.length === 1 - ? destType?.requiredFields.find((rf) => - cm.dest.values.find( - (f: FieldValuePair) => f.fieldID === rf.fieldID, - ), - )?.labelSingular - : destType?.name + const label = destType?.name || 'Unknown Type' return { title: `${cm.name} (${label})${cm.disabled ? ' - Disabled' : ''}`, From 9ac31dc27626761020c08b6c73a793fe0b0c033c Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 19 Feb 2024 16:25:55 -0600 Subject: [PATCH 17/17] update story text --- web/src/app/users/UserContactMethodListDest.stories.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/web/src/app/users/UserContactMethodListDest.stories.tsx b/web/src/app/users/UserContactMethodListDest.stories.tsx index 7c57aa11ec..440874e597 100644 --- a/web/src/app/users/UserContactMethodListDest.stories.tsx +++ b/web/src/app/users/UserContactMethodListDest.stories.tsx @@ -112,7 +112,9 @@ export const SingleContactMethod: Story = { const canvas = within(canvasElement) // ensure correct info is displayed for single-field CM - await expect(await canvas.findByText('Josiah (Phone Number)')).toBeVisible() + await expect( + await canvas.findByText('Josiah (Single Field Destination Type)'), + ).toBeVisible() await expect(await canvas.findByText('+1 555-555-5555')).toBeVisible() // ensure CM is editable await expect( @@ -145,7 +147,9 @@ export const MultiContactMethods: Story = { // ensure correct info is displayed for single field CM await expect( - await canvas.findByText('single field CM (Phone Number)'), + await canvas.findByText( + 'single field CM (Single Field Destination Type)', + ), ).toBeVisible() // ensure correct info is displayed for triple-field CM await expect(