mirror of
https://github.com/zammad/zammad
synced 2026-05-24 09:48:36 +00:00
Maintenance: Improve Checklist handling
This commit is contained in:
parent
29b5f4523a
commit
12e7524390
89 changed files with 823 additions and 792 deletions
|
|
@ -42,6 +42,13 @@ class ChecklistTemplate extends App.ControllerSubContent
|
|||
|
||||
@html elLocal
|
||||
|
||||
value = @checklistSetting.prop('checked')
|
||||
checklistTemplatesTable = elLocal.find('.js-checklistTemplatesTable')
|
||||
if value is true
|
||||
checklistTemplatesTable.show()
|
||||
else
|
||||
checklistTemplatesTable.hide()
|
||||
|
||||
validateOnSubmit: (params) ->
|
||||
errors = {}
|
||||
if !params.items || params.items.length is 0
|
||||
|
|
|
|||
|
|
@ -974,17 +974,16 @@ class App.TicketZoom extends App.Controller
|
|||
isPendingClose = ticketState.state_type.name is 'pending action' && App.TicketState.find(ticketState.next_state_id).state_type.name is 'closed'
|
||||
return @submitTimeAccounting(e, ticket, macro, editContollerForm) if !isClosed && !isPendingClose
|
||||
|
||||
App.Checklist.completedForTicketId(ticket.id, (data) =>
|
||||
return @submitTimeAccounting(e, ticket, macro, editContollerForm) if !data || data.completed is null || data.completed
|
||||
if !ticket.checklist_incomplete
|
||||
return @submitTimeAccounting(e, ticket, macro, editContollerForm)
|
||||
|
||||
new App.TicketZoomChecklistModal(
|
||||
container: @el.closest('.content')
|
||||
ticket: ticket
|
||||
cancelCallback: =>
|
||||
@submitEnable(e)
|
||||
submitCallback: =>
|
||||
@submitTimeAccounting(e, ticket, macro, editContollerForm)
|
||||
)
|
||||
new App.TicketZoomChecklistModal(
|
||||
container: @el.closest('.content')
|
||||
ticket: ticket
|
||||
cancelCallback: =>
|
||||
@submitEnable(e)
|
||||
submitCallback: =>
|
||||
@submitTimeAccounting(e, ticket, macro, editContollerForm)
|
||||
)
|
||||
|
||||
submitTimeAccounting: (e, ticket, macro, editContollerForm) =>
|
||||
|
|
|
|||
|
|
@ -9,42 +9,16 @@ class App.TicketZoomMeta extends App.ControllerObserver
|
|||
observe:
|
||||
number: true
|
||||
created_at: true
|
||||
updated_at: true
|
||||
escalation_at: true
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
App.ChecklistItem.subscribe(@checklistItemsChanged)
|
||||
@subscribeToChecklistTickets()
|
||||
|
||||
checklistItemsChanged: =>
|
||||
@subscribeToChecklistTickets()
|
||||
@forceRerender()
|
||||
|
||||
checklistTicketChanged: =>
|
||||
@forceRerender()
|
||||
|
||||
forceRerender: =>
|
||||
@render(App[@model].fullLocal(@object_id))
|
||||
|
||||
subscribeToChecklistTickets: =>
|
||||
if @checklistTicketsSubscriptions
|
||||
for id, key in @checklistTicketsSubscriptions
|
||||
App.Ticket.unsubscribeItem(id, key)
|
||||
|
||||
@checklistTicketsSubscriptions = undefined
|
||||
|
||||
checklist = App.Checklist.findByAttribute('ticket_id', @object_id)
|
||||
|
||||
return if !checklist
|
||||
|
||||
@checklistTicketsSubscriptions = checklist
|
||||
.sorted_items()
|
||||
.filter (elem) -> elem.ticket_id
|
||||
.map (elem) => [elem.ticket_id, App.Ticket.subscribeItem(elem.ticket_id, @checklistTicketChanged)]
|
||||
checklist_total: true
|
||||
checklist_incomplete: true
|
||||
|
||||
render: (ticket) =>
|
||||
checklistState = App.Checklist.calculateState(ticket)
|
||||
checklistReferences = App.Checklist.calculateReferences(ticket)
|
||||
if App.Config.get('checklist')
|
||||
checklistState = App.Checklist.calculateState(ticket)
|
||||
checklistReferences = App.Checklist.calculateReferences(ticket)
|
||||
|
||||
|
||||
@html App.view('ticket_zoom/meta')(
|
||||
ticket: ticket
|
||||
|
|
|
|||
|
|
@ -64,9 +64,10 @@ class SidebarChecklist extends App.Controller
|
|||
# ticket subscriptions
|
||||
sid = App.Ticket.subscribeItem(
|
||||
@ticket.id,
|
||||
=>
|
||||
(ticket) =>
|
||||
@delay =>
|
||||
@badgeRenderLocal()
|
||||
return if ticket.updated_by_id is App.Session.get().id
|
||||
|
||||
@shown() if !@widget?.actionController
|
||||
)
|
||||
|
|
@ -76,6 +77,10 @@ class SidebarChecklist extends App.Controller
|
|||
sid: sid,
|
||||
)
|
||||
|
||||
# Exit early and subscribe only to the ticket when sidebar is not opened
|
||||
return if !(@widget instanceof App.SidebarChecklistShow)
|
||||
# Keep going and subscribe to checklist items and tickets in there when sidebar is opened
|
||||
|
||||
# checklist subscriptions
|
||||
checklist = App.Checklist.findByAttribute('ticket_id', @ticket.id)
|
||||
return if !checklist
|
||||
|
|
@ -126,9 +131,9 @@ class SidebarChecklist extends App.Controller
|
|||
@startLoading()
|
||||
|
||||
@ajax(
|
||||
id: 'checklist_ticket'
|
||||
id: "checklist_ticket#{@ticket.id}"
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/tickets/#{@ticket.id}/checklist"
|
||||
url: "#{@apiPath}/checklists/by_ticket/#{@ticket.id}"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@clearWidget()
|
||||
|
|
@ -140,11 +145,11 @@ class SidebarChecklist extends App.Controller
|
|||
@checklist = App.Checklist.find(data.id)
|
||||
|
||||
@widget = new App.SidebarChecklistShow(el: @elSidebar, parentVC: @, checklist: @checklist, readOnly: !@changeable, enterEditMode: enterEditMode)
|
||||
|
||||
@subscribe()
|
||||
else
|
||||
@widget = new App.SidebarChecklistStart(el: @elSidebar, parentVC: @, readOnly: !@changeable)
|
||||
|
||||
@subscribe()
|
||||
|
||||
@renderActions()
|
||||
@badgeRenderLocal()
|
||||
)
|
||||
|
|
@ -161,7 +166,7 @@ class SidebarChecklist extends App.Controller
|
|||
name: 'checklist'
|
||||
icon: 'checklist'
|
||||
counterPossible: true
|
||||
counter: App.Checklist.findByAttribute('ticket_id', @ticket.id)?.open_items().length
|
||||
counter: @ticket.checklist_incomplete
|
||||
}
|
||||
|
||||
badgeRender: (el) =>
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class App.SidebarChecklistShow extends App.Controller
|
|||
item.checklist_id = @checklist.id
|
||||
item.text = ''
|
||||
|
||||
|
||||
item.save(
|
||||
done: ->
|
||||
App.ChecklistItem.full(@id, callbackDone, force: true)
|
||||
|
|
@ -235,8 +236,6 @@ class App.SidebarChecklistShow extends App.Controller
|
|||
sorted_items = @checklist.sorted_items()
|
||||
|
||||
for object in sorted_items
|
||||
ticket = undefined
|
||||
ticketAccess = undefined
|
||||
if object.ticket_id
|
||||
ticket = App.Ticket.find(object.ticket_id)
|
||||
ticketAccess = if ticket then ticket.userGroupAccess('read') else false
|
||||
|
|
@ -272,6 +271,7 @@ class App.SidebarChecklistShow extends App.Controller
|
|||
if @enterEditModeId
|
||||
cell = @table.find("tbody tr[data-id='" + @enterEditModeId + "']").find('.checklistItemValue')[0]
|
||||
row = $(cell).closest('tr')
|
||||
return if !row.length
|
||||
@enterEditModeId = undefined
|
||||
@activateItemEditMode(cell, row, row.data('id'))
|
||||
|
||||
|
|
@ -283,6 +283,7 @@ class ChecklistItemEdit extends App.Controller
|
|||
events:
|
||||
'click .js-cancel': 'onCancel'
|
||||
'click .js-confirm': 'onConfirm'
|
||||
'blur .js-input': 'onConfirm'
|
||||
'keyup #checklistItemEditText': 'onKeyUp'
|
||||
|
||||
constructor: ->
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ class App.SidebarChecklistStart extends App.Controller
|
|||
@ajax(
|
||||
id: 'checklist_ticket_add_empty'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/tickets/#{@parentVC.ticket.id}/checklist"
|
||||
url: "#{@apiPath}/checklists"
|
||||
data: JSON.stringify({ ticket_id: @parentVC.ticket.id })
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@parentVC.shown(true)
|
||||
|
|
@ -52,7 +53,7 @@ class App.SidebarChecklistStart extends App.Controller
|
|||
@ajax(
|
||||
id: 'checklist_ticket_add_from_template'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/tickets/#{@parentVC.ticket.id}/checklist"
|
||||
url: "#{@apiPath}/checklists"
|
||||
data: JSON.stringify({ ticket_id: @parentVC.ticket.id, template_id: params.checklist_template_id })
|
||||
success: (data, status, xhr) =>
|
||||
@parentVC.shown()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ class TicketReferences extends App.PopoverProvider
|
|||
@includeData = false
|
||||
|
||||
buildTitleFor: (elem) ->
|
||||
App.i18n.translateInline('Tracked by checklist item by')
|
||||
App.i18n.translateInline('Tracked as checklist item in')
|
||||
|
||||
buildContentFor: (elem) ->
|
||||
@buildHtmlContent(
|
||||
|
|
|
|||
|
|
@ -38,26 +38,17 @@ class App.Checklist extends App.Model
|
|||
)
|
||||
|
||||
@calculateState: (ticket) ->
|
||||
checklist = App.Checklist.findByAttribute('ticket_id', ticket.id)
|
||||
|
||||
return if !checklist
|
||||
|
||||
all = checklist.sorted_items().length
|
||||
open = checklist.open_items().length
|
||||
|
||||
return undefined if !open
|
||||
return if !ticket.checklist_incomplete
|
||||
|
||||
{
|
||||
all: all,
|
||||
open: open
|
||||
all: ticket.checklist_total
|
||||
open: ticket.checklist_incomplete
|
||||
|
||||
}
|
||||
|
||||
@calculateReferences: (ticket) ->
|
||||
items = App.ChecklistItem
|
||||
.findAllByAttribute('ticket_id', ticket.id)
|
||||
checklists = App.Checklist
|
||||
.findAll(ticket.referencing_checklist_ids)
|
||||
.filter (elem) -> !elem.ticket_inaccessible
|
||||
|
||||
checklist_ids = _.unique items.map (elem) -> elem.checklist_id
|
||||
checklists = App.Checklist.findAll checklist_ids
|
||||
|
||||
App.Ticket.findAll checklists.map (elem) -> elem.ticket_id
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
class App.ChecklistTemplateItem extends App.Model
|
||||
@configure 'ChecklistTemplateItem', 'text', 'checklist_template_id', 'updated_at'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/checklist_template_items'
|
||||
|
||||
@configure_attributes = [
|
||||
{ name: 'text', display: __('Name'), tag: 'input', type: 'text', limit: 100, null: false, parentClass: 'checklistItemNameCell' },
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<div class="page-header">
|
||||
<div class="page-header-title">
|
||||
<div class="zammad-switch zammad-switch--small js-checklistSetting">
|
||||
<input name="enableChecklists" type="checkbox" id="enableChecklists" <% if @C('checklist'): %>checked<% end %>>
|
||||
<label for="enableChecklists"></label>
|
||||
</div>
|
||||
<h1><%- @T('Checklists') %><small></small></h1>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -7,17 +11,6 @@
|
|||
<div class="page-content">
|
||||
<p><%- @T('With checklists you can keep track of the progress of your ticket related tasks.') %></p>
|
||||
|
||||
<div class="settings-entry">
|
||||
<div class="page-header-title">
|
||||
<div class="zammad-switch zammad-switch--small js-checklistSetting">
|
||||
<input name="enableChecklists" type="checkbox" id="enableChecklists" <% if @C('checklist'): %>checked<% end %>>
|
||||
<label for="enableChecklists"></label>
|
||||
</div>
|
||||
<h2><%- @T('Enable Checklists') %></h2>
|
||||
</div>
|
||||
<p><%- @T('Allow users to add new checklists.') %></p>
|
||||
</div>
|
||||
|
||||
<div class="settings-entry settings-entry--stretched vertical js-checklistTemplatesTable">
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,23 +3,29 @@
|
|||
class ChecklistItemsController < ApplicationController
|
||||
prepend_before_action :authenticate_and_authorize!
|
||||
|
||||
def index
|
||||
model_index_render(Checklist::Item.for_user(current_user), params)
|
||||
end
|
||||
|
||||
def show
|
||||
model_show_render(Checklist::Item.for_user(current_user), params)
|
||||
model_show_render(Checklist::Item, existing_item_params)
|
||||
end
|
||||
|
||||
def create
|
||||
model_create_render(Checklist::Item.for_user(current_user), params)
|
||||
model_create_render(Checklist::Item, new_item_params)
|
||||
end
|
||||
|
||||
def update
|
||||
model_update_render(Checklist::Item.for_user(current_user), params)
|
||||
model_update_render(Checklist::Item, existing_item_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
model_destroy_render(Checklist::Item.for_user(current_user), params)
|
||||
model_destroy_render(Checklist::Item, existing_item_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_item_params
|
||||
params.permit(:text, :checklist_id)
|
||||
end
|
||||
|
||||
def existing_item_params
|
||||
params.permit(:text, :id, :checked)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class ChecklistTemplateItemsController < ApplicationController
|
||||
prepend_before_action :authenticate_and_authorize!
|
||||
|
||||
def index
|
||||
model_index_render(ChecklistTemplate::Item, params)
|
||||
end
|
||||
|
||||
def show
|
||||
model_show_render(ChecklistTemplate::Item, params)
|
||||
end
|
||||
|
||||
def create
|
||||
model_create_render(ChecklistTemplate::Item, params)
|
||||
end
|
||||
|
||||
def update
|
||||
model_update_render(ChecklistTemplate::Item, params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
model_destroy_render(ChecklistTemplate::Item, params)
|
||||
end
|
||||
end
|
||||
|
|
@ -3,23 +3,52 @@
|
|||
class ChecklistsController < ApplicationController
|
||||
prepend_before_action :authenticate_and_authorize!
|
||||
|
||||
def index
|
||||
model_index_render(Checklist.for_user(current_user), params)
|
||||
def show_by_ticket
|
||||
checklist = Checklist.find_by ticket_id: params[:ticket_id]
|
||||
|
||||
if checklist
|
||||
authorize!(checklist, :show?)
|
||||
assets = ApplicationModel::CanAssets.reduce([checklist] + checklist.items, {})
|
||||
render json: { id: checklist.id, assets: assets }
|
||||
return
|
||||
end
|
||||
|
||||
render json: {}
|
||||
end
|
||||
|
||||
def show
|
||||
model_show_render(Checklist.for_user(current_user), params)
|
||||
model_show_render(Checklist, existing_checklist_params)
|
||||
end
|
||||
|
||||
def create
|
||||
model_create_render(Checklist.for_user(current_user), params)
|
||||
new_checklist = if params[:template_id].present?
|
||||
ChecklistTemplate.find_by(id: params[:template_id]).create_from_template!(ticket_id: params[:ticket_id])
|
||||
else
|
||||
Checklist.create!(name: '', ticket_id: params[:ticket_id]).tap do |checklist|
|
||||
Checklist::Item.create!(checklist_id: checklist.id, text: '')
|
||||
end
|
||||
end
|
||||
|
||||
new_checklist.reload
|
||||
|
||||
render json: { id: new_checklist.id, assets: new_checklist.assets({}) }, status: :created
|
||||
end
|
||||
|
||||
def update
|
||||
model_update_render(Checklist.for_user(current_user), params)
|
||||
model_update_render(Checklist, existing_checklist_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
model_destroy_render(Checklist.for_user(current_user), params)
|
||||
model_destroy_render(Checklist, existing_checklist_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_checklist_params
|
||||
params.permit(:ticket_id, :name)
|
||||
end
|
||||
|
||||
def existing_checklist_params
|
||||
params.permit(:id, :name, sorted_item_ids: [])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class TicketChecklistController < ApplicationController
|
||||
prepend_before_action :authenticate_and_authorize!
|
||||
|
||||
def show
|
||||
if checklist
|
||||
render json: { id: checklist.id, assets: checklist.assets({}) }
|
||||
return
|
||||
end
|
||||
|
||||
render json: {}
|
||||
end
|
||||
|
||||
def create
|
||||
new_checklist = if params[:template_id].present?
|
||||
ChecklistTemplate.find_by(id: params[:template_id]).create_from_template!(ticket_id: params[:ticket_id])
|
||||
else
|
||||
Checklist.create!(name: '', ticket_id: params[:ticket_id]).tap do |checklist|
|
||||
Checklist::Item.create!(checklist_id: checklist.id, text: '')
|
||||
end
|
||||
end
|
||||
|
||||
new_checklist.reload
|
||||
|
||||
render json: { id: new_checklist.id, assets: new_checklist.assets({}) }
|
||||
end
|
||||
|
||||
def update
|
||||
checklist.update! params.permit(:name, sorted_item_ids: [])
|
||||
|
||||
render json: { id: checklist.id, assets: checklist.assets({}) }
|
||||
end
|
||||
|
||||
def destroy
|
||||
checklist.destroy!
|
||||
|
||||
render json: { success: true }
|
||||
end
|
||||
|
||||
def completed
|
||||
render json: {
|
||||
completed: checklist&.completed?
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def checklist
|
||||
@checklist ||= Checklist.find_by(ticket: params[:ticket_id])
|
||||
end
|
||||
end
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class TicketChecklistItemsController < ApplicationController
|
||||
prepend_before_action :authenticate_and_authorize!
|
||||
|
||||
def create
|
||||
new_item = checklist.items.create!(checklist_params)
|
||||
|
||||
render json: { id: new_item.id, assets: checklist.assets({}) }
|
||||
end
|
||||
|
||||
def update
|
||||
checklist_item.update!(checklist_params)
|
||||
|
||||
render json: { success: true }
|
||||
end
|
||||
|
||||
def destroy
|
||||
checklist_item.destroy!
|
||||
|
||||
render json: { success: true }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def checklist
|
||||
@checklist ||= Checklist.find_by!(ticket: params[:ticket_id])
|
||||
end
|
||||
|
||||
def checklist_item
|
||||
@checklist_item ||= checklist.items.find(params[:id])
|
||||
end
|
||||
|
||||
def checklist_params
|
||||
params.permit(:text, :checked)
|
||||
end
|
||||
end
|
||||
|
|
@ -120,7 +120,7 @@ const submitEdit = () => {
|
|||
|
||||
const submitEditResult = props.onSubmitEdit(inputValue.value)
|
||||
|
||||
if (submitEditResult instanceof Promise) {
|
||||
if (submitEditResult instanceof Promise)
|
||||
return submitEditResult
|
||||
.then((result) => {
|
||||
result?.()
|
||||
|
|
@ -128,7 +128,6 @@ const submitEdit = () => {
|
|||
stopEditing(false)
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
submitEditResult?.()
|
||||
|
||||
|
|
@ -150,7 +149,10 @@ const handleMouseLeave = () => {
|
|||
isHoverTargetLink.value = false
|
||||
}
|
||||
|
||||
onClickOutside(target, () => stopEditing())
|
||||
onClickOutside(target, () => {
|
||||
if (isEditing.value) return submitEdit()
|
||||
stopEditing()
|
||||
})
|
||||
|
||||
const { setupLinksHandlers } = useHtmlLinks('/desktop')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
import { waitFor } from '@testing-library/vue'
|
||||
import { fireEvent, waitFor } from '@testing-library/vue'
|
||||
|
||||
import { renderComponent } from '#tests/support/components/index.ts'
|
||||
|
||||
|
|
@ -88,6 +88,28 @@ describe('CommonInlineEdit', async () => {
|
|||
expect(submitEditCallbackSpy).toHaveBeenCalledWith('test value update 2')
|
||||
})
|
||||
|
||||
it('submits on background click', async () => {
|
||||
const submitEditCallbackSpy = vi.fn()
|
||||
|
||||
const wrapper = renderInlineEdit({
|
||||
onSubmitEdit: (value: string) => submitEditCallbackSpy(value),
|
||||
})
|
||||
|
||||
await wrapper.events.click(wrapper.getByRole('button'))
|
||||
|
||||
await wrapper.events.type(wrapper.getByRole('textbox'), ' update 2')
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
wrapper.getByRole('textbox', { name: 'Inline Edit Label' }),
|
||||
).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
await fireEvent.click(document.body)
|
||||
|
||||
expect(submitEditCallbackSpy).toHaveBeenCalledWith('test value update 2')
|
||||
})
|
||||
|
||||
it('do not stop edit mode when submit promise failed', async () => {
|
||||
const wrapper = renderInlineEdit({
|
||||
onSubmitEdit: (): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '#shared/entities/ticket/graphql/mutations/update.mocks.ts'
|
||||
import { mockTicketArticlesQuery } from '#shared/entities/ticket/graphql/queries/ticket/articles.mocks.ts'
|
||||
import { mockTicketQuery } from '#shared/entities/ticket/graphql/queries/ticket.mocks.ts'
|
||||
import { getTicketUpdatesSubscriptionHandler } from '#shared/entities/ticket/graphql/subscriptions/ticketUpdates.mocks.ts'
|
||||
import { createDummyArticle } from '#shared/entities/ticket-article/__tests__/mocks/ticket-articles.ts'
|
||||
import { createDummyTicket } from '#shared/entities/ticket-article/__tests__/mocks/ticket.ts'
|
||||
import { EnumUserErrorException } from '#shared/graphql/types.ts'
|
||||
|
|
@ -134,7 +135,15 @@ describe('Ticket detail view', () => {
|
|||
|
||||
it('updates incomplete checklist item count', async () => {
|
||||
mockTicketQuery({
|
||||
ticket: createDummyTicket(),
|
||||
ticket: createDummyTicket({
|
||||
checklist: {
|
||||
id: convertToGraphQLId('Checklist', 1),
|
||||
complete: 1,
|
||||
completed: false,
|
||||
total: 2,
|
||||
incomplete: 1,
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const testArticle = createDummyArticle({
|
||||
|
|
@ -196,6 +205,16 @@ describe('Ticket detail view', () => {
|
|||
},
|
||||
})
|
||||
|
||||
await getTicketUpdatesSubscriptionHandler().trigger({
|
||||
ticketUpdates: {
|
||||
ticket: {
|
||||
checklist: {
|
||||
incomplete: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(
|
||||
view.queryByRole('status', { name: 'Incomplete checklist items' }),
|
||||
).not.toBeInTheDocument()
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ const menuItemKeys = computed(() =>
|
|||
ref="popoverTarget"
|
||||
v-tooltip="
|
||||
referencingTicketsCount === 1
|
||||
? $t('Show tracked ticket')
|
||||
: $t('Show tracked tickets')
|
||||
? $t('Show tracking ticket')
|
||||
: $t('Show tracking tickets')
|
||||
"
|
||||
role="button"
|
||||
tag="div"
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ describe('TicketChecklistBadges', () => {
|
|||
checklist: {
|
||||
id: convertToGraphQLId('Checklist', 1),
|
||||
completed: false,
|
||||
incomplete: 3,
|
||||
total: 5,
|
||||
complete: 2,
|
||||
},
|
||||
|
|
@ -83,6 +84,7 @@ describe('TicketChecklistBadges', () => {
|
|||
checklist: {
|
||||
id: convertToGraphQLId('Checklist', 1),
|
||||
completed: false,
|
||||
incomplete: 3,
|
||||
total: 5,
|
||||
complete: 2,
|
||||
},
|
||||
|
|
@ -109,6 +111,7 @@ describe('TicketChecklistBadges', () => {
|
|||
checklist: {
|
||||
id: convertToGraphQLId('Checklist', 1),
|
||||
completed: false,
|
||||
incomplete: 2,
|
||||
total: 3,
|
||||
complete: 1,
|
||||
},
|
||||
|
|
@ -146,6 +149,7 @@ describe('TicketChecklistBadges', () => {
|
|||
id: convertToGraphQLId('Checklist', 1),
|
||||
completed: false,
|
||||
total: 3,
|
||||
incomplete: 2,
|
||||
complete: 1,
|
||||
},
|
||||
referencingChecklistTickets: [
|
||||
|
|
@ -180,7 +184,7 @@ describe('TicketChecklistBadges', () => {
|
|||
})
|
||||
|
||||
await wrapper.events.click(
|
||||
wrapper.getByRole('button', { name: 'Show tracked tickets' }),
|
||||
wrapper.getByRole('button', { name: 'Show tracking tickets' }),
|
||||
)
|
||||
|
||||
expect(
|
||||
|
|
@ -212,6 +216,7 @@ describe('TicketChecklistBadges', () => {
|
|||
id: convertToGraphQLId('Checklist', 1),
|
||||
completed: false,
|
||||
total: 3,
|
||||
incomplete: 0,
|
||||
complete: 3,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
|
||||
import { useTicketChecklist } from '#desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/useTicketChecklist.ts'
|
||||
import { useTicketInformation } from '#desktop/pages/ticket/composables/useTicketInformation.ts'
|
||||
import {
|
||||
type TicketSidebarProps,
|
||||
type TicketSidebarEmits,
|
||||
|
|
@ -19,17 +19,20 @@ defineProps<TicketSidebarProps>()
|
|||
|
||||
const emit = defineEmits<TicketSidebarEmits>()
|
||||
|
||||
const { incompleteItemCount, checklist, isLoadingChecklist, isTicketEditable } =
|
||||
useTicketChecklist()
|
||||
const { ticket } = useTicketInformation()
|
||||
|
||||
const incompleteChecklistItemsCount = computed(
|
||||
() => ticket.value?.checklist?.incomplete,
|
||||
)
|
||||
|
||||
const badge = computed<TicketSidebarButtonBadgeDetails | undefined>(() => {
|
||||
const label = __('Incomplete checklist items')
|
||||
|
||||
if (!incompleteItemCount.value) return
|
||||
if (!incompleteChecklistItemsCount.value) return
|
||||
|
||||
return {
|
||||
type: TicketSidebarButtonBadgeType.Info,
|
||||
value: incompleteItemCount.value,
|
||||
value: incompleteChecklistItemsCount.value,
|
||||
label,
|
||||
}
|
||||
})
|
||||
|
|
@ -50,9 +53,6 @@ onMounted(() => {
|
|||
<TicketSidebarChecklistContent
|
||||
:context="context"
|
||||
:sidebar-plugin="sidebarPlugin"
|
||||
:checklist="checklist"
|
||||
:loading="isLoadingChecklist"
|
||||
:readonly="!isTicketEditable"
|
||||
/>
|
||||
</TicketSidebarWrapper>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { computed, watch, nextTick, useTemplateRef } from 'vue'
|
||||
import { computed, nextTick, useTemplateRef } from 'vue'
|
||||
|
||||
import { useConfirmation } from '#shared/composables/useConfirmation.ts'
|
||||
import { handleUserErrors } from '#shared/errors/utils.ts'
|
||||
import type {
|
||||
ChecklistItem,
|
||||
TicketChecklistItemInput,
|
||||
Checklist,
|
||||
} from '#shared/graphql/types.ts'
|
||||
import { i18n } from '#shared/i18n/index.ts'
|
||||
import { getApolloClient } from '#shared/server/apollo/client.ts'
|
||||
|
|
@ -23,6 +22,7 @@ import ChecklistItems from '#desktop/pages/ticket/components/TicketSidebar/Ticke
|
|||
import ChecklistTemplates from '#desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent/ChecklistTemplates.vue'
|
||||
import type { AddNewChecklistInput } from '#desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/types.ts'
|
||||
import { useChecklistTemplates } from '#desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/useChecklistTemplates.ts'
|
||||
import { useTicketChecklist } from '#desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/useTicketChecklist.ts'
|
||||
import TicketSidebarContent from '#desktop/pages/ticket/components/TicketSidebar/TicketSidebarContent.vue'
|
||||
import { useTicketInformation } from '#desktop/pages/ticket/composables/useTicketInformation.ts'
|
||||
import { useTicketNumber } from '#desktop/pages/ticket/composables/useTicketNumber.ts'
|
||||
|
|
@ -34,23 +34,19 @@ import { useTicketChecklistItemUpsertMutation } from '#desktop/pages/ticket/grap
|
|||
import { useTicketChecklistTitleUpdateMutation } from '#desktop/pages/ticket/graphql/mutations/ticketChecklistTitleUpdate.api.ts'
|
||||
import type { TicketSidebarContentProps } from '#desktop/pages/ticket/types/sidebar.ts'
|
||||
|
||||
interface Props extends TicketSidebarContentProps {
|
||||
checklist?: Checklist
|
||||
loading: boolean
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
defineProps<TicketSidebarContentProps>()
|
||||
|
||||
const checklistItemsInstance = useTemplateRef('checklist-items')
|
||||
|
||||
const { cache: apolloCache } = getApolloClient()
|
||||
|
||||
const { ticket } = useTicketInformation()
|
||||
const { ticket, ticketId, isTicketEditable } = useTicketInformation()
|
||||
const { ticketNumberWithTicketHook } = useTicketNumber(ticket)
|
||||
|
||||
const { checklist, isLoadingChecklist } = useTicketChecklist(ticketId, ticket)
|
||||
|
||||
const checklistTitle = computed(
|
||||
() =>
|
||||
props.checklist?.name ||
|
||||
checklist.value?.name ||
|
||||
i18n.t('%s Checklist', ticketNumberWithTicketHook.value),
|
||||
)
|
||||
|
||||
|
|
@ -62,21 +58,16 @@ const createNewChecklist = async (
|
|||
input?: Omit<AddNewChecklistInput, 'ticketId'>,
|
||||
options = { focusLastItem: true },
|
||||
) => {
|
||||
if (options.focusLastItem)
|
||||
watch(
|
||||
checklistItemsInstance,
|
||||
(component) => {
|
||||
nextTick(() => component?.focusNewItem())
|
||||
},
|
||||
{ once: true },
|
||||
)
|
||||
|
||||
if (ticket.value?.id) {
|
||||
return addNewChecklistMutation
|
||||
.send({
|
||||
...input,
|
||||
ticketId: ticket.value.id,
|
||||
})
|
||||
.then(() => {
|
||||
if (options.focusLastItem)
|
||||
nextTick(() => checklistItemsInstance.value?.focusNewItem())
|
||||
})
|
||||
.catch(handleUserErrors)
|
||||
}
|
||||
}
|
||||
|
|
@ -97,7 +88,7 @@ const removeChecklist = async () => {
|
|||
if (confirmed)
|
||||
await checklistDeleteMutation
|
||||
.send({
|
||||
checklistId: props.checklist?.id as string,
|
||||
checklistId: checklist.value?.id as string,
|
||||
})
|
||||
.catch(handleUserErrors)
|
||||
}
|
||||
|
|
@ -106,7 +97,7 @@ const updateTitle = async (title: string) => {
|
|||
return checklistTitleUpdateMutation
|
||||
.send({
|
||||
title,
|
||||
checklistId: props.checklist?.id as string,
|
||||
checklistId: checklist.value?.id as string,
|
||||
})
|
||||
.then(() => {})
|
||||
.catch(handleUserErrors)
|
||||
|
|
@ -115,18 +106,19 @@ const updateTitle = async (title: string) => {
|
|||
const itemAddMutation = new MutationHandler(
|
||||
useTicketChecklistItemUpsertMutation({
|
||||
update: (cache, { data }) => {
|
||||
if (!data || !props.checklist) return
|
||||
if (!data || !checklist.value) return
|
||||
|
||||
const { ticketChecklistItemUpsert } = data
|
||||
if (!ticketChecklistItemUpsert?.checklistItem) return
|
||||
|
||||
const newIdPresent = props.checklist?.items.find((item) => {
|
||||
const newIdPresent = checklist.value?.items.find((item) => {
|
||||
return item.id === ticketChecklistItemUpsert.checklistItem?.id
|
||||
})
|
||||
|
||||
if (newIdPresent) return
|
||||
|
||||
cache.modify({
|
||||
id: cache.identify(props.checklist),
|
||||
id: cache.identify(checklist.value),
|
||||
fields: {
|
||||
items(currentItems, { toReference }) {
|
||||
return [
|
||||
|
|
@ -134,8 +126,14 @@ const itemAddMutation = new MutationHandler(
|
|||
toReference(ticketChecklistItemUpsert.checklistItem!),
|
||||
]
|
||||
},
|
||||
incomplete() {
|
||||
return (props.checklist?.incomplete || 0) + 1
|
||||
complete(currentComplete) {
|
||||
return currentComplete + 1
|
||||
},
|
||||
total(totalCount) {
|
||||
return totalCount + 1
|
||||
},
|
||||
incomplete(incompleteCount) {
|
||||
return incompleteCount + 1
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -168,7 +166,7 @@ const itemDeleteMutation = new MutationHandler(
|
|||
)
|
||||
|
||||
const modifyIncompleteItemCountCache = (increase: boolean) => {
|
||||
const currentCheckList = props.checklist!
|
||||
const currentCheckList = checklist.value!
|
||||
const previousIncompleteItemCount = currentCheckList.incomplete
|
||||
const previousCompleted = currentCheckList.completed
|
||||
|
||||
|
|
@ -259,7 +257,7 @@ const modifyCheckedCache = (item: ChecklistItem) => {
|
|||
}
|
||||
|
||||
const modifyItemsCache = (items: ChecklistItem[]) => {
|
||||
const currentCheckList = props.checklist!
|
||||
const currentCheckList = checklist.value
|
||||
|
||||
const checklistId = apolloCache.identify(currentCheckList)
|
||||
|
||||
|
|
@ -277,7 +275,7 @@ const modifyItemsCache = (items: ChecklistItem[]) => {
|
|||
|
||||
const updateItem = async (itemId: string, input: TicketChecklistItemInput) => {
|
||||
return itemUpsertMutation.send({
|
||||
checklistId: props.checklist?.id as string,
|
||||
checklistId: checklist.value?.id as string,
|
||||
checklistItemId: itemId,
|
||||
input,
|
||||
})
|
||||
|
|
@ -291,25 +289,19 @@ const setItemCheckedState = async (item: ChecklistItem) => {
|
|||
})
|
||||
}
|
||||
|
||||
const addNewItem = async () => {
|
||||
watch(
|
||||
() => props.checklist,
|
||||
() => {
|
||||
nextTick(() => checklistItemsInstance.value?.focusNewItem())
|
||||
},
|
||||
{ once: true },
|
||||
)
|
||||
|
||||
return itemAddMutation
|
||||
const addNewItem = async () =>
|
||||
itemAddMutation
|
||||
.send({
|
||||
checklistId: props.checklist?.id as string,
|
||||
checklistId: checklist.value?.id as string,
|
||||
input: {
|
||||
text: '',
|
||||
checked: false,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
checklistItemsInstance.value?.focusNewItem()
|
||||
})
|
||||
.catch(handleUserErrors)
|
||||
}
|
||||
|
||||
const editItem = async (item: ChecklistItem) => {
|
||||
return updateItem(item.id, { text: item.text })
|
||||
|
|
@ -320,7 +312,7 @@ const editItem = async (item: ChecklistItem) => {
|
|||
const saveItemsOrder = (items: ChecklistItem[], stopReordering: () => void) => {
|
||||
itemOrderMutation
|
||||
.send({
|
||||
checklistId: props.checklist?.id as string,
|
||||
checklistId: checklist.value?.id as string,
|
||||
order: items.map((item) => item.id),
|
||||
})
|
||||
.then(() => {
|
||||
|
|
@ -342,7 +334,7 @@ const removeItem = async (item: ChecklistItem) => {
|
|||
if (!confirmed) return
|
||||
}
|
||||
|
||||
const previousChecklistItems = cloneDeep(props.checklist?.items || [])
|
||||
const previousChecklistItems = cloneDeep(checklist.value?.items || [])
|
||||
apolloCache.evict({ id: apolloCache.identify(item) })
|
||||
apolloCache.gc()
|
||||
|
||||
|
|
@ -350,7 +342,7 @@ const removeItem = async (item: ChecklistItem) => {
|
|||
|
||||
return itemDeleteMutation
|
||||
.send({
|
||||
checklistId: props.checklist?.id as string,
|
||||
checklistId: checklist.value?.id as string,
|
||||
checklistItemId: item.id,
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -366,7 +358,7 @@ const checklistActions: MenuItem[] = [
|
|||
label: __('Rename checklist'),
|
||||
icon: 'input-cursor-text',
|
||||
onClick: () => checklistItemsInstance.value?.focusTitle(),
|
||||
show: () => !!props.checklist,
|
||||
show: () => !!checklist.value,
|
||||
},
|
||||
{
|
||||
key: 'remove',
|
||||
|
|
@ -374,7 +366,7 @@ const checklistActions: MenuItem[] = [
|
|||
variant: 'danger',
|
||||
icon: 'trash3',
|
||||
onClick: () => removeChecklist(),
|
||||
show: () => !!props.checklist,
|
||||
show: () => !!checklist.value,
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -384,11 +376,11 @@ const { isLoadingTemplates, checklistTemplatesMenuItems } =
|
|||
|
||||
<template>
|
||||
<TicketSidebarContent
|
||||
:actions="readonly ? undefined : checklistActions"
|
||||
:actions="!isTicketEditable ? undefined : checklistActions"
|
||||
:title="sidebarPlugin.title"
|
||||
:icon="sidebarPlugin.icon"
|
||||
>
|
||||
<CommonLoader :loading="loading">
|
||||
<CommonLoader :loading="isLoadingChecklist">
|
||||
<div class="flex flex-col gap-3">
|
||||
<ChecklistItems
|
||||
v-if="checklist"
|
||||
|
|
@ -396,7 +388,7 @@ const { isLoadingTemplates, checklistTemplatesMenuItems } =
|
|||
:no-default-title="!!checklist.name"
|
||||
:title="checklistTitle"
|
||||
:items="checklist?.items"
|
||||
:read-only="readonly"
|
||||
:read-only="!isTicketEditable"
|
||||
@add-item="addNewItem"
|
||||
@remove-item="removeItem"
|
||||
@set-item-checked="setItemCheckedState"
|
||||
|
|
@ -404,7 +396,7 @@ const { isLoadingTemplates, checklistTemplatesMenuItems } =
|
|||
@save-order="saveItemsOrder"
|
||||
@update-title="updateTitle"
|
||||
/>
|
||||
<template v-else-if="!readonly">
|
||||
<template v-else-if="isTicketEditable">
|
||||
<CommonButton
|
||||
variant="primary"
|
||||
size="medium"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
import { computed } from 'vue'
|
||||
import { computed, type ComputedRef } from 'vue'
|
||||
|
||||
import type { TicketById } from '#shared/entities/ticket/types.ts'
|
||||
import type {
|
||||
Checklist,
|
||||
TicketChecklistQuery,
|
||||
|
|
@ -10,13 +11,16 @@ import type {
|
|||
} from '#shared/graphql/types.ts'
|
||||
import { QueryHandler } from '#shared/server/apollo/handler/index.ts'
|
||||
|
||||
import { useTicketInformation } from '#desktop/pages/ticket/composables/useTicketInformation.ts'
|
||||
import { useTicketChecklistQuery } from '#desktop/pages/ticket/graphql/queries/ticketChecklist.api.ts'
|
||||
import { TicketChecklistUpdatesDocument } from '#desktop/pages/ticket/graphql/subscriptions/ticketChecklistUpdates.api.ts'
|
||||
|
||||
export const useTicketChecklist = () => {
|
||||
const { ticket, ticketId, isTicketEditable } = useTicketInformation()
|
||||
|
||||
export const useTicketChecklist = (
|
||||
/**
|
||||
* TicketId is always available since we use it from the route not `ticket` directly
|
||||
*/
|
||||
ticketId: ComputedRef<string>,
|
||||
ticket: ComputedRef<TicketById | undefined>,
|
||||
) => {
|
||||
const checklistQuery = new QueryHandler(
|
||||
useTicketChecklistQuery(() => ({
|
||||
ticketId: ticketId.value,
|
||||
|
|
@ -76,7 +80,6 @@ export const useTicketChecklist = () => {
|
|||
return {
|
||||
checklist,
|
||||
incompleteItemCount,
|
||||
isTicketEditable,
|
||||
isLoadingChecklist,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import * as Types from '#shared/graphql/types.ts';
|
|||
|
||||
import gql from 'graphql-tag';
|
||||
import { ObjectAttributeValuesFragmentDoc } from '../../../../graphql/fragments/objectAttributeValues.api';
|
||||
import { ReferencingTicketFragmentDoc } from './referencingTicket.api';
|
||||
export const TicketAttributesFragmentDoc = gql`
|
||||
fragment ticketAttributes on Ticket {
|
||||
id
|
||||
|
|
@ -101,15 +100,5 @@ export const TicketAttributesFragmentDoc = gql`
|
|||
closeEscalationAt
|
||||
updateEscalationAt
|
||||
initialChannel
|
||||
checklist {
|
||||
id
|
||||
completed
|
||||
total
|
||||
complete
|
||||
}
|
||||
referencingChecklistTickets {
|
||||
...referencingTicket
|
||||
}
|
||||
}
|
||||
${ObjectAttributeValuesFragmentDoc}
|
||||
${ReferencingTicketFragmentDoc}`;
|
||||
${ObjectAttributeValuesFragmentDoc}`;
|
||||
|
|
@ -95,13 +95,4 @@ fragment ticketAttributes on Ticket {
|
|||
closeEscalationAt
|
||||
updateEscalationAt
|
||||
initialChannel
|
||||
checklist {
|
||||
id
|
||||
completed
|
||||
total
|
||||
complete
|
||||
}
|
||||
referencingChecklistTickets {
|
||||
...referencingTicket
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import * as Types from '#shared/graphql/types.ts';
|
|||
|
||||
import gql from 'graphql-tag';
|
||||
import { TicketAttributesFragmentDoc } from '../fragments/ticketAttributes.api';
|
||||
import { ReferencingTicketFragmentDoc } from '../fragments/referencingTicket.api';
|
||||
import { TicketMentionFragmentDoc } from '../fragments/ticketMention.api';
|
||||
import { ReferencingTicketFragmentDoc } from '../fragments/referencingTicket.api';
|
||||
import * as VueApolloComposable from '@vue/apollo-composable';
|
||||
import * as VueCompositionApi from 'vue';
|
||||
export type ReactiveFunction<TParam> = () => TParam;
|
||||
|
|
@ -30,6 +30,7 @@ export const TicketDocument = gql`
|
|||
checklist {
|
||||
id
|
||||
completed
|
||||
incomplete
|
||||
total
|
||||
complete
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ query ticket($ticketId: ID, $ticketInternalId: Int, $ticketNumber: String) {
|
|||
checklist {
|
||||
id
|
||||
completed
|
||||
incomplete
|
||||
total
|
||||
complete
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import * as Types from '#shared/graphql/types.ts';
|
|||
|
||||
import gql from 'graphql-tag';
|
||||
import { TicketAttributesFragmentDoc } from '../fragments/ticketAttributes.api';
|
||||
import { ReferencingTicketFragmentDoc } from '../fragments/referencingTicket.api';
|
||||
import { TicketMentionFragmentDoc } from '../fragments/ticketMention.api';
|
||||
import { ReferencingTicketFragmentDoc } from '../fragments/referencingTicket.api';
|
||||
import * as VueApolloComposable from '@vue/apollo-composable';
|
||||
import * as VueCompositionApi from 'vue';
|
||||
export type ReactiveFunction<TParam> = () => TParam;
|
||||
|
|
@ -29,6 +29,7 @@ export const TicketUpdatesDocument = gql`
|
|||
checklist {
|
||||
id
|
||||
completed
|
||||
incomplete
|
||||
total
|
||||
complete
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ subscription ticketUpdates($ticketId: ID!, $initial: Boolean = false) {
|
|||
checklist {
|
||||
id
|
||||
completed
|
||||
incomplete
|
||||
total
|
||||
complete
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export interface TicketLiveAppUser {
|
|||
}
|
||||
|
||||
export type TicketById = TicketQuery['ticket']
|
||||
|
||||
export type TicketArticle = ConfidentTake<
|
||||
TicketArticlesQuery,
|
||||
'articles.edges.node'
|
||||
|
|
|
|||
|
|
@ -5451,7 +5451,7 @@ export type TicketWithMentionLimitQueryVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type TicketWithMentionLimitQuery = { __typename?: 'Queries', ticket: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, createArticleType?: { __typename?: 'TicketArticleType', id: string, name?: string | null } | null, mentions?: { __typename?: 'MentionConnection', totalCount: number, edges: Array<{ __typename?: 'MentionEdge', cursor: string, node: { __typename?: 'Mention', user: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, vip?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, active?: boolean | null, image?: string | null } } }> } | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null } };
|
||||
export type TicketWithMentionLimitQuery = { __typename?: 'Queries', ticket: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, createArticleType?: { __typename?: 'TicketArticleType', id: string, name?: string | null } | null, mentions?: { __typename?: 'MentionConnection', totalCount: number, edges: Array<{ __typename?: 'MentionEdge', cursor: string, node: { __typename?: 'Mention', user: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, vip?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, active?: boolean | null, image?: string | null } } }> } | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null } };
|
||||
|
||||
export type TicketOverviewUpdatesSubscriptionVariables = Exact<{
|
||||
withTicketCount: Scalars['Boolean']['input'];
|
||||
|
|
@ -5851,7 +5851,7 @@ export type ReferencingTicketFragment = { __typename?: 'Ticket', id: string, int
|
|||
|
||||
export type TicketArticleAttributesFragment = { __typename?: 'TicketArticle', id: string, internalId: number, messageId?: string | null, subject?: string | null, messageIdMd5?: string | null, inReplyTo?: string | null, contentType: string, preferences?: any | null, bodyWithUrls: string, internal: boolean, createdAt: string, from?: { __typename?: 'AddressesField', raw: string, parsed?: Array<{ __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null, isSystemAddress: boolean }> | null } | null, to?: { __typename?: 'AddressesField', raw: string, parsed?: Array<{ __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null, isSystemAddress: boolean }> | null } | null, cc?: { __typename?: 'AddressesField', raw: string, parsed?: Array<{ __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null, isSystemAddress: boolean }> | null } | null, replyTo?: { __typename?: 'AddressesField', raw: string, parsed?: Array<{ __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null, isSystemAddress: boolean }> | null } | null, attachmentsWithoutInline: Array<{ __typename?: 'StoredFile', id: string, internalId: number, name: string, size?: number | null, type?: string | null, preferences?: any | null }>, author: { __typename?: 'User', id: string, fullname?: string | null, firstname?: string | null, lastname?: string | null, email?: string | null, active?: boolean | null, image?: string | null, vip?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, authorizations?: Array<{ __typename?: 'Authorization', provider: string, uid: string, username?: string | null }> | null }, type?: { __typename?: 'TicketArticleType', name?: string | null, communication?: boolean | null } | null, sender?: { __typename?: 'TicketArticleSender', name?: EnumTicketArticleSenderName | null } | null, securityState?: { __typename?: 'TicketArticleSecurityState', encryptionMessage?: string | null, encryptionSuccess?: boolean | null, signingMessage?: string | null, signingSuccess?: boolean | null, type?: EnumSecurityStateType | null } | null, mediaErrorState?: { __typename?: 'TicketArticleMediaErrorState', error?: boolean | null } | null };
|
||||
|
||||
export type TicketAttributesFragment = { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null };
|
||||
export type TicketAttributesFragment = { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null };
|
||||
|
||||
export type TicketLiveUserAttributesFragment = { __typename?: 'TicketLiveUser', user: { __typename?: 'User', id: string, firstname?: string | null, lastname?: string | null, fullname?: string | null, email?: string | null, vip?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, active?: boolean | null, image?: string | null }, apps: Array<{ __typename?: 'TicketLiveUserApp', name: EnumTaskbarApp, editing: boolean, lastInteraction: string }> };
|
||||
|
||||
|
|
@ -5864,7 +5864,7 @@ export type TicketCreateMutationVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type TicketCreateMutation = { __typename?: 'Mutations', ticketCreate?: { __typename?: 'TicketCreatePayload', ticket?: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null } | null, errors?: Array<{ __typename?: 'UserError', message: string, field?: string | null, exception?: EnumUserErrorException | null }> | null } | null };
|
||||
export type TicketCreateMutation = { __typename?: 'Mutations', ticketCreate?: { __typename?: 'TicketCreatePayload', ticket?: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null } | null, errors?: Array<{ __typename?: 'UserError', message: string, field?: string | null, exception?: EnumUserErrorException | null }> | null } | null };
|
||||
|
||||
export type TicketCustomerUpdateMutationVariables = Exact<{
|
||||
ticketId: Scalars['ID']['input'];
|
||||
|
|
@ -5872,7 +5872,7 @@ export type TicketCustomerUpdateMutationVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type TicketCustomerUpdateMutation = { __typename?: 'Mutations', ticketCustomerUpdate?: { __typename?: 'TicketCustomerUpdatePayload', ticket?: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null } | null, errors?: Array<{ __typename?: 'UserError', message: string, field?: string | null, exception?: EnumUserErrorException | null }> | null } | null };
|
||||
export type TicketCustomerUpdateMutation = { __typename?: 'Mutations', ticketCustomerUpdate?: { __typename?: 'TicketCustomerUpdatePayload', ticket?: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null } | null, errors?: Array<{ __typename?: 'UserError', message: string, field?: string | null, exception?: EnumUserErrorException | null }> | null } | null };
|
||||
|
||||
export type TicketMergeMutationVariables = Exact<{
|
||||
sourceTicketId: Scalars['ID']['input'];
|
||||
|
|
@ -5903,7 +5903,7 @@ export type TicketUpdateMutationVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type TicketUpdateMutation = { __typename?: 'Mutations', ticketUpdate?: { __typename?: 'TicketUpdatePayload', ticket?: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null } | null, errors?: Array<{ __typename?: 'UserError', message: string, field?: string | null, exception?: EnumUserErrorException | null }> | null } | null };
|
||||
export type TicketUpdateMutation = { __typename?: 'Mutations', ticketUpdate?: { __typename?: 'TicketUpdatePayload', ticket?: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null } | null, errors?: Array<{ __typename?: 'UserError', message: string, field?: string | null, exception?: EnumUserErrorException | null }> | null } | null };
|
||||
|
||||
export type TicketQueryVariables = Exact<{
|
||||
ticketId?: InputMaybe<Scalars['ID']['input']>;
|
||||
|
|
@ -5912,7 +5912,7 @@ export type TicketQueryVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type TicketQuery = { __typename?: 'Queries', ticket: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, createArticleType?: { __typename?: 'TicketArticleType', id: string, name?: string | null } | null, mentions?: { __typename?: 'MentionConnection', totalCount: number, edges: Array<{ __typename?: 'MentionEdge', cursor: string, node: { __typename?: 'Mention', user: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, vip?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, active?: boolean | null, image?: string | null } } }> } | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null } };
|
||||
export type TicketQuery = { __typename?: 'Queries', ticket: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, createArticleType?: { __typename?: 'TicketArticleType', id: string, name?: string | null } | null, mentions?: { __typename?: 'MentionConnection', totalCount: number, edges: Array<{ __typename?: 'MentionEdge', cursor: string, node: { __typename?: 'Mention', user: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, vip?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, active?: boolean | null, image?: string | null } } }> } | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, incomplete: number, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null } };
|
||||
|
||||
export type TicketArticlesQueryVariables = Exact<{
|
||||
ticketId?: InputMaybe<Scalars['ID']['input']>;
|
||||
|
|
@ -5957,7 +5957,7 @@ export type TicketUpdatesSubscriptionVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type TicketUpdatesSubscription = { __typename?: 'Subscriptions', ticketUpdates: { __typename?: 'TicketUpdatesPayload', ticket?: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, createArticleType?: { __typename?: 'TicketArticleType', id: string, name?: string | null } | null, mentions?: { __typename?: 'MentionConnection', totalCount: number, edges: Array<{ __typename?: 'MentionEdge', cursor: string, node: { __typename?: 'Mention', user: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, vip?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, active?: boolean | null, image?: string | null } } }> } | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null } | null } };
|
||||
export type TicketUpdatesSubscription = { __typename?: 'Subscriptions', ticketUpdates: { __typename?: 'TicketUpdatesPayload', ticket?: { __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, createdAt: string, escalationAt?: string | null, updatedAt: string, pendingTime?: string | null, tags?: Array<string> | null, timeUnit?: number | null, subscribed?: boolean | null, preferences?: any | null, stateColorCode: EnumTicketStateColorCode, sharedDraftZoomId?: string | null, firstResponseEscalationAt?: string | null, closeEscalationAt?: string | null, updateEscalationAt?: string | null, initialChannel?: EnumChannelArea | null, createArticleType?: { __typename?: 'TicketArticleType', id: string, name?: string | null } | null, mentions?: { __typename?: 'MentionConnection', totalCount: number, edges: Array<{ __typename?: 'MentionEdge', cursor: string, node: { __typename?: 'Mention', user: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, vip?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, active?: boolean | null, image?: string | null } } }> } | null, checklist?: { __typename?: 'Checklist', id: string, completed: boolean, incomplete: number, total: number, complete: number } | null, referencingChecklistTickets?: Array<{ __typename?: 'Ticket', id: string, internalId: number, number: string, title: string, stateColorCode: EnumTicketStateColorCode, state: { __typename?: 'TicketState', id: string, name: string } }> | null, updatedBy?: { __typename?: 'User', id: string } | null, owner: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null }, customer: { __typename?: 'User', id: string, internalId: number, firstname?: string | null, lastname?: string | null, fullname?: string | null, phone?: string | null, mobile?: string | null, image?: string | null, vip?: boolean | null, active?: boolean | null, outOfOffice?: boolean | null, outOfOfficeStartAt?: string | null, outOfOfficeEndAt?: string | null, email?: string | null, hasSecondaryOrganizations?: boolean | null, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, active?: boolean | null, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null } | null, policy: { __typename?: 'PolicyDefault', update: boolean } }, organization?: { __typename?: 'Organization', id: string, internalId: number, name?: string | null, vip?: boolean | null, active?: boolean | null } | null, state: { __typename?: 'TicketState', id: string, name: string, stateType: { __typename?: 'TicketStateType', id: string, name: string } }, group: { __typename?: 'Group', id: string, name?: string | null, emailAddress?: { __typename?: 'EmailAddressParsed', name?: string | null, emailAddress?: string | null } | null }, priority: { __typename?: 'TicketPriority', id: string, name: string, defaultCreate: boolean, uiColor?: string | null }, objectAttributeValues?: Array<{ __typename?: 'ObjectAttributeValue', value?: any | null, renderedLink?: string | null, attribute: { __typename?: 'ObjectManagerFrontendAttribute', name: string, display: string } }> | null, policy: { __typename?: 'PolicyTicket', update: boolean, agentReadAccess: boolean }, timeUnitsPerType?: Array<{ __typename?: 'TicketTimeAccountingTypeSum', name: string, timeUnit: number }> | null } | null } };
|
||||
|
||||
export type TokenAttributesFragment = { __typename?: 'Token', id: string, name?: string | null, preferences?: any | null, expiresAt?: string | null, lastUsedAt?: string | null, createdAt: string, user?: { __typename?: 'User', id: string } | null };
|
||||
|
||||
|
|
|
|||
13
app/graphql/gql/concerns/ensures_checklist_feature_active.rb
Normal file
13
app/graphql/gql/concerns/ensures_checklist_feature_active.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
module Gql::Concerns::EnsuresChecklistFeatureActive
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
||||
def self.ensure_checklist_feature_active!
|
||||
raise Exceptions::Forbidden, 'The checklist feature is not active' if !Setting.get('checklist') # rubocop:disable Zammad/DetectTranslatableString
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
module Gql::Mutations
|
||||
class Ticket::Checklist::Base < BaseMutation
|
||||
include Gql::Concerns::EnsuresChecklistFeatureActive
|
||||
|
||||
description 'Base class for checklist mutations.'
|
||||
|
||||
def self.authorize(_obj, ctx)
|
||||
ensure_checklist_feature_active!
|
||||
ctx.current_user.permissions?(['ticket.agent'])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module Gql::Queries
|
||||
class Checklist::Templates < BaseQuery
|
||||
include Gql::Concerns::EnsuresChecklistFeatureActive
|
||||
|
||||
description 'Fetch checklist templates'
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ module Gql::Queries
|
|||
type [Gql::Types::Checklist::TemplateType, { null: false }], null: false
|
||||
|
||||
def self.authorize(_obj, ctx)
|
||||
ensure_checklist_feature_active!
|
||||
ctx.current_user.permissions?(['ticket.agent'])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module Gql::Queries
|
||||
class Ticket::Checklist < BaseQuery
|
||||
include Gql::Concerns::EnsuresChecklistFeatureActive
|
||||
|
||||
description 'Fetch ticket checklist'
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ module Gql::Queries
|
|||
type Gql::Types::ChecklistType, null: true
|
||||
|
||||
def self.authorize(_obj, ctx)
|
||||
ensure_checklist_feature_active!
|
||||
ctx.current_user.permissions?(['ticket.agent'])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module Gql::Subscriptions
|
||||
class Checklist::TemplateUpdates < BaseSubscription
|
||||
include Gql::Concerns::EnsuresChecklistFeatureActive
|
||||
|
||||
description 'Subscription for checklist template changes.'
|
||||
|
||||
|
|
@ -9,6 +10,11 @@ module Gql::Subscriptions
|
|||
|
||||
field :checklist_templates, [Gql::Types::Checklist::TemplateType, { null: false }], description: 'Checklist templates'
|
||||
|
||||
def self.authorize(_obj, ctx)
|
||||
ensure_checklist_feature_active!
|
||||
super
|
||||
end
|
||||
|
||||
def authorized?(only_active:)
|
||||
context.current_user.permissions?('ticket.agent')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module Gql::Subscriptions
|
||||
class Ticket::ChecklistUpdates < BaseSubscription
|
||||
include Gql::Concerns::EnsuresChecklistFeatureActive
|
||||
|
||||
description 'Subscription for ticket checklist changes.'
|
||||
|
||||
|
|
@ -10,6 +11,11 @@ module Gql::Subscriptions
|
|||
field :ticket_checklist, Gql::Types::ChecklistType, description: 'Ticket checklist'
|
||||
field :removed_ticket_checklist, Boolean, description: 'Ticket checklist was removed from ticket'
|
||||
|
||||
def self.authorize(_obj, ctx)
|
||||
ensure_checklist_feature_active!
|
||||
super
|
||||
end
|
||||
|
||||
def authorized?(ticket_id:)
|
||||
context.current_user.permissions?('ticket.agent') && Gql::ZammadSchema.authorized_object_from_id(ticket_id, type: ::Ticket, user: context.current_user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -114,7 +114,15 @@ module Gql::Types
|
|||
Gql::ZammadSchema.id_from_object(@object.shared_draft)
|
||||
end
|
||||
|
||||
def checklist
|
||||
return nil if !Setting.get('checklist')
|
||||
|
||||
@object.checklist
|
||||
end
|
||||
|
||||
def referencing_checklist_tickets
|
||||
return nil if !Setting.get('checklist')
|
||||
|
||||
::Checklist.tickets_referencing(@object, context.current_user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,8 @@ class Checklist < ApplicationModel
|
|||
belongs_to :ticket
|
||||
has_many :items, inverse_of: :checklist, dependent: :destroy
|
||||
|
||||
scope :for_user, ->(user) { joins(:ticket).where(ticket: { group: user.group_ids_access('read') }) }
|
||||
|
||||
before_validation :ensure_text_not_nil
|
||||
|
||||
validates :ticket_id, uniqueness: true
|
||||
validates :name, length: { maximum: 250 }
|
||||
|
||||
history_attributes_ignored :sorted_item_ids
|
||||
|
||||
|
|
@ -55,9 +52,7 @@ class Checklist < ApplicationModel
|
|||
end
|
||||
|
||||
def incomplete
|
||||
Auth::RequestCache.fetch_value("Checklist/#{id}/incomplete") do
|
||||
items.count(&:incomplete?)
|
||||
end
|
||||
items.incomplete.count
|
||||
end
|
||||
|
||||
def total
|
||||
|
|
@ -88,12 +83,15 @@ class Checklist < ApplicationModel
|
|||
.resolve
|
||||
end
|
||||
|
||||
private
|
||||
def self.ticket_closed?(ticket)
|
||||
state = Ticket::State.lookup id: ticket.state_id
|
||||
state_type = Ticket::StateType.lookup id: state.state_type_id
|
||||
|
||||
def ensure_text_not_nil
|
||||
self.name ||= ''
|
||||
%w[closed merged].include? state_type.name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_ticket
|
||||
ticket.updated_at = Time.current
|
||||
ticket.save!
|
||||
|
|
|
|||
|
|
@ -14,7 +14,13 @@ class Checklist
|
|||
return data if data[ app_model ][ id ]
|
||||
|
||||
data[ app_model ][ id ] = attributes_with_association_ids
|
||||
items.map { |item| data = item.assets(data) }
|
||||
|
||||
if ticket && !ticket.authorized_asset?
|
||||
data[self.class.to_app_model][id]['ticket_inaccessible'] = true
|
||||
end
|
||||
|
||||
items.each { |elem| elem.assets(data) }
|
||||
|
||||
data
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,20 +10,27 @@ class Checklist::Item < ApplicationModel
|
|||
attr_accessor :initial_clone
|
||||
|
||||
belongs_to :checklist
|
||||
belongs_to :ticket, optional: true
|
||||
belongs_to :ticket, optional: true, inverse_of: :referencing_checklist_items
|
||||
|
||||
scope :for_user, ->(user) { joins(checklist: :ticket).where(tickets: { group: user.group_ids_access('read') }) }
|
||||
scope :incomplete, -> { where(checked: false) }
|
||||
|
||||
before_validation :detect_ticket_reference, unless: :initial_clone
|
||||
before_validation :ensure_text_not_nil
|
||||
before_validation :detect_ticket_reference_state
|
||||
|
||||
validate :detect_ticket_loop_reference, on: %i[create update], unless: -> { ticket.blank? || checklist.blank? }
|
||||
validate :validate_item_count, on: :create
|
||||
validate :detect_ticket_loop_reference, unless: -> { ticket.blank? || checklist.blank? }
|
||||
validate :validate_item_count, on: :create, unless: -> { checklist.blank? }
|
||||
|
||||
# MySQL does not support default value on non-null text columns
|
||||
# Can be removed after dropping MySQL
|
||||
before_validation :ensure_text_not_nil, if: -> { ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2' }
|
||||
|
||||
after_update :history_update_checked, if: -> { saved_change_to_checked? }
|
||||
after_destroy :update_checklist_on_destroy
|
||||
after_destroy :update_referenced_ticket
|
||||
after_save :update_checklist_on_save
|
||||
|
||||
after_save :update_referenced_ticket
|
||||
|
||||
history_attributes_ignored :checked
|
||||
|
||||
def history_log_attributes
|
||||
|
|
@ -56,12 +63,6 @@ class Checklist::Item < ApplicationModel
|
|||
}
|
||||
end
|
||||
|
||||
def incomplete?
|
||||
return ticket_incomplete? if ticket.present?
|
||||
|
||||
!checked
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_checklist_on_save
|
||||
|
|
@ -81,7 +82,7 @@ class Checklist::Item < ApplicationModel
|
|||
end
|
||||
|
||||
def detect_ticket_reference
|
||||
return if changes.key?(:ticket_id)
|
||||
return if ticket_id_changed?
|
||||
|
||||
ticket = Ticket::Number.check(text)
|
||||
return if ticket.blank?
|
||||
|
|
@ -89,6 +90,13 @@ class Checklist::Item < ApplicationModel
|
|||
self.ticket = ticket
|
||||
end
|
||||
|
||||
def detect_ticket_reference_state
|
||||
return if !ticket
|
||||
return if !ticket_id_changed?
|
||||
|
||||
self.checked = Checklist.ticket_closed?(ticket)
|
||||
end
|
||||
|
||||
def detect_ticket_loop_reference
|
||||
return if ticket.id != checklist.ticket.id
|
||||
|
||||
|
|
@ -101,13 +109,20 @@ class Checklist::Item < ApplicationModel
|
|||
errors.add(:base, __('Checklist items are limited to 100 items per checklist.'))
|
||||
end
|
||||
|
||||
def ticket_incomplete?
|
||||
# Consider the following ticket state types as incomplete:
|
||||
# - closed
|
||||
# - merged
|
||||
!ticket.state.state_type.name.match?(%r{^(closed|merged)$}i)
|
||||
def update_referenced_ticket
|
||||
return if !saved_change_to_ticket_id? && !destroyed?
|
||||
|
||||
[ticket_id, ticket_id_before_last_save]
|
||||
.compact
|
||||
.map { |elem| Ticket.find_by(id: elem) }
|
||||
.each do |elem|
|
||||
elem.updated_at = Time.current
|
||||
elem.save!
|
||||
end
|
||||
end
|
||||
|
||||
# MySQL does not support default value on non-null text columns
|
||||
# Can be removed after dropping MySQL
|
||||
def ensure_text_not_nil
|
||||
self.text ||= ''
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,28 +4,21 @@ class Checklist::Item
|
|||
module Assets
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def assets(...)
|
||||
data = super
|
||||
def assets(data)
|
||||
app_model = self.class.to_app_model
|
||||
|
||||
if !data[ app_model ]
|
||||
data[ app_model ] = {}
|
||||
end
|
||||
Rails.logger.debug [app_model, id, data[app_model]]
|
||||
return data if data[ app_model ][ id ]
|
||||
|
||||
data[ app_model ][ id ] = attributes_with_association_ids
|
||||
|
||||
checklist.assets(data)
|
||||
ticket&.assets(data) if ticket&.authorized_asset?
|
||||
|
||||
add_referencing_ticket_assets(data)
|
||||
checklist.assets(data)
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_referencing_ticket_assets(data)
|
||||
return if !ticket
|
||||
|
||||
if !checklist.ticket.authorized_asset?
|
||||
data[self.class.to_app_model][id]['ticket_inaccessible'] = true
|
||||
return
|
||||
end
|
||||
|
||||
ticket.assets(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class ChecklistTemplate < ApplicationModel
|
|||
|
||||
has_many :items, inverse_of: :checklist_template, dependent: :destroy
|
||||
|
||||
validates :name, presence: { allow_blank: true }
|
||||
validates :name, length: { maximum: 250 }
|
||||
|
||||
def create_from_template!(ticket_id:)
|
||||
raise ActiveRecord::RecordInvalid if !active
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
class ChecklistTemplate::Item < ApplicationModel
|
||||
include ChecksClientNotification
|
||||
include HasDefaultModelUserRelations
|
||||
include ChecklistTemplate::TriggersSubscriptions
|
||||
include ChecklistTemplate::Item::Assets
|
||||
|
||||
belongs_to :checklist_template
|
||||
|
||||
# MySQL does not support default value on non-null text columns
|
||||
# Can be removed after dropping MySQL
|
||||
before_validation :ensure_text_not_nil, if: -> { ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2' }
|
||||
|
||||
after_create :update_checklist
|
||||
after_destroy :update_checklist
|
||||
|
||||
validates :text, presence: { allow_blank: true }
|
||||
validate :validate_item_count, on: :create
|
||||
validate :validate_item_count, on: :create, unless: -> { checklist_template.blank? }
|
||||
|
||||
private
|
||||
|
||||
|
|
@ -31,4 +32,10 @@ class ChecklistTemplate::Item < ApplicationModel
|
|||
|
||||
errors.add(:base, __('Checklist Template items are limited to 100 items per checklist.'))
|
||||
end
|
||||
|
||||
# MySQL does not support default value on non-null text columns
|
||||
# Can be removed after dropping MySQL
|
||||
def ensure_text_not_nil
|
||||
self.text ||= ''
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class ChecklistTemplate::Item
|
||||
module Assets
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def assets(data)
|
||||
app_model = self.class.to_app_model
|
||||
|
||||
if !data[ app_model ]
|
||||
data[ app_model ] = {}
|
||||
end
|
||||
return data if data[ app_model ][ id ]
|
||||
|
||||
data[ app_model ][ id ] = attributes_with_association_ids
|
||||
checklist_template.assets(data)
|
||||
data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15,7 +15,7 @@ module CanChecklistSortedItems
|
|||
end
|
||||
|
||||
def default_sorted_item_ids
|
||||
self.sorted_item_ids = [] if sorted_item_ids.nil?
|
||||
self.sorted_item_ids ||= []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class Ticket < ApplicationModel
|
|||
include Ticket::TouchesAssociations
|
||||
include Ticket::TriggersSubscriptions
|
||||
include Ticket::ChecksReopenAfterCertainTime
|
||||
include Ticket::Checklists
|
||||
|
||||
include ::Ticket::Escalation
|
||||
include ::Ticket::Subject
|
||||
|
|
@ -95,9 +96,7 @@ class Ticket < ApplicationModel
|
|||
has_many :articles, -> { reorder(:created_at, :id) }, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update, dependent: :destroy, inverse_of: :ticket
|
||||
has_many :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', dependent: :destroy, inverse_of: :ticket
|
||||
has_many :mentions, as: :mentionable, dependent: :destroy
|
||||
has_many :checklist_items, class_name: 'Checklist::Item', dependent: :nullify
|
||||
has_one :shared_draft, class_name: 'Ticket::SharedDraftZoom', inverse_of: :ticket, dependent: :destroy
|
||||
has_one :checklist, dependent: :destroy
|
||||
belongs_to :state, class_name: 'Ticket::State', optional: true
|
||||
belongs_to :priority, class_name: 'Ticket::Priority', optional: true
|
||||
belongs_to :owner, class_name: 'User', optional: true
|
||||
|
|
|
|||
|
|
@ -23,20 +23,30 @@ returns
|
|||
=end
|
||||
|
||||
def assets(data)
|
||||
app_model_ticket = Ticket.to_app_model
|
||||
app_model = self.class.to_app_model
|
||||
|
||||
if !data[ app_model_ticket ]
|
||||
data[ app_model_ticket ] = {}
|
||||
if !data[ app_model ]
|
||||
data[ app_model ] = {}
|
||||
end
|
||||
return data if data[ app_model_ticket ][ id ]
|
||||
Rails.logger.debug [app_model, id, data[app_model]]
|
||||
return data if data[ app_model ][ id ]
|
||||
|
||||
data[app_model_ticket][id] = attributes_with_association_ids
|
||||
data[ app_model ][ id ] = attributes_with_association_ids
|
||||
|
||||
group.assets(data)
|
||||
organization&.assets(data)
|
||||
checklist&.assets(data)
|
||||
assets_user(data)
|
||||
|
||||
if Setting.get('checklist')
|
||||
checklist&.assets(data)
|
||||
referencing_checklists
|
||||
.includes(:ticket)
|
||||
.each do |elem|
|
||||
elem.assets(data)
|
||||
elem.ticket.assets(data) if elem.ticket.authorized_asset?
|
||||
end
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
|
|
|
|||
39
app/models/ticket/checklists.rb
Normal file
39
app/models/ticket/checklists.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
module Ticket::Checklists
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :referencing_checklist_items, class_name: 'Checklist::Item', dependent: :nullify
|
||||
has_many :referencing_checklists, class_name: 'Checklist', through: :referencing_checklist_items, source: :checklist
|
||||
has_one :checklist, dependent: :destroy
|
||||
|
||||
after_save :update_referenced_checklist_items
|
||||
|
||||
association_attributes_ignored :referencing_checklist_items
|
||||
end
|
||||
|
||||
def attributes_with_association_ids
|
||||
attributes = super
|
||||
|
||||
return attributes if !Setting.get('checklist')
|
||||
|
||||
attributes['checklist_id'] = checklist&.id
|
||||
attributes['checklist_incomplete'] = checklist&.incomplete
|
||||
attributes['checklist_total'] = checklist&.total
|
||||
|
||||
attributes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_referenced_checklist_items
|
||||
return if !saved_change_to_state_id?
|
||||
|
||||
is_closed = Checklist.ticket_closed?(self)
|
||||
|
||||
referencing_checklist_items
|
||||
.where(checked: !is_closed)
|
||||
.each { |elem| elem.update! checked: is_closed }
|
||||
end
|
||||
end
|
||||
|
|
@ -15,15 +15,13 @@ module Ticket::TriggersSubscriptions
|
|||
Gql::Subscriptions::TicketUpdates.trigger(self, arguments: { ticket_id: Gql::ZammadSchema.id_from_object(self) })
|
||||
end
|
||||
|
||||
TRIGGER_CHECKLIST_UPDATE_ON = %w[title state_id group_id].freeze
|
||||
TRIGGER_CHECKLIST_UPDATE_ON = %w[title group_id].freeze
|
||||
|
||||
def trigger_checklist_subscriptions
|
||||
return if !saved_changes.keys.intersect? TRIGGER_CHECKLIST_UPDATE_ON
|
||||
|
||||
referenced_in_checklists = checklist_items.pluck(:checklist_id)
|
||||
|
||||
Checklist
|
||||
.where(id: referenced_in_checklists)
|
||||
.where(id: referencing_checklists)
|
||||
.includes(:ticket)
|
||||
.each do |elem|
|
||||
Gql::Subscriptions::Ticket::ChecklistUpdates.trigger(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ class Checklist::ItemPolicy < ApplicationPolicy
|
|||
private
|
||||
|
||||
def checklist_policy
|
||||
ChecklistPolicy.new(user, record.checklist)
|
||||
ChecklistPolicy.new(user, record&.checklist)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,23 +2,27 @@
|
|||
|
||||
class ChecklistPolicy < ApplicationPolicy
|
||||
def show?
|
||||
ticket_policy.agent_read_access?
|
||||
check_prerequisites? && ticket_policy.agent_read_access?
|
||||
end
|
||||
|
||||
def create?
|
||||
ticket_policy.agent_update_access?
|
||||
check_prerequisites? && ticket_policy.agent_update_access?
|
||||
end
|
||||
|
||||
def update?
|
||||
ticket_policy.agent_update_access?
|
||||
check_prerequisites? && ticket_policy.agent_update_access?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
ticket_policy.agent_update_access?
|
||||
check_prerequisites? && ticket_policy.agent_update_access?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_prerequisites?
|
||||
Setting.get('checklist') && record&.ticket
|
||||
end
|
||||
|
||||
def ticket_policy
|
||||
TicketPolicy.new(user, record.ticket)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,23 +2,27 @@
|
|||
|
||||
class ChecklistTemplatePolicy < ApplicationPolicy
|
||||
def show?
|
||||
agent? || admin?
|
||||
checklist_feature_enabled? && (agent? || admin?)
|
||||
end
|
||||
|
||||
def create?
|
||||
admin?
|
||||
checklist_feature_enabled? && admin?
|
||||
end
|
||||
|
||||
def update?
|
||||
admin?
|
||||
checklist_feature_enabled? && admin?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
admin?
|
||||
checklist_feature_enabled? && admin?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def checklist_feature_enabled?
|
||||
Setting.get('checklist')
|
||||
end
|
||||
|
||||
def agent?
|
||||
user.permissions?('ticket.agent')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,27 +2,36 @@
|
|||
|
||||
class Controllers::ChecklistItemsControllerPolicy < Controllers::ApplicationControllerPolicy
|
||||
def create?
|
||||
update_access_via_ticket?
|
||||
Checklist::ItemPolicy
|
||||
.new(user, checklist&.items&.build)
|
||||
.create?
|
||||
end
|
||||
|
||||
def show?
|
||||
Checklist::ItemPolicy
|
||||
.new(user, checklist_item)
|
||||
.show?
|
||||
end
|
||||
|
||||
def update?
|
||||
update_access_via_ticket?
|
||||
Checklist::ItemPolicy
|
||||
.new(user, checklist_item)
|
||||
.update?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
update_access_via_ticket?
|
||||
Checklist::ItemPolicy
|
||||
.new(user, checklist_item)
|
||||
.destroy?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ticket_policy
|
||||
ticket = Checklist.lookup(id: record.params[:checklist_id])&.ticket || Checklist::Item.lookup(id: record.params[:id])&.checklist&.ticket
|
||||
@ticket_policy ||= TicketPolicy.new(user, ticket)
|
||||
def checklist
|
||||
Checklist.lookup(id: record.params[:checklist_id])
|
||||
end
|
||||
|
||||
def update_access_via_ticket?
|
||||
user.permissions?(['ticket.agent']) && ticket_policy.agent_update_access?
|
||||
def checklist_item
|
||||
Checklist::Item.lookup(id: record.params[:id])
|
||||
end
|
||||
|
||||
default_permit!(['ticket.agent'])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class Controllers::ChecklistTemplateItemsControllerPolicy < Controllers::ApplicationControllerPolicy
|
||||
permit! %i[index show], to: ['ticket.agent', 'admin.checklist']
|
||||
default_permit!(['admin.checklist'])
|
||||
end
|
||||
|
|
@ -2,27 +2,40 @@
|
|||
|
||||
class Controllers::ChecklistsControllerPolicy < Controllers::ApplicationControllerPolicy
|
||||
def create?
|
||||
update_access_via_ticket?
|
||||
ChecklistPolicy
|
||||
.new(user, ticket&.build_checklist)
|
||||
.create?
|
||||
end
|
||||
|
||||
def update?
|
||||
update_access_via_ticket?
|
||||
ChecklistPolicy
|
||||
.new(user, checklist)
|
||||
.update?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
update_access_via_ticket?
|
||||
ChecklistPolicy
|
||||
.new(user, checklist)
|
||||
.destroy?
|
||||
end
|
||||
|
||||
def show?
|
||||
ChecklistPolicy
|
||||
.new(user, checklist)
|
||||
.show?
|
||||
end
|
||||
|
||||
def show_by_ticket?
|
||||
user.permissions?('ticket.agent')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ticket_policy
|
||||
ticket = Checklist.lookup(id: record.params[:id])&.ticket || Ticket.lookup(id: record.params[:ticket_id])
|
||||
@ticket_policy ||= TicketPolicy.new(user, ticket)
|
||||
def checklist
|
||||
Checklist.lookup(id: record.params[:id])
|
||||
end
|
||||
|
||||
def update_access_via_ticket?
|
||||
user.permissions?(['ticket.agent']) && ticket_policy.agent_update_access?
|
||||
def ticket
|
||||
Ticket.lookup(id: record.params[:ticket_id])
|
||||
end
|
||||
|
||||
default_permit!(['ticket.agent'])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ class Controllers::TicketChecklistControllerPolicy < Controllers::ApplicationCon
|
|||
update_access_via_ticket?
|
||||
end
|
||||
|
||||
def completed?
|
||||
update_access_via_ticket?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ticket_policy
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class Controllers::TicketChecklistItemsControllerPolicy < Controllers::ApplicationControllerPolicy
|
||||
def create?
|
||||
update_access_via_ticket?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
update_access_via_ticket?
|
||||
end
|
||||
|
||||
def update?
|
||||
update_access_via_ticket?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ticket_policy
|
||||
@ticket_policy ||= TicketPolicy.new(user, Ticket.lookup(id: record.params[:ticket_id]))
|
||||
end
|
||||
|
||||
def update_access_via_ticket?
|
||||
ticket_policy.agent_update_access?
|
||||
end
|
||||
end
|
||||
|
|
@ -20,10 +20,14 @@ class Service::Ticket::Update::Validator
|
|||
private
|
||||
|
||||
def ticket_closed?
|
||||
return false if !ticket_data[:state]
|
||||
|
||||
ticket_data[:state].state_type.name == 'closed'
|
||||
end
|
||||
|
||||
def ticket_pending_close?
|
||||
return false if !ticket_data[:state]
|
||||
|
||||
ticket_data[:state].state_type.name == 'pending action' && ticket_data[:state].next_state.state_type.name == 'closed'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
Zammad::Application.routes.draw do
|
||||
scope Rails.configuration.api_path do
|
||||
resources :checklists, only: %i[index show create update destroy]
|
||||
resources :checklists, only: %i[index show create update destroy] do
|
||||
collection do
|
||||
get 'by_ticket/:ticket_id', to: 'checklists#show_by_ticket'
|
||||
end
|
||||
end
|
||||
|
||||
resources :checklist_items, only: %i[create update destroy show]
|
||||
resources :checklist_templates, only: %i[index show create update destroy]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
Zammad::Application.routes.draw do
|
||||
scope Rails.configuration.api_path do
|
||||
resources :checklist_items, only: %i[index show create update destroy]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
Zammad::Application.routes.draw do
|
||||
scope Rails.configuration.api_path do
|
||||
resources :checklist_templates, only: %i[index show create update destroy]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
Zammad::Application.routes.draw do
|
||||
scope Rails.configuration.api_path do
|
||||
resources :checklist_template_items, only: %i[index show create update destroy]
|
||||
end
|
||||
end
|
||||
|
|
@ -69,16 +69,4 @@ Zammad::Application.routes.draw do
|
|||
match api_path + '/ticket_article_plain/:id', to: 'ticket_articles#article_plain', via: :get
|
||||
match api_path + '/ticket_articles/:id/retry_security_process', to: 'ticket_articles#retry_security_process', via: :post
|
||||
match api_path + '/ticket_articles/:id/retry_whatsapp_attachment_download', to: 'ticket_articles#retry_whatsapp_attachment_download', via: :post
|
||||
|
||||
# ticket checklist
|
||||
match api_path + '/tickets/:ticket_id/checklist/completed', to: 'ticket_checklist#completed', via: :get
|
||||
scope Rails.configuration.api_path do
|
||||
scope 'tickets/:ticket_id' do
|
||||
resource 'checklist', controller: 'ticket_checklist', only: %i[show create destroy update] do
|
||||
member do
|
||||
resources :items, controller: 'ticket_checklist_items', only: %i[create update destroy]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -549,7 +549,7 @@ class CreateTicket < ActiveRecord::Migration[4.2]
|
|||
end
|
||||
|
||||
create_table :checklists do |t|
|
||||
t.string :name, limit: 250, null: false
|
||||
t.string :name, limit: 250, null: false, default: ''
|
||||
if Rails.application.config.db_column_array
|
||||
t.string :sorted_item_ids, null: false, array: true, default: []
|
||||
else
|
||||
|
|
@ -557,12 +557,16 @@ class CreateTicket < ActiveRecord::Migration[4.2]
|
|||
end
|
||||
t.references :created_by, null: false, foreign_key: { to_table: :users }
|
||||
t.references :updated_by, null: false, foreign_key: { to_table: :users }
|
||||
t.references :ticket, null: true, foreign_key: true, index: { unique: true }
|
||||
t.references :ticket, null: false, foreign_key: true, index: { unique: true }
|
||||
t.timestamps limit: 3, null: false
|
||||
end
|
||||
|
||||
create_table :checklist_items do |t|
|
||||
t.text :text, null: false
|
||||
if ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2'
|
||||
t.text :text, null: false
|
||||
else
|
||||
t.text :text, null: false, default: ''
|
||||
end
|
||||
t.boolean :checked, null: false, default: false
|
||||
t.references :checklist, null: false, foreign_key: true
|
||||
t.references :created_by, null: false, foreign_key: { to_table: :users }
|
||||
|
|
@ -570,9 +574,10 @@ class CreateTicket < ActiveRecord::Migration[4.2]
|
|||
t.references :ticket, null: true, foreign_key: true
|
||||
t.timestamps limit: 3, null: false
|
||||
end
|
||||
add_index :checklist_items, [:checked]
|
||||
|
||||
create_table :checklist_templates do |t|
|
||||
t.string :name, limit: 250, null: false
|
||||
t.string :name, limit: 250, null: false, default: ''
|
||||
t.boolean :active, default: true, null: false
|
||||
if Rails.application.config.db_column_array
|
||||
t.string :sorted_item_ids, null: false, array: true, default: []
|
||||
|
|
@ -583,9 +588,14 @@ class CreateTicket < ActiveRecord::Migration[4.2]
|
|||
t.references :updated_by, null: false, foreign_key: { to_table: :users }
|
||||
t.timestamps limit: 3, null: false
|
||||
end
|
||||
add_index :checklist_templates, [:active]
|
||||
|
||||
create_table :checklist_template_items do |t|
|
||||
t.text :text, null: false
|
||||
if ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2'
|
||||
t.text :text, null: false
|
||||
else
|
||||
t.text :text, null: false, default: ''
|
||||
end
|
||||
t.references :checklist_template, null: false, foreign_key: true
|
||||
t.references :created_by, null: false, foreign_key: { to_table: :users }
|
||||
t.references :updated_by, null: false, foreign_key: { to_table: :users }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class ChecklistImproveReferenceTracking < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
# return if it's a new setup
|
||||
return if !Setting.exists?(name: 'system_init_done')
|
||||
|
||||
add_default_empty_strings
|
||||
add_indexes
|
||||
change_column_null :checklists, :ticket_id, false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_default_empty_strings
|
||||
change_column_default :checklists, :name, ''
|
||||
change_column_default :checklist_templates, :name, ''
|
||||
|
||||
# MySQL does not support default text on non-null text items
|
||||
return if ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2'
|
||||
|
||||
change_column_default :checklist_items, :text, ''
|
||||
change_column_default :checklist_template_items, :text, ''
|
||||
end
|
||||
|
||||
def add_indexes
|
||||
add_index :checklist_items, :checked
|
||||
add_index :checklist_templates, :active
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
class SynchronizeChecklistItemStateFromTickets < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
# return if it's a new setup
|
||||
return if !Setting.exists?(name: 'system_init_done')
|
||||
|
||||
update_checklist_items
|
||||
end
|
||||
|
||||
def update_checklist_items
|
||||
Checklist::Item.where.not(ticket_id: nil).each do |item|
|
||||
item.update!(checked: Checklist.ticket_closed?(item.ticket))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -136,7 +136,7 @@ msgstr ""
|
|||
|
||||
#: app/assets/javascripts/app/controllers/ticket_zoom/sidebar_checklist_show.coffee:42
|
||||
#: app/assets/javascripts/app/views/ticket_zoom/sidebar_checklist_title_edit.jst.eco:6
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:54
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:50
|
||||
msgid "%s Checklist"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -596,7 +596,7 @@ msgstr ""
|
|||
msgid "A test ticket has been created, you can find it in your overview \"%s\" %l."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/ticket.rb:277
|
||||
#: app/models/ticket.rb:276
|
||||
msgid "A ticket cannot be merged into itself."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -944,7 +944,7 @@ msgstr ""
|
|||
msgid "Add Certificate"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:414
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:417
|
||||
msgid "Add Empty Checklist"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -1329,10 +1329,6 @@ msgstr ""
|
|||
msgid "Allow reopening of tickets within a certain time."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/checklist_template/index.jst.eco:18
|
||||
msgid "Allow users to add new checklists."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/tag/index.jst.eco:16
|
||||
msgid "Allow users to add new tags."
|
||||
msgstr ""
|
||||
|
|
@ -2677,12 +2673,12 @@ msgstr ""
|
|||
msgid "Checklist Template items are limited to 100 items per checklist."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/checklist/item.rb:101
|
||||
#: app/models/checklist/item.rb:109
|
||||
msgid "Checklist items are limited to 100 items per checklist."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/checklist_template.coffee:3
|
||||
#: app/assets/javascripts/app/views/checklist_template/index.jst.eco:3
|
||||
#: app/assets/javascripts/app/views/checklist_template/index.jst.eco:7
|
||||
#: db/seeds/permissions.rb:299
|
||||
#: db/seeds/settings.rb:5790
|
||||
msgid "Checklists"
|
||||
|
|
@ -3595,7 +3591,7 @@ msgstr ""
|
|||
#: app/assets/javascripts/app/models/checklist.coffee:9
|
||||
#: app/assets/javascripts/app/models/checklist_item.coffee:8
|
||||
#: app/assets/javascripts/app/models/checklist_template.coffee:10
|
||||
#: app/assets/javascripts/app/models/checklist_template_item.coffee:8
|
||||
#: app/assets/javascripts/app/models/checklist_template_item.coffee:6
|
||||
#: app/assets/javascripts/app/models/knowledge_base_answer_translation.coffee:7
|
||||
#: app/assets/javascripts/app/models/organization.coffee:9
|
||||
#: app/assets/javascripts/app/models/ticket.coffee:28
|
||||
|
|
@ -5522,10 +5518,6 @@ msgstr ""
|
|||
msgid "Enable Chat"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/checklist_template/index.jst.eco:16
|
||||
msgid "Enable Checklists"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/api.jst.eco:33
|
||||
#: db/seeds/settings.rb:3411
|
||||
msgid "Enable REST API access using the username/email address and password for the authentication user."
|
||||
|
|
@ -6140,11 +6132,11 @@ msgstr ""
|
|||
msgid "Failed Tasks"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:145
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:148
|
||||
msgid "Failed to add new checklist item."
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:166
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:169
|
||||
msgid "Failed to delete checklist item."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -6162,11 +6154,11 @@ msgstr ""
|
|||
msgid "Failed to roll back the migration of the channel!"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:152
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:155
|
||||
msgid "Failed to save checklist order."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/ticket_zoom/sidebar_checklist_show.coffee:117
|
||||
#: app/assets/javascripts/app/controllers/ticket_zoom/sidebar_checklist_show.coffee:114
|
||||
msgid "Failed to save the order of the checklist items. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -6178,7 +6170,7 @@ msgstr ""
|
|||
msgid "Failed to set up QR code. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:159
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:162
|
||||
msgid "Failed to update checklist item."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -7560,7 +7552,7 @@ msgstr ""
|
|||
msgid "Incomplete Ticket Checklist"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklist.vue:26
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklist.vue:29
|
||||
msgid "Incomplete checklist items"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -7904,7 +7896,7 @@ msgstr ""
|
|||
msgid "It is not possible to delete your current account."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/ticket.rb:275
|
||||
#: app/models/ticket.rb:274
|
||||
msgid "It is not possible to merge into an already merged ticket."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9267,7 +9259,7 @@ msgstr ""
|
|||
#: app/assets/javascripts/app/models/checklist.coffee:7
|
||||
#: app/assets/javascripts/app/models/checklist_item.coffee:7
|
||||
#: app/assets/javascripts/app/models/checklist_template.coffee:7
|
||||
#: app/assets/javascripts/app/models/checklist_template_item.coffee:7
|
||||
#: app/assets/javascripts/app/models/checklist_template_item.coffee:5
|
||||
#: app/assets/javascripts/app/models/core_workflow.coffee:6
|
||||
#: app/assets/javascripts/app/models/core_workflow_custom_module.coffee:4
|
||||
#: app/assets/javascripts/app/models/group.coffee:7
|
||||
|
|
@ -9708,7 +9700,7 @@ msgid "No certificate found."
|
|||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/ticket_zoom/sidebar_checklist_start.jst.eco:3
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:427
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:430
|
||||
msgid "No checklist added to this ticket yet."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9866,7 +9858,7 @@ msgstr ""
|
|||
msgid "No translation for this locale available"
|
||||
msgstr ""
|
||||
|
||||
#: app/models/ticket.rb:397
|
||||
#: app/models/ticket.rb:396
|
||||
msgid "No triggers active"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10934,7 +10926,7 @@ msgstr ""
|
|||
msgid "Please accept the %s."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/checklist_template.coffee:48
|
||||
#: app/assets/javascripts/app/controllers/checklist_template.coffee:55
|
||||
msgid "Please add at least one item to the checklist."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11573,7 +11565,7 @@ msgid "Remove authenticator app"
|
|||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/ticket_zoom/sidebar_checklist.coffee:22
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:373
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:376
|
||||
msgid "Remove checklist"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11623,7 +11615,7 @@ msgid "Removes the shadows for a flat look."
|
|||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/ticket_zoom/sidebar_checklist.coffee:17
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:366
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarChecklist/TicketSidebarChecklistContent.vue:369
|
||||
msgid "Rename checklist"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12959,11 +12951,11 @@ msgid "Show to user roles"
|
|||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/TicketDetailTopBar/TopBarHeader/TicketInformation/TicketInformationBadgeList/ReferencingTicketsBadgePopover.vue:54
|
||||
msgid "Show tracked ticket"
|
||||
msgid "Show tracking ticket"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/TicketDetailTopBar/TopBarHeader/TicketInformation/TicketInformationBadgeList/ReferencingTicketsBadgePopover.vue:55
|
||||
msgid "Show tracked tickets"
|
||||
msgid "Show tracking tickets"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/integration/cti.jst.eco:95
|
||||
|
|
@ -16081,14 +16073,11 @@ msgstr ""
|
|||
msgid "Track retweets"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/lib/app_post/popover_provider/ticket_references_popover_provider.coffee:8
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/TicketDetailTopBar/TopBarHeader/TicketInformation/TicketInformationBadgeList/ReferencingTicketsBadgePopover.vue:85
|
||||
msgid "Tracked as checklist item in"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/lib/app_post/popover_provider/ticket_references_popover_provider.coffee:8
|
||||
msgid "Tracked by checklist item by"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/customer_chat/chat_window.jst.eco:27
|
||||
msgid "Transfer conversation to another chat:"
|
||||
msgstr ""
|
||||
|
|
@ -16558,7 +16547,7 @@ msgstr ""
|
|||
#: app/assets/javascripts/app/models/checklist.coffee:10
|
||||
#: app/assets/javascripts/app/models/checklist_item.coffee:9
|
||||
#: app/assets/javascripts/app/models/checklist_template.coffee:11
|
||||
#: app/assets/javascripts/app/models/checklist_template_item.coffee:9
|
||||
#: app/assets/javascripts/app/models/checklist_template_item.coffee:7
|
||||
#: app/assets/javascripts/app/models/knowledge_base_answer_translation.coffee:8
|
||||
#: app/assets/javascripts/app/models/organization.coffee:11
|
||||
#: app/assets/javascripts/app/models/ticket.coffee:30
|
||||
|
|
@ -17274,7 +17263,7 @@ msgstr ""
|
|||
msgid "With checklist templates you can pre-fill your checklists."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/checklist_template/index.jst.eco:8
|
||||
#: app/assets/javascripts/app/views/checklist_template/index.jst.eco:12
|
||||
msgid "With checklists you can keep track of the progress of your ticket related tasks."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -18837,7 +18826,7 @@ msgstr ""
|
|||
msgid "recovery codes"
|
||||
msgstr ""
|
||||
|
||||
#: app/models/checklist/item.rb:95
|
||||
#: app/models/checklist/item.rb:103
|
||||
msgid "reference must not be the checklist ticket."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe SynchronizeChecklistItemStateFromTickets, :aggregate_failures, current_user_id: 1, type: :db_migration do
|
||||
let!(:checklist) do
|
||||
create(:checklist, ticket: create(:ticket), item_count: 1).tap do |checklist|
|
||||
if another_ticket
|
||||
checklist.items.last.update!(text: "Ticket##{another_ticket.number}", ticket_id: another_ticket.id)
|
||||
end
|
||||
checklist.items.last.update!(checked: checklist_item_checked)
|
||||
checklist.reload
|
||||
end
|
||||
end
|
||||
let(:checklist_item_checked) { true }
|
||||
let(:another_ticket) { create(:ticket, state: Ticket::State.find_by(name: state_name)) }
|
||||
let(:state_name) { 'open' }
|
||||
|
||||
context 'without linked ticket' do
|
||||
let(:another_ticket) { nil }
|
||||
|
||||
context 'when checklist item is checked' do
|
||||
it 'does not change the checklist item' do
|
||||
expect(checklist.items.first.checked).to be(true)
|
||||
expect { migrate }.not_to(change { checklist.items.first.checked })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when checklist item is unchecked' do
|
||||
let(:checklist_item_checked) { false }
|
||||
|
||||
it 'does not change the checklist item' do
|
||||
expect(checklist.items.first.checked).to be(false)
|
||||
expect { migrate }.not_to(change { checklist.items.first.checked })
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'with a linked ticket' do
|
||||
context 'when ticket is closed' do
|
||||
let(:state_name) { 'closed' }
|
||||
|
||||
context 'when checklist item is checked' do
|
||||
it 'does not change the checklist item' do
|
||||
expect(checklist.items.first.checked).to be(true)
|
||||
expect { migrate }.not_to(change { checklist.items.first.checked })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when checklist item is unchecked' do
|
||||
let(:checklist_item_checked) { false }
|
||||
|
||||
it 'checks the item' do
|
||||
expect { migrate }.to change { checklist.items.first.checked }.from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'when ticket is open' do
|
||||
context 'when checklist item is checked' do
|
||||
it 'unchecks the item' do
|
||||
expect { migrate }.to change { checklist.items.first.checked }.from(true).to(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when checklist item is unchecked' do
|
||||
let(:checklist_item_checked) { false }
|
||||
|
||||
it 'does not change the checklist item' do
|
||||
expect(checklist.items.first.checked).to be(false)
|
||||
expect { migrate }.not_to(change { checklist.items.first.checked })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -45,6 +45,7 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::Add, current_user_id: 1, type:
|
|||
end
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
checklist if defined?(checklist)
|
||||
gql.execute(query, variables: variables)
|
||||
end
|
||||
|
|
@ -70,6 +71,14 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::Add, current_user_id: 1, type:
|
|||
context 'with authenticated session', authenticated_as: :agent do
|
||||
it_behaves_like 'creating the ticket checklist'
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it_behaves_like 'raising an error', Exceptions::Forbidden
|
||||
end
|
||||
|
||||
context 'without access to the ticket' do
|
||||
let(:agent) { create(:agent) }
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::Delete, current_user_id: 1, ty
|
|||
let(:variables) { { checklistId: gql.id(checklist) } }
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
gql.execute(query, variables: variables)
|
||||
end
|
||||
|
||||
|
|
@ -42,6 +43,14 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::Delete, current_user_id: 1, ty
|
|||
context 'with authenticated session', authenticated_as: :agent do
|
||||
it_behaves_like 'deleting the ticket checklist'
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it_behaves_like 'raising an error', Exceptions::Forbidden
|
||||
end
|
||||
|
||||
context 'without access to the ticket' do
|
||||
let(:agent) { create(:agent) }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::ItemDelete, current_user_id: 1
|
|||
let(:variables) { { checklistId: gql.id(checklist), checklistItemId: gql.id(checklist_item) } }
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
gql.execute(query, variables: variables)
|
||||
end
|
||||
|
||||
|
|
@ -43,6 +44,14 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::ItemDelete, current_user_id: 1
|
|||
context 'with authenticated session', authenticated_as: :agent do
|
||||
it_behaves_like 'deleting the ticket checklist item'
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it_behaves_like 'raising an error', Exceptions::Forbidden
|
||||
end
|
||||
|
||||
context 'without access to the ticket' do
|
||||
let(:agent) { create(:agent) }
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::ItemOrderUpdate, current_user_
|
|||
let(:variables) { { checklistId: gql.id(checklist), order: order } }
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
gql.execute(query, variables: variables)
|
||||
end
|
||||
|
||||
|
|
@ -48,6 +49,14 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::ItemOrderUpdate, current_user_
|
|||
context 'with authenticated session', authenticated_as: :agent do
|
||||
it_behaves_like 'updating the ticket checklist item order'
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it_behaves_like 'raising an error', Exceptions::Forbidden
|
||||
end
|
||||
|
||||
context 'without access to the ticket' do
|
||||
let(:agent) { create(:agent) }
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::ItemUpsert, current_user_id: 1
|
|||
let(:variables) { { checklistId: gql.id(checklist), input: input } }
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
gql.execute(query, variables: variables)
|
||||
end
|
||||
|
||||
|
|
@ -60,6 +61,14 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::ItemUpsert, current_user_id: 1
|
|||
context 'with authenticated session', authenticated_as: :agent do
|
||||
it_behaves_like 'creating the ticket checklist item'
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it_behaves_like 'raising an error', Exceptions::Forbidden
|
||||
end
|
||||
|
||||
context 'when providing both checked state and text' do
|
||||
let(:input) { { 'checked' => true, 'text' => '' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::TitleUpdate, current_user_id:
|
|||
let(:variables) { { checklistId: gql.id(checklist), title: title } }
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
gql.execute(query, variables: variables)
|
||||
end
|
||||
|
||||
|
|
@ -46,6 +47,14 @@ RSpec.describe Gql::Mutations::Ticket::Checklist::TitleUpdate, current_user_id:
|
|||
context 'with authenticated session', authenticated_as: :agent do
|
||||
it_behaves_like 'updating the ticket checklist title'
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it_behaves_like 'raising an error', Exceptions::Forbidden
|
||||
end
|
||||
|
||||
context 'without access to the ticket' do
|
||||
let(:agent) { create(:agent) }
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ RSpec.describe Gql::Queries::Checklist::Templates, current_user_id: 1, type: :gr
|
|||
end
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
checklist_template
|
||||
gql.execute(query, variables: variables)
|
||||
end
|
||||
|
|
@ -51,6 +52,14 @@ RSpec.describe Gql::Queries::Checklist::Templates, current_user_id: 1, type: :gr
|
|||
context 'with authenticated session', authenticated_as: :agent do
|
||||
it_behaves_like 'returning template data'
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it_behaves_like 'raising an error', Exceptions::Forbidden
|
||||
end
|
||||
|
||||
context 'without agent permissions', authenticated_as: :customer do
|
||||
let(:customer) { create(:customer) }
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ RSpec.describe Gql::Queries::Ticket::Checklist, current_user_id: 1, type: :graph
|
|||
end
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
checklist
|
||||
gql.execute(query, variables: variables)
|
||||
end
|
||||
|
|
@ -80,6 +81,14 @@ RSpec.describe Gql::Queries::Ticket::Checklist, current_user_id: 1, type: :graph
|
|||
context 'with authenticated session', authenticated_as: :agent do
|
||||
it_behaves_like 'returning checklist data'
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it_behaves_like 'raising an error', Exceptions::Forbidden
|
||||
end
|
||||
|
||||
context 'without access to the ticket' do
|
||||
let(:agent) { create(:agent) }
|
||||
|
||||
|
|
|
|||
|
|
@ -105,41 +105,41 @@ RSpec.describe Gql::Queries::Ticket, current_user_id: 1, type: :graphql do
|
|||
context 'with an agent', authenticated_as: :agent do
|
||||
context 'with permission' do
|
||||
let(:agent) { create(:agent, groups: [ticket.group]) }
|
||||
let(:base_expected_result) do
|
||||
{
|
||||
'id' => gql.id(ticket),
|
||||
'internalId' => ticket.id,
|
||||
'number' => ticket.number,
|
||||
# Agent is allowed to see user data
|
||||
'owner' => include(
|
||||
'firstname' => ticket.owner.firstname,
|
||||
'email' => ticket.owner.email,
|
||||
'createdBy' => { 'internalId' => 1 },
|
||||
'updatedBy' => { 'internalId' => 1 },
|
||||
),
|
||||
'tags' => %w[tag1 tag2],
|
||||
'policy' => {
|
||||
'agentReadAccess' => true,
|
||||
'agentUpdateAccess' => true,
|
||||
'createMentions' => true,
|
||||
'destroy' => false,
|
||||
'followUp' => true,
|
||||
'update' => true
|
||||
},
|
||||
'stateColorCode' => 'open',
|
||||
'checklist' => {
|
||||
'name' => checklist.name
|
||||
},
|
||||
'referencingChecklistTickets' => [
|
||||
{
|
||||
'id' => gql.id(another_ticket)
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
let(:expected_result) { base_expected_result }
|
||||
|
||||
shared_examples 'finds the ticket' do
|
||||
let(:expected_result) do
|
||||
{
|
||||
'id' => gql.id(ticket),
|
||||
'internalId' => ticket.id,
|
||||
'number' => ticket.number,
|
||||
# Agent is allowed to see user data
|
||||
'owner' => include(
|
||||
'firstname' => ticket.owner.firstname,
|
||||
'email' => ticket.owner.email,
|
||||
'createdBy' => { 'internalId' => 1 },
|
||||
'updatedBy' => { 'internalId' => 1 },
|
||||
),
|
||||
'tags' => %w[tag1 tag2],
|
||||
'policy' => {
|
||||
'agentReadAccess' => true,
|
||||
'agentUpdateAccess' => true,
|
||||
'createMentions' => true,
|
||||
'destroy' => false,
|
||||
'followUp' => true,
|
||||
'update' => true
|
||||
},
|
||||
'stateColorCode' => 'open',
|
||||
'checklist' => {
|
||||
'name' => checklist.name
|
||||
},
|
||||
'referencingChecklistTickets' => [
|
||||
{
|
||||
'id' => gql.id(another_ticket)
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'finds the ticket' do
|
||||
expect(gql.result.data).to include(expected_result)
|
||||
end
|
||||
|
|
@ -169,6 +169,15 @@ RSpec.describe Gql::Queries::Ticket, current_user_id: 1, type: :graphql do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with having checklist feature disabled' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
let(:expected_result) { base_expected_result.merge({ 'checklist' => nil, 'referencingChecklistTickets' => nil }) }
|
||||
|
||||
include_examples 'finds the ticket'
|
||||
end
|
||||
|
||||
context 'with having time accounting enabled' do
|
||||
let(:ticket_time_accounting_types) { create_list(:ticket_time_accounting_type, 2) }
|
||||
let(:ticket_time_accounting) { create(:ticket_time_accounting, ticket: ticket, time_unit: 50) }
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ RSpec.describe Gql::Subscriptions::Checklist::TemplateUpdates, current_user_id:
|
|||
end
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
template if defined?(template)
|
||||
gql.execute(subscription, variables: variables, context: { channel: mock_channel })
|
||||
end
|
||||
|
|
@ -37,6 +38,16 @@ RSpec.describe Gql::Subscriptions::Checklist::TemplateUpdates, current_user_id:
|
|||
expect(gql.result.data).not_to be_nil
|
||||
end
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it 'denies subscription with an error' do
|
||||
expect(gql.result.error_type).to eq(Exceptions::Forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
it 'triggers after template create' do
|
||||
template = create(:checklist_template)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ RSpec.describe Gql::Subscriptions::Ticket::ChecklistUpdates, :aggregate_failures
|
|||
end
|
||||
|
||||
before do
|
||||
setup if defined?(setup)
|
||||
gql.execute(subscription, variables: variables, context: { channel: mock_channel })
|
||||
end
|
||||
|
||||
|
|
@ -44,6 +45,16 @@ RSpec.describe Gql::Subscriptions::Ticket::ChecklistUpdates, :aggregate_failures
|
|||
expect(gql.result.data).not_to be_nil
|
||||
end
|
||||
|
||||
context 'with disabled checklist feature' do
|
||||
let(:setup) do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it 'denies subscription with an error' do
|
||||
expect(gql.result.error_type).to eq(Exceptions::Forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
it 'triggers after checklist create' do
|
||||
checklist = create(:checklist, ticket: ticket, item_count: nil)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,15 @@ RSpec.describe Ticket::TriggersSubscriptions do
|
|||
.with(referenced_checklist, arguments: { ticket_id: referenced_checklist.ticket.to_global_id.to_s })
|
||||
end
|
||||
|
||||
it 'triggers checklist update ticket is tracked in twice on state and group change' do
|
||||
ticket.update state: Ticket::State.find_by(name: 'closed'), group: create(:group)
|
||||
|
||||
expect(Gql::Subscriptions::Ticket::ChecklistUpdates)
|
||||
.to have_received(:trigger)
|
||||
.with(referenced_checklist, arguments: { ticket_id: referenced_checklist.ticket.to_global_id.to_s })
|
||||
.twice
|
||||
end
|
||||
|
||||
it 'triggers checklist update once per checklist' do
|
||||
ticket.update title: 'new title'
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ describe Checklist::ItemPolicy do
|
|||
let(:user) { create(:agent, groups: [ticket.group]) }
|
||||
|
||||
it { is_expected.to permit_actions(:show, :create, :update, :destroy) }
|
||||
|
||||
context 'when checklist feature is disabled' do
|
||||
before do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it { is_expected.to forbid_actions(:show, :create, :update, :destroy) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has access to the ticket by having customer access' do
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ describe ChecklistPolicy do
|
|||
let(:user) { create(:agent, groups: [ticket.group]) }
|
||||
|
||||
it { is_expected.to permit_actions(:show, :create, :update, :destroy) }
|
||||
|
||||
context 'when checklist feature is disabled' do
|
||||
before do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it { is_expected.to forbid_actions(:show, :create, :update, :destroy) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has access to the ticket by having customer access' do
|
||||
|
|
|
|||
|
|
@ -18,6 +18,14 @@ describe ChecklistTemplatePolicy do
|
|||
let(:user) { create(:admin) }
|
||||
|
||||
it { is_expected.to permit_actions(:show, :create, :update, :destroy) }
|
||||
|
||||
context 'when checklist feature is disabled' do
|
||||
before do
|
||||
Setting.set('checklist', false)
|
||||
end
|
||||
|
||||
it { is_expected.to forbid_actions(:show, :create, :update, :destroy) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a customer' do
|
||||
|
|
|
|||
|
|
@ -17,22 +17,20 @@ RSpec.describe 'Checklist Item', authenticated_as: :agent_1, current_user_id: 1,
|
|||
checklist_2
|
||||
end
|
||||
|
||||
it 'does list checklist items', :aggregate_failures do
|
||||
get '/api/v1/checklist_items', params: {}, as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response).to include(hash_including('id' => checklist_1.items.first.id))
|
||||
expect(json_response).not_to include(hash_including('id' => checklist_2.items.first.id))
|
||||
end
|
||||
|
||||
it 'does show checklist items', :aggregate_failures do
|
||||
get "/api/v1/checklist_items/#{checklist_1.items.first.id}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response).to include('id' => checklist_1.items.first.id)
|
||||
end
|
||||
|
||||
it 'does not show checklist items' do
|
||||
it 'does not show inaccessible checklist items' do
|
||||
get "/api/v1/checklist_items/#{checklist_2.items.first.id}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:not_found)
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
|
||||
it 'does not show nonexistant checklist items' do
|
||||
get '/api/v1/checklist_items/1234', params: {}, as: :json
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
|
||||
it 'does create checklist items', :aggregate_failures do
|
||||
|
|
|
|||
|
|
@ -19,22 +19,20 @@ RSpec.describe 'Checklist', authenticated_as: :agent_1, current_user_id: 1, type
|
|||
checklist_2
|
||||
end
|
||||
|
||||
it 'does list checklists', :aggregate_failures do
|
||||
get '/api/v1/checklists', params: {}, as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response).to include(hash_including('id' => checklist_1.id))
|
||||
expect(json_response).not_to include(hash_including('id' => checklist_2.id))
|
||||
end
|
||||
|
||||
it 'does show checklist', :aggregate_failures do
|
||||
get "/api/v1/checklists/#{checklist_1.id}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response).to include('id' => checklist_1.id)
|
||||
end
|
||||
|
||||
it 'does not show checklist' do
|
||||
it 'does not show inaccessible checklist' do
|
||||
get "/api/v1/checklists/#{checklist_2.id}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:not_found)
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
|
||||
it 'does not show nonexistant checklist' do
|
||||
get '/api/v1/checklists/1234', params: {}, as: :json
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
|
||||
it 'does create checklist', :aggregate_failures do
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Ticket Checklist Items', authenticated_as: :agent, current_user_id: 1, type: :request do
|
||||
let(:group) { create(:group) }
|
||||
let(:agent) { create(:agent, groups: [group], group_names_access_map: { group.name => %w[read change] }) }
|
||||
let(:ticket) { create(:ticket, group: group) }
|
||||
|
||||
describe '#create' do
|
||||
let(:checklist) { create(:checklist, ticket: ticket) }
|
||||
|
||||
let(:checklist_item_params) do
|
||||
{
|
||||
text: 'foobar',
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
checklist
|
||||
post "/api/v1/tickets/#{ticket.id}/checklist/items", params: checklist_item_params
|
||||
end
|
||||
|
||||
context 'when user is not authorized' do
|
||||
let(:agent) { create(:agent, groups: [group], group_names_access_map: { group.name => 'read' }) }
|
||||
|
||||
it 'returns forbidden status' do
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
it 'returns ok status' do
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update' do
|
||||
let(:checklist) { create(:checklist, ticket: ticket) }
|
||||
|
||||
let(:checklist_item_params) do
|
||||
{
|
||||
checked: true,
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
put "/api/v1/tickets/#{ticket.id}/checklist/items/#{checklist.items.last.id}", params: checklist_item_params
|
||||
end
|
||||
|
||||
context 'when user is not authorized' do
|
||||
let(:agent) { create(:agent, groups: [group], group_names_access_map: { group.name => 'read' }) }
|
||||
|
||||
it 'returns forbidden status' do
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
it 'returns ok status' do
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
let(:checklist) { create(:checklist, ticket: ticket) }
|
||||
|
||||
before do
|
||||
delete "/api/v1/tickets/#{ticket.id}/checklist/items/#{checklist.items.last.id}"
|
||||
end
|
||||
|
||||
context 'when user is not authorized' do
|
||||
let(:agent) { create(:agent, groups: [group], group_names_access_map: { group.name => 'read' }) }
|
||||
|
||||
it 'returns forbidden status' do
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
it 'returns ok status' do
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Ticket Checklist', authenticated_as: :agent, current_user_id: 1, type: :request do
|
||||
let(:group) { create(:group) }
|
||||
let(:agent) { create(:agent, groups: [group], group_names_access_map: { group.name => %w[read change] }) }
|
||||
let(:ticket) { create(:ticket, group: group) }
|
||||
|
||||
describe '#show' do
|
||||
let(:checklist) { create(:checklist, ticket: ticket) }
|
||||
|
||||
before do
|
||||
checklist
|
||||
end
|
||||
|
||||
context 'when user is not authorized' do
|
||||
let(:agent) { create(:agent) }
|
||||
|
||||
it 'returns forbidden status' do
|
||||
get "/api/v1/tickets/#{ticket.id}/checklist", as: :json
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
it 'returns ok status' do
|
||||
get "/api/v1/tickets/#{ticket.id}/checklist"
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create', current_user_id: 1 do
|
||||
context 'when user is not authorized' do
|
||||
let(:agent) { create(:agent, groups: [group], group_names_access_map: { group.name => 'read' }) }
|
||||
|
||||
it 'returns forbidden status' do
|
||||
post "/api/v1/tickets/#{ticket.id}/checklist"
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
it 'returns ok status' do
|
||||
post "/api/v1/tickets/#{ticket.id}/checklist"
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
context 'when ticket already has a checklist' do
|
||||
before do
|
||||
create(:checklist, ticket:)
|
||||
end
|
||||
|
||||
it 'returns unprocessable entity', aggregate_failures: true do
|
||||
post "/api/v1/tickets/#{ticket.id}/checklist", as: :json
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update' do
|
||||
let(:checklist) { create(:checklist, ticket: ticket) }
|
||||
|
||||
let(:checklist_params) do
|
||||
{
|
||||
name: 'foobar',
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
checklist
|
||||
put "/api/v1/tickets/#{ticket.id}/checklist", params: checklist_params
|
||||
end
|
||||
|
||||
context 'when user is not authorized' do
|
||||
let(:agent) { create(:agent, groups: [group], group_names_access_map: { group.name => 'read' }) }
|
||||
|
||||
it 'returns forbidden status' do
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
it 'returns ok status' do
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
let(:checklist) { create(:checklist, ticket: ticket) }
|
||||
|
||||
before do
|
||||
checklist
|
||||
delete "/api/v1/tickets/#{ticket.id}/checklist"
|
||||
end
|
||||
|
||||
context 'when user is not authorized' do
|
||||
let(:agent) { create(:agent, groups: [group], group_names_access_map: { group.name => 'read' }) }
|
||||
|
||||
it 'returns forbidden status' do
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
it 'returns ok status' do
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -143,9 +143,11 @@ RSpec.describe 'Ticket zoom > Checklist', authenticated_as: :authenticate, curre
|
|||
item_text = SecureRandom.uuid
|
||||
find(".checklistShow tr[data-id='#{item.id}'] .js-input").fill_in with: item_text, fill_options: { clear: :backspace }
|
||||
|
||||
# simulate other users change
|
||||
other_item_text = SecureRandom.uuid
|
||||
checklist.items.create!(text: other_item_text, created_by: other_agent, updated_by: other_agent)
|
||||
# simulate other users change
|
||||
UserInfo.with_user_id(other_agent.id) do
|
||||
checklist.items.create!(text: other_item_text)
|
||||
end
|
||||
|
||||
# not really another way to be absolutely sure that this works
|
||||
sleep 5
|
||||
|
|
|
|||
Loading…
Reference in a new issue