Skip to content

Commit

Permalink
Feature: Desktop View - Add an explicit discard article button to the…
Browse files Browse the repository at this point in the history
… article reply form
  • Loading branch information
vBenTec committed Sep 27, 2024
1 parent cf7938e commit 4a7e36b
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -674,5 +674,281 @@ describe('Ticket detail view', () => {
view.queryByRole('textbox', { name: 'Text' }),
).not.toBeInTheDocument()
})

it('discards reply form and it keeps the ticket attribute fields state', async () => {
mockTicketQuery({
ticket: createDummyTicket({
articleType: 'phone',
defaultPolicy: {
update: true,
agentReadAccess: true,
},
}),
})

mockTicketArticlesQuery({
articles: {
totalCount: 1,
edges: [
{
node: createDummyArticle({
articleType: 'phone',
internal: false,
}),
},
],
},
})

mockFormUpdaterQuery({
formUpdater: {
fields: {
group_id: {
options: [
{
value: 1,
label: 'Users',
},
{
value: 2,
label: 'test group',
},
],
},
owner_id: {
options: [
{
value: 3,
label: 'Test Admin Agent',
},
],
},
state_id: {
options: [
{
value: 4,
label: 'closed',
},
{
value: 2,
label: 'open',
},
{
value: 6,
label: 'pending close',
},
{
value: 3,
label: 'pending reminder',
},
],
},
pending_time: {
show: false,
},
priority_id: {
options: [
{
value: 1,
label: '1 low',
},
{
value: 2,
label: '2 normal',
},
{
value: 3,
label: '3 high',
},
],
},
},
flags: {
newArticlePresent: false,
},
},
})

const view = await visitView('/tickets/1')

// Discard changes inside the reply form
await view.events.click(
view.getByRole('button', { name: 'Add phone call' }),
)

expect(
await view.findByRole('heading', { level: 2, name: 'Reply' }),
).toBeInTheDocument()

// Sets dirty set for a ticket attribute
await view.events.click(view.getByLabelText('State'))
await view.events.click(
await view.findByRole('option', { name: 'closed' }),
)

await view.events.click(
view.getByRole('button', { name: 'Discard unsaved reply' }),
)

expect(
await view.findByRole('dialog', { name: 'Unsaved Changes' }),
).toBeInTheDocument()

await view.events.click(
view.getByRole('button', { name: 'Discard Changes' }),
)

// Verify that ticket attributes state is not lost
expect(view.getByLabelText('State')).toHaveTextContent('closed')
})

// TODO: Currently we have a problem in our resetForm-Function but also Formkit has an bug inside the own reset handling
// (null / false will currently ignored when setting back the initial value).
// So we will improve our own reset function and create an issue on FormKit side to fix this.
it.skip('discards complete form with an reply and afterwards only the reply directly', async () => {
mockTicketQuery({
ticket: createDummyTicket({
group: {
id: convertToGraphQLId('Group', 1),
emailAddress: {
name: 'Zammad Helpdesk',
emailAddress: 'zammad@localhost',
},
},
defaultPolicy: {
update: true,
agentReadAccess: true,
},
}),
})

mockTicketArticlesQuery({
articles: {
totalCount: 1,
edges: [
{
node: createDummyArticle({
articleType: 'phone',
internal: false,
}),
},
],
},
})

mockFormUpdaterQuery({
formUpdater: {
fields: {
group_id: {
options: [
{
value: 1,
label: 'Users',
},
{
value: 2,
label: 'test group',
},
],
},
owner_id: {
options: [
{
value: 3,
label: 'Test Admin Agent',
},
],
},
state_id: {
options: [
{
value: 4,
label: 'closed',
},
{
value: 2,
label: 'open',
},
{
value: 6,
label: 'pending close',
},
{
value: 3,
label: 'pending reminder',
},
],
},
pending_time: {
show: false,
},
priority_id: {
options: [
{
value: 1,
label: '1 low',
},
{
value: 2,
label: '2 normal',
},
{
value: 3,
label: '3 high',
},
],
},
},
flags: {
newArticlePresent: false,
},
},
})

const view = await visitView('/tickets/1')

// Discard changes inside the reply form
await view.events.click(view.getByRole('button', { name: 'Add reply' }))

await view.events.click(
await view.findByRole('button', {
name: 'Discard your unsaved changes',
}),
)

expect(
await view.findByRole('dialog', { name: 'Unsaved Changes' }),
).toBeInTheDocument()

await view.events.click(
view.getByRole('button', { name: 'Discard Changes' }),
)

expect(
view.queryByRole('button', {
name: 'Discard your unsaved changes',
}),
).not.toBeInTheDocument()

await view.events.click(view.getByRole('button', { name: 'Add reply' }))

await view.events.click(
view.getByRole('button', { name: 'Discard unsaved reply' }),
)

expect(
await view.findByRole('dialog', { name: 'Unsaved Changes' }),
).toBeInTheDocument()

await view.events.click(
view.getByRole('button', { name: 'Discard Changes' }),
)

expect(
view.queryByRole('button', {
name: 'Discard your unsaved changes',
}),
).not.toBeInTheDocument()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const emit = defineEmits<{
articleType: string,
performReply: AppSpecificTicketArticleType['performReply'],
]
'discard-form': []
}>()

const currentTicketArticleType = computed(() => {
Expand Down Expand Up @@ -218,7 +219,7 @@ defineExpose({
}"
>
<div
class="flex h-10 items-center justify-between p-3"
class="flex h-10 items-center p-3"
:class="{
'bg-neutral-50 dark:bg-gray-500': pinned,
'border-b border-b-transparent': pinned && articleFormReachedTop,
Expand All @@ -228,12 +229,19 @@ defineExpose({
>
<CommonLabel
id="article-reply-form-title"
class="text-stone-200 dark:text-neutral-500"
class="text-stone-200 ltr:mr-auto rtl:ml-auto dark:text-neutral-500"
tag="h2"
size="small"
>
{{ $t('Reply') }}
</CommonLabel>
<CommonButton
v-tooltip="$t('Discard unsaved reply')"
class="text-red-500 ltr:mr-2 rtl:ml-2"
variant="none"
icon="trash"
@click="$emit('discard-form')"
/>
<CommonButton
v-tooltip="pinned ? $t('Unpin this panel') : $t('Pin this panel')"
:icon="pinned ? 'pin' : 'pin-angle'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const useTicketArticleReply = (
initialNewTicketArticlePresent: Ref<boolean | undefined>,
) => {
const localNewTicketArticlePresent = ref<boolean>()
// TODO: swichting tabs when you added a new article is shortly showing the buttons (because taskbar tab don't has the information yet?)
// TODO: switching tabs when you added a new article is shortly showing the buttons (because taskbar tab don't has the information yet?)
const newTicketArticlePresent = computed({
get: () => {
if (localNewTicketArticlePresent.value !== undefined)
Expand Down
6 changes: 3 additions & 3 deletions app/frontend/apps/desktop/pages/ticket/views/TicketCreate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ interface Props {
tabId?: string
}

// - handover context to useTaskbarTab composable
// - Default output for TicketCreate-TabEntity without a "entity/state"

defineOptions({
beforeRouteEnter(to) {
const { ticketCreateEnabled, checkUniqueTicketCreateRoute } =
Expand Down Expand Up @@ -324,6 +321,9 @@ const discardChanges = async () => {
}

const applyTemplate = (templateId: string) => {
// Skip subscription for the current tab, to avoid not needed form updater requests.
setSkipNextStateUpdate(true)

triggerFormUpdater({
includeDirtyFields: true,
additionalParams: {
Expand Down
20 changes: 20 additions & 0 deletions app/frontend/apps/desktop/pages/ticket/views/TicketDetailView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ const discardChanges = async () => {
newTicketArticlePresent.value = false

await nextTick()

// Skip subscription for the current tab, to avoid not needed form updater requests.
setSkipNextStateUpdate(true)

formReset()
}
}
Expand Down Expand Up @@ -490,6 +494,21 @@ const submitEditTicket = async (
})
}

const discardReplyForm = async () => {
const confirm = await waitForVariantConfirmation('unsaved')

if (!confirm) return

newTicketArticlePresent.value = false

await nextTick()

// Skip subscription for the current tab, to avoid not needed form updater requests.
setSkipNextStateUpdate(true)

return triggerFormUpdater()
}

const handleShowArticleForm = (
articleType: string,
performReply: AppSpecificTicketArticleType['performReply'],
Expand Down Expand Up @@ -552,6 +571,7 @@ watch(ticketId, () => {
:has-internal-article="hasInternalArticle"
:parent-reached-bottom-scroll="reachedBottom"
@show-article-form="handleShowArticleForm"
@discard-form="discardReplyForm"
/>

<div id="wrapper-form-ticket-edit" class="hidden" aria-hidden="true">
Expand Down
Loading

0 comments on commit 4a7e36b

Please sign in to comment.