Skip to content

Commit

Permalink
Feature: Desktop-View - Drafts in ticket detail view.
Browse files Browse the repository at this point in the history
Co-authored-by: Benjamin Scharf <[email protected]>
Co-authored-by: Dominik Klein <[email protected]>
Co-authored-by: Florian Liebe <[email protected]>
Co-authored-by: Mantas Masalskis <[email protected]>
Co-authored-by: Tobias Schäfer <[email protected]>
  • Loading branch information
5 people committed Sep 23, 2024
1 parent 5c0d888 commit 7667a7d
Show file tree
Hide file tree
Showing 63 changed files with 2,264 additions and 287 deletions.
2 changes: 1 addition & 1 deletion app/controllers/ticket_shared_draft_zoom_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def destroy
end

def import_attachments
new_attachments = ticket.shared_draft.clone_attachments 'UploadCache', params[:form_id]
new_attachments = ticket.shared_draft.clone_attachments 'UploadCache', params[:form_id], only_attached_attachments: true

render json: {
attachments: new_attachments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ describe('ticket create view - shared drafts sidebar', async () => {
expect.objectContaining({
meta: expect.objectContaining({
additionalData: expect.objectContaining({
sharedDraftStartId: draftToMock.id,
sharedDraftId: draftToMock.id,
draftType: 'start',
}),
}),
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

import { within } from '@testing-library/vue'

import { visitView } from '#tests/support/components/visitView.ts'
import { mockPermissions } from '#tests/support/mock-permissions.ts'

import { mockFormUpdaterQuery } from '#shared/components/Form/graphql/queries/formUpdater.mocks.ts'
import { mockTicketQuery } from '#shared/entities/ticket/graphql/queries/ticket.mocks.ts'
import { createDummyTicket } from '#shared/entities/ticket-article/__tests__/mocks/ticket.ts'

describe('Ticket detail view - draft handling', () => {
describe('when user is an agent', () => {
beforeEach(() => {
mockPermissions(['ticket.agent'])
})

it('shows save as draft if it is enabled for group and user is agent', async () => {
mockFormUpdaterQuery({
formUpdater: {
fields: {},
flags: {
hasSharedDraft: true,
},
},
})

mockTicketQuery({
ticket: createDummyTicket(),
})

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

const actionMenu = await view.findByLabelText(
'Additional ticket edit actions',
)

await view.events.click(actionMenu)

const menu = await view.findByRole('menu')

expect(within(menu).getByText('Save as draft')).toBeInTheDocument()
})
})

describe('when user is an customer', () => {
beforeEach(() => {
mockPermissions(['ticket.customer'])
})

it('shows no save as draft if it an customer', async () => {
mockFormUpdaterQuery({
formUpdater: {
fields: {},
flags: {
hasSharedDraft: true,
},
},
})

mockTicketQuery({
ticket: createDummyTicket(),
})

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

expect(
view.queryByLabelText('Additional ticket edit actions'),
).not.toBeInTheDocument()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { expect } from 'vitest'

import { getTestRouter } from '#tests/support/components/renderComponent.ts'
import { visitView } from '#tests/support/components/visitView.ts'
import { mockPermissions } from '#tests/support/mock-permissions.ts'

import { mockFormUpdaterQuery } from '#shared/components/Form/graphql/queries/formUpdater.mocks.ts'
import { mockTicketQuery } from '#shared/entities/ticket/graphql/queries/ticket.mocks.ts'
Expand All @@ -16,6 +17,8 @@ import { getUserCurrentTaskbarItemUpdatesSubscriptionHandler } from '#desktop/en

describe('Ticket detail view macros', () => {
it('executes example macro which closes current tab', async () => {
mockPermissions(['ticket.agent'])

const ticket = createDummyTicket()

mockTicketQuery({ ticket })
Expand Down Expand Up @@ -110,10 +113,9 @@ describe('Ticket detail view macros', () => {

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

const updateButton = view.getByRole('button', { name: 'Update' })
const footer = updateButton.parentElement!
const actionMenu =
await within(footer).findByLabelText('Action menu button')
const actionMenu = await view.findByLabelText(
'Additional ticket edit actions',
)

await view.events.click(actionMenu)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,35 @@
<script setup lang="ts">
import { computed, toRef } from 'vue'

import { NotificationTypes } from '#shared/components/CommonNotifications/types.ts'
import { useNotifications } from '#shared/components/CommonNotifications/useNotifications.ts'
import type { FormRef } from '#shared/components/Form/types.ts'
import { useMacros } from '#shared/entities/macro/composables/useMacros.ts'
import type { MacroById } from '#shared/entities/macro/types.ts'
import type { TicketLiveAppUser } from '#shared/entities/ticket/types.ts'
import { useTicketSharedDraftZoomCreateMutation } from '#shared/entities/ticket-shared-draft-zoom/graphql/mutations/ticketSharedDraftZoomCreate.api.ts'
import { MutationHandler } from '#shared/server/apollo/handler/index.ts'

import CommonActionMenu from '#desktop/components/CommonActionMenu/CommonActionMenu.vue'
import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
import { useDialog } from '#desktop/components/CommonDialog/useDialog.ts'
import TicketScreenBehavior from '#desktop/pages/ticket/components/TicketDetailView/TicketScreenBehavior/TicketScreenBehavior.vue'
import { useTicketSharedDraft } from '#desktop/pages/ticket/composables/useTicketSharedDraft.ts'

import TicketLiveUsers from './TicketLiveUsers.vue'
import TicketSharedDraftZoom from './TicketSharedDraftZoom.vue'

export interface Props {
dirty: boolean
disabled: boolean
formNodeId?: string
isTicketEditable: boolean
isTicketAgent: boolean
ticketId: string
groupId?: string
form?: FormRef
hasAvailableDraft?: boolean
canUseDraft?: boolean
sharedDraftId?: string | null
liveUserList: TicketLiveAppUser[]
}

Expand All @@ -34,6 +47,8 @@ const emit = defineEmits<{

const { macros } = useMacros(groupId)

const { notify } = useNotifications()

const groupLabels = {
drafts: __('Drafts'),
macros: __('Macros'),
Expand All @@ -50,48 +65,94 @@ const actionItems = computed(() => {
}))

return [
// :TODO add later drafts action item
// {
// label: __('Save as draft'),
// groupLabel: groupLabels.drafts,
// icon: 'floppy',
// key: 'macro1',
// onClick: () => {},
// },
{
label: __('Save as draft'),
groupLabel: groupLabels.drafts,
icon: 'floppy',
key: 'save-draft',
show: () => props.canUseDraft,
onClick: () => {
const { mapSharedDraftParams } = useTicketSharedDraft()

if (props.sharedDraftId) {
const sharedDraftConflictDialog = useDialog({
name: 'shared-draft-conflict',
component: () => import('../TicketSharedDraftConflictDialog.vue'),
})

sharedDraftConflictDialog.open({
sharedDraftId: props.sharedDraftId,
sharedDraftParams: mapSharedDraftParams(props.ticketId, props.form),
form: props.form,
})

return
}

const draftCreateMutation = new MutationHandler(
useTicketSharedDraftZoomCreateMutation(),
{
errorNotificationMessage: __('Draft could not be saved.'),
},
)

draftCreateMutation
.send({ input: mapSharedDraftParams(props.ticketId, props.form) })
.then(() => {
notify({
id: 'shared-draft-detail-view-created',
type: NotificationTypes.Success,
message: __('Shared draft has been created successfully.'),
})
})
},
},
...(groupId.value ? macroMenu : []),
]
})
</script>

<template>
<TicketLiveUsers v-if="liveUserList?.length" :live-user-list="liveUserList" />
<div class="flex gap-4 ltr:mr-auto rtl:ml-auto">
<TicketLiveUsers
v-if="liveUserList?.length"
:live-user-list="liveUserList"
/>

<TicketSharedDraftZoom
v-if="hasAvailableDraft"
:form="form"
:shared-draft-id="sharedDraftId"
/>
</div>

<template v-if="isTicketEditable">
<CommonButton
v-if="dirty"
size="large"
variant="danger"
:disabled="disabled"
@click="$emit('discard', $event)"
>{{ $t('Discard your unsaved changes') }}</CommonButton
>
>{{ $t('Discard your unsaved changes') }}
</CommonButton>

<TicketScreenBehavior />

<CommonButton
size="large"
variant="submit"
type="button"
:form="formNodeId"
:disabled="disabled"
@click="$emit('submit', $event)"
>{{ $t('Update') }}</CommonButton
>
>{{ $t('Update') }}
</CommonButton>
<CommonActionMenu
v-if="actionItems"
v-if="isTicketAgent && actionItems"
class="flex"
button-size="large"
no-single-action-mode
placement="arrowEnd"
custom-menu-button-label="Additional ticket edit actions"
:actions="actionItems"
/>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const isLiveUserMobile = (liveUser: TicketLiveAppUser) =>
</script>

<template>
<div class="flex items-center gap-2.5 ltr:mr-auto rtl:ml-auto">
<div class="flex items-center gap-2.5">
<div
v-for="liveUser in visibleLiveUsers"
:key="liveUser.user.id"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->

<script setup lang="ts">
import type { FormRef } from '#shared/components/Form/types.ts'

import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
import { useTicketSharedDraft } from '#desktop/pages/ticket/composables/useTicketSharedDraft.ts'

defineProps<{
sharedDraftId?: string | null
form?: FormRef
}>()

const { openSharedDraftFlyout } = useTicketSharedDraft()
</script>

<template>
<div class="flex items-center gap-2.5">
<CommonButton
prefix-icon="template"
size="large"
variant="tertiary"
@click="openSharedDraftFlyout('detail-view', sharedDraftId, form)"
>{{ $t('Draft Available') }}</CommonButton
>
</div>
</template>
Loading

0 comments on commit 7667a7d

Please sign in to comment.