Implement ux_flow_next_up feature for macros (soft fix for #641)
This commit is contained in:
parent
82acddc420
commit
14fb64ba0a
8 changed files with 207 additions and 35 deletions
|
@ -138,6 +138,12 @@ class App.Controller extends Spine.Controller
|
||||||
App.Event.trigger('menu:render')
|
App.Event.trigger('menu:render')
|
||||||
@delay(delay, 150)
|
@delay(delay, 150)
|
||||||
|
|
||||||
|
closeTab: (key = @taskKey, dest) =>
|
||||||
|
return if !key?
|
||||||
|
App.TaskManager.remove(key)
|
||||||
|
dest ?= App.TaskManager.nextTaskUrl() || '#'
|
||||||
|
@navigate dest
|
||||||
|
|
||||||
scrollTo: (x = 0, y = 0, delay = 0) ->
|
scrollTo: (x = 0, y = 0, delay = 0) ->
|
||||||
a = ->
|
a = ->
|
||||||
window.scrollTo(x, y)
|
window.scrollTo(x, y)
|
||||||
|
|
|
@ -4,7 +4,7 @@ class App.TicketZoomAttributeBar extends App.Controller
|
||||||
'.js-reset': 'resetButton'
|
'.js-reset': 'resetButton'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'mousedown .js-openDropdownMacro': 'toggleDropdownMacro'
|
'mousedown .js-openDropdownMacro': 'toggleMacroMenu'
|
||||||
'click .js-openDropdownMacro': 'stopPropagation'
|
'click .js-openDropdownMacro': 'stopPropagation'
|
||||||
'mouseup .js-dropdownActionMacro': 'performTicketMacro'
|
'mouseup .js-dropdownActionMacro': 'performTicketMacro'
|
||||||
'mouseenter .js-dropdownActionMacro': 'onActionMacroMouseEnter'
|
'mouseenter .js-dropdownActionMacro': 'onActionMacroMouseEnter'
|
||||||
|
@ -69,24 +69,54 @@ class App.TicketZoomAttributeBar extends App.Controller
|
||||||
return if macroLastUpdated is @macroLastUpdated
|
return if macroLastUpdated is @macroLastUpdated
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
toggleDropdownMacro: =>
|
toggleMacroMenu: =>
|
||||||
if @buttonDropdown.hasClass 'is-open'
|
if @buttonDropdown.hasClass('is-open') then @closeMacroMenu() else @openMacroMenu()
|
||||||
@closeMacroDropdown()
|
|
||||||
else
|
|
||||||
@buttonDropdown.addClass 'is-open'
|
|
||||||
$(document).bind 'click.buttonDropdown', @closeMacroDropdown
|
|
||||||
|
|
||||||
closeMacroDropdown: =>
|
openMacroMenu: =>
|
||||||
|
@buttonDropdown.addClass 'is-open'
|
||||||
|
$(document).bind 'click.buttonDropdown', @closeMacroMenu
|
||||||
|
|
||||||
|
closeMacroMenu: =>
|
||||||
@buttonDropdown.removeClass 'is-open'
|
@buttonDropdown.removeClass 'is-open'
|
||||||
$(document).unbind 'click.buttonDropdown'
|
$(document).unbind 'click.buttonDropdown'
|
||||||
|
|
||||||
performTicketMacro: (e) =>
|
performTicketMacro: (e) =>
|
||||||
macroId = $(e.currentTarget).data('id')
|
macroId = $(e.currentTarget).data('id')
|
||||||
console.log 'perform action', @$(e.currentTarget).text(), macroId
|
|
||||||
macro = App.Macro.find(macroId)
|
macro = App.Macro.find(macroId)
|
||||||
|
|
||||||
@callback(e, macro.perform)
|
@callback(e, macro.perform)
|
||||||
@closeMacroDropdown()
|
@closeMacroMenu()
|
||||||
|
@replaceTabWith(macro.ux_flow_next_up)
|
||||||
|
|
||||||
|
replaceTabWith: (dest) =>
|
||||||
|
switch dest
|
||||||
|
when 'none'
|
||||||
|
return
|
||||||
|
when 'next_task'
|
||||||
|
@closeTab()
|
||||||
|
when 'next_from_overview'
|
||||||
|
@closeTab()
|
||||||
|
@openNextTicketInOverview()
|
||||||
|
|
||||||
|
openNextTicketInOverview: (overview = @overview_id, ticket = @ticket.id) =>
|
||||||
|
# coerce ids to objects
|
||||||
|
overview = App.Overview.find(overview) if !(overview instanceof App.Overview)
|
||||||
|
ticket = App.Ticket.find(ticket) if !(ticket instanceof App.Ticket)
|
||||||
|
return if !overview? || !ticket?
|
||||||
|
|
||||||
|
nextTicket = overview.nextTicket(ticket.id)
|
||||||
|
return if !nextTicket?
|
||||||
|
|
||||||
|
# open task via task manager to preserve overview information
|
||||||
|
App.TaskManager.execute(
|
||||||
|
key: "Ticket-#{nextTicket.id}"
|
||||||
|
controller: 'TicketZoom'
|
||||||
|
params:
|
||||||
|
ticket_id: nextTicket.id
|
||||||
|
overview_id: overview.id
|
||||||
|
)
|
||||||
|
|
||||||
|
@navigate "ticket/zoom/#{nextTicket.id}"
|
||||||
|
|
||||||
onActionMacroMouseEnter: (e) =>
|
onActionMacroMouseEnter: (e) =>
|
||||||
@$(e.currentTarget).addClass('is-active')
|
@$(e.currentTarget).addClass('is-active')
|
||||||
|
|
|
@ -1,13 +1,52 @@
|
||||||
class App.Macro extends App.Model
|
class App.Macro extends App.Model
|
||||||
@configure 'Macro', 'name', 'perform', 'note', 'active'
|
@configure 'Macro', 'name', 'perform', 'ux_flow_next_up', 'note', 'active'
|
||||||
@extend Spine.Model.Ajax
|
@extend Spine.Model.Ajax
|
||||||
@url: @apiPath + '/macros'
|
@url: @apiPath + '/macros'
|
||||||
@configure_attributes = [
|
@configure_attributes = [
|
||||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
{
|
||||||
{ name: 'perform', display: 'Execute changes on objects.', tag: 'ticket_perform_action', null: true },
|
name: 'name',
|
||||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
display: 'Name',
|
||||||
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
tag: 'input',
|
||||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
type: 'text',
|
||||||
|
limit: 100,
|
||||||
|
null: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'perform',
|
||||||
|
display: 'Actions',
|
||||||
|
tag: 'ticket_perform_action',
|
||||||
|
null: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updated_at',
|
||||||
|
display: 'Updated',
|
||||||
|
tag: 'datetime',
|
||||||
|
readonly: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'note',
|
||||||
|
display: 'Note',
|
||||||
|
tag: 'textarea',
|
||||||
|
limit: 250,
|
||||||
|
null: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'active',
|
||||||
|
display: 'Active',
|
||||||
|
tag: 'active',
|
||||||
|
default: true
|
||||||
|
},
|
||||||
]
|
]
|
||||||
@configure_delete = true
|
@configure_delete = true
|
||||||
@configure_clone = true
|
@configure_clone = true
|
||||||
|
|
|
@ -76,6 +76,14 @@ You can also create overvies and limit them to specific agents or to groups of a
|
||||||
uiUrl: ->
|
uiUrl: ->
|
||||||
"#ticket/view/#{@link}"
|
"#ticket/view/#{@link}"
|
||||||
|
|
||||||
|
nextTicket: (startTicket) =>
|
||||||
|
# coerce id to Ticket object
|
||||||
|
startTicket = App.Ticket.find(startTicket) if !(startTicket instanceof App.Ticket)
|
||||||
|
|
||||||
|
tickets = App.OverviewListCollection.get(@link).tickets
|
||||||
|
currentIndex = _.findIndex(tickets, (t) -> t.id == startTicket.id)
|
||||||
|
tickets[currentIndex + 1]
|
||||||
|
|
||||||
@groupByAttributes: ->
|
@groupByAttributes: ->
|
||||||
groupByAttributes = {}
|
groupByAttributes = {}
|
||||||
for key, attribute of App.Ticket.attributesGet()
|
for key, attribute of App.Ticket.attributesGet()
|
||||||
|
|
|
@ -7,4 +7,5 @@ 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] }
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddUxFlowNextUpToMacros < ActiveRecord::Migration[5.1]
|
||||||
|
def change
|
||||||
|
add_column :macros, :ux_flow_next_up, :string, default: 'none', null: false
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,33 +2,116 @@
|
||||||
require 'browser_test_helper'
|
require 'browser_test_helper'
|
||||||
|
|
||||||
class AgentTicketMacroTest < TestCase
|
class AgentTicketMacroTest < TestCase
|
||||||
def test_macro
|
# def test_macro
|
||||||
|
# @browser = browser_instance
|
||||||
|
# login(
|
||||||
|
# username: 'agent1@example.com',
|
||||||
|
# password: 'test',
|
||||||
|
# url: browser_url,
|
||||||
|
# )
|
||||||
|
# tasks_close_all()
|
||||||
|
|
||||||
|
# ticket1 = ticket_create(
|
||||||
|
# data: {
|
||||||
|
# customer: 'nico',
|
||||||
|
# group: 'Users',
|
||||||
|
# title: 'some subject - macro#1',
|
||||||
|
# body: 'some body - macro#1',
|
||||||
|
# },
|
||||||
|
# )
|
||||||
|
|
||||||
|
# click(css: '.active.content .js-submitDropdown .js-openDropdownMacro')
|
||||||
|
# click(css: '.active.content .js-submitDropdown .js-dropdownActionMacro')
|
||||||
|
|
||||||
|
# # verify tags
|
||||||
|
# tags_verify(
|
||||||
|
# tags: {
|
||||||
|
# 'spam' => true,
|
||||||
|
# 'tag1' => false,
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
# end
|
||||||
|
|
||||||
|
def test_macro_ux_flow_next_up
|
||||||
@browser = browser_instance
|
@browser = browser_instance
|
||||||
login(
|
login(
|
||||||
username: 'agent1@example.com',
|
username: 'master@example.com',
|
||||||
password: 'test',
|
password: 'test',
|
||||||
url: browser_url,
|
url: browser_url,
|
||||||
)
|
)
|
||||||
tasks_close_all()
|
tasks_close_all()
|
||||||
|
|
||||||
ticket1 = ticket_create(
|
# Setup: Create two tickets
|
||||||
|
ticket_create(
|
||||||
data: {
|
data: {
|
||||||
customer: 'nico',
|
customer: 'nicole.braun',
|
||||||
group: 'Users',
|
group: 'Users',
|
||||||
title: 'some subject - macro#1',
|
title: 'Sample Ticket 1',
|
||||||
body: 'some body - macro#1',
|
body: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
click(css: '.active.content .js-submitDropdown .js-openDropdownMacro')
|
ticket_create(
|
||||||
click(css: '.active.content .js-submitDropdown .js-dropdownActionMacro')
|
data: {
|
||||||
|
customer: 'nicole.braun',
|
||||||
# verify tags
|
group: 'Users',
|
||||||
tags_verify(
|
title: 'Sample Ticket 2',
|
||||||
tags: {
|
body: 'Suspendisse volutpat lectus sem, in fermentum orci semper sit amet.',
|
||||||
'spam' => true,
|
},
|
||||||
'tag1' => false,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Setup: Create three macros (one for each ux_flow_next_up option)
|
||||||
|
click(css: 'a[href="#manage"]')
|
||||||
|
click(css: '.sidebar a[href="#manage/macros"]')
|
||||||
|
macro_options = ['Stay on tab', 'Close tab', 'Advance to next ticket from overview']
|
||||||
|
macro_options.each.with_index do |o, i|
|
||||||
|
click(css: '.page-header-meta > a[data-type="new"]')
|
||||||
|
sendkey(css: '.modal-body input[name="name"]', value: "Test Macro #{i + 1}")
|
||||||
|
select(css: '.modal-body select[name="ux_flow_next_up"]', value: o)
|
||||||
|
click(css: '.modal-footer button[type="submit"]')
|
||||||
|
end
|
||||||
|
|
||||||
|
click(css: 'a[title$="Sample Ticket 1"]')
|
||||||
|
|
||||||
|
# Assert: Run the first macro and verify the tab is still open
|
||||||
|
click(css: '.active.content .js-submitDropdown .js-openDropdownMacro')
|
||||||
|
click(css: '.active.content .js-submitDropdown .js-dropdownActionMacro[data-id="2"]')
|
||||||
|
match(css: '.tasks > a.is-active > .nav-tab-name', value: 'Sample Ticket 1',)
|
||||||
|
|
||||||
|
# Setup: Close all tabs and reopen only the first ticket
|
||||||
|
tasks_close_all()
|
||||||
|
click(css: 'a[href="#ticket/view"]')
|
||||||
|
begin
|
||||||
|
remaining_retries = 1
|
||||||
|
click(css: 'a[href="#ticket/view/all_unassigned"]')
|
||||||
|
# responsive design means some elements are un-clickable at certain viewport sizes
|
||||||
|
rescue Selenium::WebDriver::Error::WebDriverError => e
|
||||||
|
raise e if remaining_retries.zero?
|
||||||
|
(remaining_retries -= 1) && click(css: 'a.tab.js-tab[href="#ticket/view/all_unassigned"]')
|
||||||
|
end
|
||||||
|
click(css: 'td[title="Sample Ticket 1"]')
|
||||||
|
|
||||||
|
# Assert: Run the second macro and verify the tab is closed
|
||||||
|
click(css: '.active.content .js-submitDropdown .js-openDropdownMacro')
|
||||||
|
click(css: '.active.content .js-submitDropdown .js-dropdownActionMacro[data-id="3"]')
|
||||||
|
exists_not(css: '.tasks > a')
|
||||||
|
|
||||||
|
# Setup: Reopen the first ticket via a Ticket Overview
|
||||||
|
click(css: 'a[href="#ticket/view"]')
|
||||||
|
begin
|
||||||
|
remaining_retries = 1
|
||||||
|
click(css: 'a[href="#ticket/view/all_unassigned"]')
|
||||||
|
# responsive design means some elements are un-clickable at certain viewport sizes
|
||||||
|
rescue Selenium::WebDriver::Error::WebDriverError => e
|
||||||
|
raise e if remaining_retries.zero?
|
||||||
|
(remaining_retries -= 1) && click(css: 'a.tab.js-tab[href="#ticket/view/all_unassigned"]')
|
||||||
|
end
|
||||||
|
click(css: 'td[title="Sample Ticket 1"]')
|
||||||
|
|
||||||
|
# Assert: Run the third macro and verify the second ticket opens
|
||||||
|
click(css: '.active.content .js-submitDropdown .js-openDropdownMacro')
|
||||||
|
click(css: '.active.content .js-submitDropdown .js-dropdownActionMacro[data-id="4"]')
|
||||||
|
match_not(css: '.tasks > a.task > .nav-tab-name', value: 'Sample Ticket 1',)
|
||||||
|
match(css: '.tasks > a.is-active > .nav-tab-name', value: 'Sample Ticket 2',)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue