implement delete for conversation messages

fixes VICE-2143
flag=react_inbox

Test Plan:
- Enable the react inbox FF
- Navigate to the inbox
- Delete a conversation message in a conversation
- It should work

Change-Id: Ie2c11d60edf8d4e8f05643b66ce7acb255783b2b
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/276908
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Chawn Neal <chawn.neal@instructure.com>
QA-Review: Chawn Neal <chawn.neal@instructure.com>
Product-Review: Chawn Neal <chawn.neal@instructure.com>
This commit is contained in:
Matthew Lemon 2021-10-27 08:46:13 -06:00
parent 74bffb22f0
commit 51e5cb10c8
10 changed files with 92 additions and 11 deletions

View File

@ -26,6 +26,7 @@ export const Conversation = {
fragment: gql`
fragment Conversation on Conversation {
_id
id
contextId
contextType
contextName
@ -47,6 +48,7 @@ export const Conversation = {
shape: shape({
_id: string,
id: string,
contextId: string,
contextType: string,
contextName: string,
@ -61,6 +63,7 @@ export const Conversation = {
mock: ({
_id = '196',
id = 'Q29udmVyc2F0aW9uLTE5Ng==',
contextId = '195',
contextType = 'Course',
contextName = 'XavierSchool',
@ -118,6 +121,7 @@ export const Conversation = {
}
} = {}) => ({
_id,
id,
contextId,
contextType,
contextName,

View File

@ -136,3 +136,14 @@ export const ADD_CONVERSATION_MESSAGE = gql`
${Error.fragment}
${ConversationMessage.fragment}
`
export const DELETE_CONVERSATION_MESSAGES = gql`
mutation DeleteConversationMessages($ids: [ID!]!) {
deleteConversationMessages(input: {ids: $ids}) {
conversationMessageIds
errors {
message
}
}
}
`

View File

@ -66,6 +66,7 @@ export const handlers = [
}),
conversation: Conversation.mock({
_id: '195',
id: 'Q29udmVyc2F0aW9uLTE5NQ==',
subject: 'h1'
})
}
@ -99,6 +100,7 @@ export const handlers = [
...ConversationParticipant.mock({_id: '123', id: 'Q29udmVyc2F0aW9uUGFydGljaXBhbnQtMTA='}),
conversation: Conversation.mock({
_id: '10',
id: 'Q29udmVyc2F0aW9uLTEw',
subject: 'This is a course scoped conversation'
})
}
@ -112,7 +114,11 @@ export const handlers = [
{_id: '256', id: 'Q29udmVyc2F0aW9uUGFydGljaXBhbnQtMjU2', workflowState: 'unread'},
{_id: '257', id: 'Q29udmVyc2F0aW9uUGFydGljaXBhbnQtMjU4', workflowState: 'unread'}
),
conversation: Conversation.mock({_id: '197', subject: 'This is an inbox conversation'})
conversation: Conversation.mock({
_id: '197',
id: 'Q29udmVyc2F0aW9uLTE5Nw==',
subject: 'This is an inbox conversation'
})
}
]
data.legacyNode.conversationsConnection.nodes[0].conversation.conversationMessagesConnection.nodes =

View File

@ -58,7 +58,9 @@ export const MessageDetailActions = ({...props}) => {
{I18n.t('Reply All')}
</Menu.Item>
<Menu.Item value="forward">{I18n.t('Forward')}</Menu.Item>
<Menu.Item value="delete">{I18n.t('Delete')}</Menu.Item>
<Menu.Item value="delete" onSelect={props.onDelete}>
{I18n.t('Delete')}
</Menu.Item>
</Menu>
</>
)
@ -66,5 +68,6 @@ export const MessageDetailActions = ({...props}) => {
MessageDetailActions.propTypes = {
onReply: PropTypes.func,
onReplyAll: PropTypes.func
onReplyAll: PropTypes.func,
onDelete: PropTypes.func
}

View File

@ -25,7 +25,8 @@ export default {
component: MessageDetailActions,
argTypes: {
onReply: {action: 'reply'},
onReplyAll: {action: 'replyAll'}
onReplyAll: {action: 'replyAll'},
onDelete: {action: 'delete'}
}
}

View File

@ -24,7 +24,8 @@ describe('MessageDetailItem', () => {
it('sends the selected option to the provided callback function', () => {
const props = {
onReply: jest.fn(),
onReplyAll: jest.fn()
onReplyAll: jest.fn(),
onDelete: jest.fn()
}
const {getByRole, getByText} = render(<MessageDetailActions {...props} />)
@ -40,5 +41,9 @@ describe('MessageDetailItem', () => {
fireEvent.click(moreOptionsButton)
fireEvent.click(getByText('Reply All'))
expect(props.onReplyAll).toHaveBeenCalled()
fireEvent.click(moreOptionsButton)
fireEvent.click(getByText('Delete'))
expect(props.onDelete).toHaveBeenCalled()
})
})

View File

@ -64,7 +64,11 @@ export const MessageDetailItem = ({...props}) => {
<View as="div" margin="none none x-small">
<Text weight="light">{createdAt}</Text>
</View>
<MessageDetailActions onReply={props.onReply} onReplyAll={props.onReplyAll} />
<MessageDetailActions
onReply={props.onReply}
onReplyAll={props.onReplyAll}
onDelete={props.onDelete}
/>
</Flex.Item>
</Flex>
<Text>{props.conversationMessage.body}</Text>
@ -93,7 +97,8 @@ MessageDetailItem.propTypes = {
conversationMessage: PropTypes.object,
contextName: PropTypes.string,
onReply: PropTypes.func,
onReplyAll: PropTypes.func
onReplyAll: PropTypes.func,
onDelete: PropTypes.func
}
MessageDetailItem.defaultProps = {

View File

@ -25,7 +25,8 @@ export default {
component: MessageDetailItem,
argTypes: {
onReply: {action: 'reply'},
onReplyAll: {action: 'replyAll'}
onReplyAll: {action: 'replyAll'},
onDelete: {action: 'delete'}
}
}

View File

@ -79,7 +79,8 @@ describe('MessageDetailItem', () => {
},
contextName: 'Fake Course 1',
onReply: jest.fn(),
onReplyAll: jest.fn()
onReplyAll: jest.fn(),
onDelete: jest.fn()
}
const {getByTestId, getByText} = render(<MessageDetailItem {...props} />)
@ -92,5 +93,9 @@ describe('MessageDetailItem', () => {
fireEvent.click(moreOptionsButton)
fireEvent.click(getByText('Reply All'))
expect(props.onReplyAll).toHaveBeenCalled()
fireEvent.click(moreOptionsButton)
fireEvent.click(getByText('Delete'))
expect(props.onDelete).toHaveBeenCalled()
})
})

View File

@ -16,15 +16,54 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {AlertManagerContext} from '@canvas/alerts/react/AlertManager'
import {Conversation} from '../../../graphql/Conversation'
import {DELETE_CONVERSATION_MESSAGES} from '../../../graphql/Mutations'
import I18n from 'i18n!conversations_2'
import {MessageDetailHeader} from '../../components/MessageDetailHeader/MessageDetailHeader'
import {MessageDetailItem} from '../../components/MessageDetailItem/MessageDetailItem'
import PropTypes from 'prop-types'
import React from 'react'
import React, {useContext} from 'react'
import {useMutation} from 'react-apollo'
import {View} from '@instructure/ui-view'
export const MessageDetailContainer = props => {
const {setOnFailure, setOnSuccess} = useContext(AlertManagerContext)
const removeConversationMessagesFromCache = (cache, result) => {
const data = JSON.parse(
JSON.stringify(
cache.readFragment({
id: props.conversation.id,
fragment: Conversation.fragment,
fragmentName: 'Conversation'
})
)
)
data.conversationMessagesConnection.nodes = data.conversationMessagesConnection.nodes.filter(
message =>
!result.data.deleteConversationMessages.conversationMessageIds.includes(message._id)
)
cache.writeFragment({
id: props.conversation.id,
fragment: Conversation.fragment,
fragmentName: 'Conversation',
data
})
}
const [deleteConversationMessages] = useMutation(DELETE_CONVERSATION_MESSAGES, {
update: removeConversationMessagesFromCache,
onCompleted() {
setOnSuccess(I18n.t('Successfully deleted the conversation message'))
},
onError() {
setOnFailure(I18n.t('There was an unexpected error deleting the conversation message'))
}
})
return (
<>
<MessageDetailHeader
@ -39,6 +78,7 @@ export const MessageDetailContainer = props => {
context={props.conversation.contextName}
onReply={() => props.onReply(message)}
onReplyAll={() => props.onReplyAll(message)}
onDelete={() => deleteConversationMessages({variables: {ids: [message._id]}})}
/>
</View>
))}