Fixes #257 - Define default "stay on tab" / "close tab" behavior.
This commit is contained in:
parent
b3bfe71ab8
commit
41691e9846
8 changed files with 224 additions and 5 deletions
|
@ -978,8 +978,14 @@ class App.TicketZoom extends App.Controller
|
||||||
@openTicketInOverview(nextTicket)
|
@openTicketInOverview(nextTicket)
|
||||||
App.Event.trigger('overview:fetch')
|
App.Event.trigger('overview:fetch')
|
||||||
return
|
return
|
||||||
|
else if taskAction is 'closeTabOnTicketClose' || taskAction is 'next_task_on_close'
|
||||||
if taskAction is 'closeTab' || taskAction is 'next_task'
|
state_type_id = App.TicketState.find(ticket.state_id).state_type_id
|
||||||
|
state_type = App.TicketStateType.find(state_type_id).name
|
||||||
|
if state_type is 'closed'
|
||||||
|
App.Event.trigger('overview:fetch')
|
||||||
|
@taskCloseTicket(true)
|
||||||
|
return
|
||||||
|
else if taskAction is 'closeTab' || taskAction is 'next_task'
|
||||||
App.Event.trigger('overview:fetch')
|
App.Event.trigger('overview:fetch')
|
||||||
@taskCloseTicket(true)
|
@taskCloseTicket(true)
|
||||||
return
|
return
|
||||||
|
|
|
@ -15,7 +15,7 @@ class App.TicketZoomAttributeBar extends App.Controller
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
|
||||||
@secondaryAction = 'stayOnTab'
|
@secondaryAction = @getAction()
|
||||||
|
|
||||||
@subscribeId = App.Macro.subscribe(@checkMacroChanges)
|
@subscribeId = App.Macro.subscribe(@checkMacroChanges)
|
||||||
@render()
|
@render()
|
||||||
|
@ -31,6 +31,9 @@ class App.TicketZoomAttributeBar extends App.Controller
|
||||||
@render()
|
@render()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
getAction: ->
|
||||||
|
return App.Session.get().preferences.secondaryAction || App.Config.get('ticket_secondary_action') || 'stayOnTab'
|
||||||
|
|
||||||
release: =>
|
release: =>
|
||||||
App.Macro.unsubscribe(@subscribeId)
|
App.Macro.unsubscribe(@subscribeId)
|
||||||
|
|
||||||
|
@ -74,6 +77,7 @@ class App.TicketZoomAttributeBar extends App.Controller
|
||||||
start: =>
|
start: =>
|
||||||
return if !@taskbarWatcher
|
return if !@taskbarWatcher
|
||||||
@taskbarWatcher.start()
|
@taskbarWatcher.start()
|
||||||
|
@setSecondaryAction(@getAction(), @el)
|
||||||
|
|
||||||
stop: =>
|
stop: =>
|
||||||
return if !@taskbarWatcher
|
return if !@taskbarWatcher
|
||||||
|
@ -114,11 +118,25 @@ class App.TicketZoomAttributeBar extends App.Controller
|
||||||
chooseSecondaryAction: (e) =>
|
chooseSecondaryAction: (e) =>
|
||||||
type = $(e.currentTarget).find('.js-secondaryActionLabel').data('type')
|
type = $(e.currentTarget).find('.js-secondaryActionLabel').data('type')
|
||||||
@setSecondaryAction(type, @el)
|
@setSecondaryAction(type, @el)
|
||||||
|
@setUserPreferencesSecondaryAction(type)
|
||||||
|
|
||||||
setSecondaryAction: (type, localEl) ->
|
setSecondaryAction: (type, localEl) ->
|
||||||
element = localEl.find(".js-secondaryActionLabel[data-type=#{type}]")
|
element = localEl.find(".js-secondaryActionLabel[data-type=#{type}]")
|
||||||
|
return @setSecondaryAction('stayOnTab', localEl) if element.length == 0
|
||||||
text = element.text()
|
text = element.text()
|
||||||
localEl.find('.js-secondaryAction .js-selectedIcon.is-selected').removeClass('is-selected')
|
localEl.find('.js-secondaryAction .js-selectedIcon.is-selected').removeClass('is-selected')
|
||||||
element.closest('.js-secondaryAction').find('.js-selectedIcon').addClass('is-selected')
|
element.closest('.js-secondaryAction').find('.js-selectedIcon').addClass('is-selected')
|
||||||
localEl.find('.js-secondaryActionButtonLabel').text(text)
|
localEl.find('.js-secondaryActionButtonLabel').text(text)
|
||||||
localEl.find('.js-secondaryActionButtonLabel').data('type', type)
|
localEl.find('.js-secondaryActionButtonLabel').data('type', type)
|
||||||
|
|
||||||
|
setUserPreferencesSecondaryAction: (type) ->
|
||||||
|
session = App.Session.get()
|
||||||
|
return if session.preferences.secondaryAction is type
|
||||||
|
|
||||||
|
@ajax(
|
||||||
|
id: 'setUserPreferencesSecondaryAction'
|
||||||
|
type: 'PUT'
|
||||||
|
url: "#{App.Config.get('api_path')}/users/preferences"
|
||||||
|
data: JSON.stringify(secondaryAction: type)
|
||||||
|
processData: true
|
||||||
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ class App.Macro extends App.Model
|
||||||
{ name: 'perform', display: 'Actions', tag: 'ticket_perform_action', null: true
|
{ name: 'perform', display: 'Actions', tag: 'ticket_perform_action', null: true
|
||||||
},
|
},
|
||||||
{ name: 'ux_flow_next_up', display: 'Once completed...', tag: 'select', default: 'none', options: {
|
{ name: 'ux_flow_next_up', display: 'Once completed...', tag: 'select', default: 'none', options: {
|
||||||
none: 'Stay on tab', next_task: 'Close tab', next_from_overview: 'Advance to next ticket from overview'
|
none: 'Stay on tab', next_task: 'Close tab', next_task_on_close: 'Close tab on ticket close', next_from_overview: 'Advance to next ticket from overview'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||||
|
|
|
@ -10,6 +10,11 @@
|
||||||
<span class="dropdown-selectedSpacer js-selectedIcon">
|
<span class="dropdown-selectedSpacer js-selectedIcon">
|
||||||
<%- @Icon('checkmark') %>
|
<%- @Icon('checkmark') %>
|
||||||
</span>
|
</span>
|
||||||
|
<li class="js-secondaryAction" role="menuitem">
|
||||||
|
<span class="js-secondaryActionLabel" data-type="closeTabOnTicketClose"><%- @T('Close tab on ticket close') %></span>
|
||||||
|
<span class="dropdown-selectedSpacer js-selectedIcon">
|
||||||
|
<%- @Icon('checkmark') %>
|
||||||
|
</span>
|
||||||
<% if @overview_id: %>
|
<% if @overview_id: %>
|
||||||
<li class="js-secondaryAction" role="menuitem">
|
<li class="js-secondaryAction" role="menuitem">
|
||||||
<span class="js-secondaryActionLabel" data-type="closeNextInOverview"><%- @T('Next in overview') %></span>
|
<span class="js-secondaryActionLabel" data-type="closeNextInOverview"><%- @T('Next in overview') %></span>
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Macro < ApplicationModel
|
||||||
|
|
||||||
store :perform
|
store :perform
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :ux_flow_next_up, inclusion: { in: %w[none next_task next_from_overview] }
|
validates :ux_flow_next_up, inclusion: { in: %w[none next_task next_task_on_close next_from_overview] }
|
||||||
|
|
||||||
has_and_belongs_to_many :groups, after_add: :cache_update, after_remove: :cache_update, class_name: 'Group'
|
has_and_belongs_to_many :groups, after_add: :cache_update, after_remove: :cache_update, class_name: 'Group'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
class Issue257TicketSecondaryAction < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
# return if it's a new setup
|
||||||
|
return if !Setting.exists?(name: 'system_init_done')
|
||||||
|
|
||||||
|
Setting.create_if_not_exists(
|
||||||
|
title: 'Tab behaviour after ticket action',
|
||||||
|
name: 'ticket_secondary_action',
|
||||||
|
area: 'CustomerWeb::Base',
|
||||||
|
description: 'Defines the tab behaviour after a ticket action.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'ticket_secondary_action',
|
||||||
|
tag: 'boolean',
|
||||||
|
options: {
|
||||||
|
'closeTab' => 'Close tab',
|
||||||
|
'closeTabOnTicketClose' => 'Close tab on ticket close',
|
||||||
|
'closeNextInOverview' => 'Next in overview',
|
||||||
|
'stayOnTab' => 'Stay on tab',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: 'stayOnTab',
|
||||||
|
preferences: {
|
||||||
|
authentication: true,
|
||||||
|
permission: ['admin.channel_web'],
|
||||||
|
},
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -2363,6 +2363,35 @@ Setting.create_if_not_exists(
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Setting.create_if_not_exists(
|
||||||
|
title: 'Tab behaviour after ticket action',
|
||||||
|
name: 'ticket_secondary_action',
|
||||||
|
area: 'CustomerWeb::Base',
|
||||||
|
description: 'Defines the tab behaviour after a ticket action.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'ticket_secondary_action',
|
||||||
|
tag: 'boolean',
|
||||||
|
options: {
|
||||||
|
'closeTab' => 'Close tab',
|
||||||
|
'closeTabOnTicketClose' => 'Close tab on ticket close',
|
||||||
|
'closeNextInOverview' => 'Next in overview',
|
||||||
|
'stayOnTab' => 'Stay on tab',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: 'stayOnTab',
|
||||||
|
preferences: {
|
||||||
|
authentication: true,
|
||||||
|
permission: ['admin.channel_web'],
|
||||||
|
},
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
title: 'Enable Ticket creation',
|
title: 'Enable Ticket creation',
|
||||||
name: 'form_ticket_create',
|
name: 'form_ticket_create',
|
||||||
|
|
|
@ -1782,4 +1782,128 @@ RSpec.describe 'Ticket zoom', type: :system do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'Tab behaviour - Define default "stay on tab" / "close tab" behavior #257', authenticated_as: :authenticate do
|
||||||
|
def authenticate
|
||||||
|
Setting.set('ticket_secondary_action', 'closeTabOnTicketClose')
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
visit "ticket/zoom/#{ticket.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does show the default of the system' do
|
||||||
|
expect(page).to have_text('Close tab on ticket close')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does save state for the user preferences' do
|
||||||
|
click '.js-attributeBar .dropup div'
|
||||||
|
click 'span[data-type=stayOnTab]'
|
||||||
|
refresh
|
||||||
|
expect(page).to have_text('Stay on tab')
|
||||||
|
expect(User.find_by(email: 'admin@example.com').preferences[:secondaryAction]).to eq('stayOnTab')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Tab behaviour - Close tab on ticket close' do
|
||||||
|
it 'does not close the tab without any action' do
|
||||||
|
click '.js-submit'
|
||||||
|
expect(current_url).to include('ticket/zoom')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does close the tab on ticket close' do
|
||||||
|
select 'closed', from: 'State'
|
||||||
|
click '.js-submit'
|
||||||
|
expect(current_url).not_to include('ticket/zoom')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Tab behaviour - Stay on tab' do
|
||||||
|
def authenticate
|
||||||
|
Setting.set('ticket_secondary_action', 'stayOnTab')
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not close the tab without any action' do
|
||||||
|
click '.js-submit'
|
||||||
|
expect(current_url).to include('ticket/zoom')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not close the tab on ticket close' do
|
||||||
|
select 'closed', from: 'State'
|
||||||
|
click '.js-submit'
|
||||||
|
expect(current_url).to include('ticket/zoom')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Tab behaviour - Close tab' do
|
||||||
|
def authenticate
|
||||||
|
Setting.set('ticket_secondary_action', 'closeTab')
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does close the tab without any action' do
|
||||||
|
click '.js-submit'
|
||||||
|
expect(current_url).not_to include('ticket/zoom')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does close the tab on ticket close' do
|
||||||
|
select 'closed', from: 'State'
|
||||||
|
click '.js-submit'
|
||||||
|
expect(current_url).not_to include('ticket/zoom')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Tab behaviour - Next in overview' do
|
||||||
|
let(:ticket1) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
|
||||||
|
let(:ticket2) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
|
||||||
|
let(:ticket3) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
|
||||||
|
|
||||||
|
def authenticate
|
||||||
|
Setting.set('ticket_secondary_action', 'closeNextInOverview')
|
||||||
|
ticket1
|
||||||
|
ticket2
|
||||||
|
ticket3
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
visit 'ticket/view/all_open'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does change the tab without any action' do
|
||||||
|
click_on ticket1.title
|
||||||
|
expect(current_url).to include("ticket/zoom/#{ticket1.id}")
|
||||||
|
click '.js-submit'
|
||||||
|
expect(current_url).to include("ticket/zoom/#{ticket2.id}")
|
||||||
|
click '.js-submit'
|
||||||
|
expect(current_url).to include("ticket/zoom/#{ticket3.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does show default stay on tab if secondary action is not given' do
|
||||||
|
click_on ticket1.title
|
||||||
|
refresh
|
||||||
|
expect(page).to have_text('Stay on tab')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'On ticket switch' do
|
||||||
|
let(:ticket1) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
|
||||||
|
let(:ticket2) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
visit "ticket/zoom/#{ticket1.id}"
|
||||||
|
visit "ticket/zoom/#{ticket2.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does setup the last behaviour' do
|
||||||
|
click '.js-attributeBar .dropup div'
|
||||||
|
click 'span[data-type=stayOnTab]'
|
||||||
|
visit "ticket/zoom/#{ticket1.id}"
|
||||||
|
expect(page).to have_text('Stay on tab')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue