diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.coffee b/app/assets/javascripts/app/controllers/_application_controller_table.coffee index d32477773..6240cca77 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_table.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_table.coffee @@ -113,6 +113,7 @@ class App.ControllerTable extends App.Controller radio: false renderState: undefined groupBy: undefined + groupDirection: undefined shownPerPage: 150 shownPage: 0 @@ -771,6 +772,9 @@ class App.ControllerTable extends App.Controller for key of groupObjects groupsSorted.push key groupsSorted = groupsSorted.sort() + # Reverse the sorted groups depending on the groupDirection + if @groupDirection == 'DESC' + groupsSorted.reverse() # get new order localObjects = [] diff --git a/app/assets/javascripts/app/controllers/ticket_overview.coffee b/app/assets/javascripts/app/controllers/ticket_overview.coffee index 369fea4d5..7a70edd5d 100644 --- a/app/assets/javascripts/app/controllers/ticket_overview.coffee +++ b/app/assets/javascripts/app/controllers/ticket_overview.coffee @@ -969,6 +969,7 @@ class Table extends App.Controller overviewAttributes: @overview.view.s objects: ticketListShow groupBy: @overview.group_by + groupDirection: @overview.group_direction orderBy: @overview.order.by orderDirection: @overview.order.direction ) @@ -1134,6 +1135,7 @@ class Table extends App.Controller objects: ticketListShow checkbox: checkbox groupBy: @overview.group_by + groupDirection: @overview.group_direction orderBy: @overview.order.by orderDirection: @overview.order.direction class: 'table--light' @@ -1529,7 +1531,7 @@ class App.OverviewSettings extends App.ControllerModal }, { name: 'order::direction' - display: 'Direction' + display: 'Order by Direction' tag: 'select' default: @overview.order.direction null: false @@ -1547,7 +1549,18 @@ class App.OverviewSettings extends App.ControllerModal nulloption: true translate: true options: App.Overview.groupByAttributes() - }) + }, + { + name: 'group_direction' + display: 'Group by Direction' + tag: 'select' + default: @overview.group_direction + null: false + translate: true + options: + ASC: 'up' + DESC: 'down' + },) controller = new App.ControllerForm( model: { configure_attributes: @configure_attributes_article } @@ -1572,6 +1585,10 @@ class App.OverviewSettings extends App.ControllerModal @overview.order.direction = params.order.direction @reload_needed = true + if @overview.group_direction isnt params.group_direction + @overview.group_direction = params.group_direction + @reload_needed = true + for key, value of params.view @overview.view[key] = value diff --git a/app/assets/javascripts/app/models/overview.coffee b/app/assets/javascripts/app/models/overview.coffee index f5126170a..4834b94bf 100644 --- a/app/assets/javascripts/app/models/overview.coffee +++ b/app/assets/javascripts/app/models/overview.coffee @@ -1,5 +1,5 @@ class App.Overview extends App.Model - @configure 'Overview', 'name', 'prio', 'condition', 'order', 'group_by', 'view', 'user_ids', 'organization_shared', 'role_ids', 'active' + @configure 'Overview', 'name', 'prio', 'condition', 'order', 'group_by', 'group_direction', 'view', 'user_ids', 'organization_shared', 'role_ids', 'active' @extend Spine.Model.Ajax @url: @apiPath + '/overviews' @configure_attributes = [ @@ -29,7 +29,7 @@ class App.Overview extends App.Model }, { name: 'order::direction' - display: 'Direction' + display: 'Order by Direction' tag: 'select' default: 'down' null: false @@ -53,6 +53,17 @@ class App.Overview extends App.Model group: 'Group' owner: 'Owner' }, + { + name: 'group_direction' + display: 'Group by Direction' + tag: 'select' + default: 'down' + null: false + translate: true + options: + ASC: 'up' + DESC: 'down' + }, { name: 'active', display: 'Active', tag: 'active', default: true }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, diff --git a/db/migrate/20120101000010_create_ticket.rb b/db/migrate/20120101000010_create_ticket.rb index 9c16bf3d7..07ee6371d 100644 --- a/db/migrate/20120101000010_create_ticket.rb +++ b/db/migrate/20120101000010_create_ticket.rb @@ -237,6 +237,7 @@ class CreateTicket < ActiveRecord::Migration[4.2] t.column :condition, :text, limit: 500.kilobytes + 1, null: false t.column :order, :string, limit: 2500, null: false t.column :group_by, :string, limit: 250, null: true + t.column :group_direction, :string, limit: 250, null: true t.column :organization_shared, :boolean, null: false, default: false t.column :out_of_office, :boolean, null: false, default: false t.column :view, :string, limit: 1000, null: false diff --git a/db/migrate/20180709020509_add_group_direction_to_overviews.rb b/db/migrate/20180709020509_add_group_direction_to_overviews.rb new file mode 100644 index 000000000..fba072601 --- /dev/null +++ b/db/migrate/20180709020509_add_group_direction_to_overviews.rb @@ -0,0 +1,8 @@ +class AddGroupDirectionToOverviews < ActiveRecord::Migration[5.1] + def change + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + add_column :overviews, :group_direction, :string, limit: 250, null: true + end +end diff --git a/public/assets/tests/table.js b/public/assets/tests/table.js index 8754aaa8d..4e3164e17 100644 --- a/public/assets/tests/table.js +++ b/public/assets/tests/table.js @@ -355,6 +355,39 @@ test('table test', function() { el.find('tbody > tr:nth-child(5) > td:nth-child(1) label').click() equal(el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').prop('checked'), true, 'check row 5') equal(el.find('tbody > tr:nth-child(5) > td:nth-child(1) input').val(), '1', 'check row 5') + + + $('#table').append('

table Group By Direction DESC

') + el = $('#table6') + var clickCheckbox = function (id, checked, e) { + console.log('clickCheckbox', id, checked, e.target) + }; + new App.ControllerTable({ + el: el, + overview: ['number', 'title', 'owner', 'customer', 'priority', 'group', 'state', 'created_at'], + model: App.Ticket, + objects: App.Ticket.search({sortBy:'created_at', order: 'DESC'}), + groupBy: 'priority', + groupDirection: 'DESC', + }) + equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), '2 normal', 'check row 1') + equal(el.find('tbody > tr:nth-child(3) > td:nth-child(1)').text().trim(), '1 niedrig', 'check row 3') + + $('#table').append('

table Group By Direction ASC

') + el = $('#table7') + var clickCheckbox = function (id, checked, e) { + console.log('clickCheckbox', id, checked, e.target) + }; + new App.ControllerTable({ + el: el, + overview: ['number', 'title', 'owner', 'customer', 'priority', 'group', 'state', 'created_at'], + model: App.Ticket, + objects: App.Ticket.search({sortBy:'created_at', order: 'DESC'}), + groupBy: 'priority', + groupDirection: 'ASC', + }) + equal(el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), '1 niedrig', 'check row 1') + equal(el.find('tbody > tr:nth-child(4) > td:nth-child(1)').text().trim(), '2 normal', 'check row 4') }); test('table test 2.1', function() { diff --git a/test/browser/admin_overview_test.rb b/test/browser/admin_overview_test.rb index 87b6791f5..e9cfe0b9c 100644 --- a/test/browser/admin_overview_test.rb +++ b/test/browser/admin_overview_test.rb @@ -38,4 +38,106 @@ class AdminOverviewTest < TestCase ) end + def test_overview_group_by_direction + name = "overview_#{rand(99_999_999)}" + ticket_titles = (1..3).map { |i| "Priority #{i} ticket" } + + @browser = instance = browser_instance + login( + username: 'master@example.com', + password: 'test', + url: browser_url, + ) + tasks_close_all() + + ticket_create( + data: { + customer: 'nico', + group: 'Users', + title: 'Priority 1 ticket', + body: 'some body 123äöü', + priority: '1 low', + }, + ) + + ticket_create( + data: { + customer: 'nico', + group: 'Users', + title: 'Priority 2 ticket', + body: 'some body 123äöü', + priority: '2 normal', + }, + ) + + ticket_create( + data: { + customer: 'nico', + group: 'Users', + title: 'Priority 3 ticket', + body: 'some body 123äöü', + priority: '3 high', + }, + ) + + # Add new overview to sort groups from high to low + overview_create( + data: { + name: name, + roles: ['Agent'], + selector: { + 'State' => 'open', + }, + 'order::direction' => 'down', + group_by: 'Priority', + group_direction: 'down', + } + ) + + click( + browser: instance, + css: 'a[href="#ticket/view"]', + mute_log: true, + ) + click( + browser: instance, + css: "div.overview-header a[href='#ticket/view/#{name}']", + mute_log: true, + ) + + # Sort the tickets according to their onscreen Y location + tickets_low_to_high = ticket_titles.map do |title| + [title, + get_location( css: "td[title='#{title}']").y] + end + tickets_low_to_high = tickets_low_to_high.sort_by { |x| -x[1] }.map { |x| x[0] } + assert_equal(ticket_titles, tickets_low_to_high) + + # Update overview to sort groups from low to high + overview_update( + data: { + name: name, + group_direction: 'up', + } + ) + + click( + browser: instance, + css: 'a[href="#ticket/view"]', + mute_log: true, + ) + click( + browser: instance, + css: "div.overview-header a[href='#ticket/view/#{name}']", + mute_log: true, + ) + + # Sort the tickets according to their onscreen Y location + tickets_high_to_low = ticket_titles.map do |title| + [title, + get_location( css: "td[title='#{title}']").y] + end + tickets_high_to_low = tickets_high_to_low.sort_by { |x| x[1] }.map { |x| x[0] } + assert_equal(ticket_titles, tickets_high_to_low) + end end diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb index 75b22fafe..12705ddc6 100644 --- a/test/browser_test_helper.rb +++ b/test/browser_test_helper.rb @@ -1018,6 +1018,40 @@ class TestCase < Test::Unit::TestCase =begin +Get the on-screen pixel coordinates of a given DOM element. Can be used to compare +the relative location of table rows before and after sort, for example. + +Returns a Selenium::WebDriver::Point object. Use result.x and result.y to access +its X and Y coordinates respectively. + + get_location( + browser: browser1, + css: '.some_class', + ) + +=end + + def get_location(params) + switch_window_focus(params) + log('exists', params) + + instance = params[:browser] || @browser + if params[:css] + query = { css: params[:css] } + end + if params[:xpath] + query = { xpath: params[:xpath] } + end + if !instance.find_elements(query)[0] + screenshot(browser: instance, comment: 'exists_failed') + raise "#{query} dosn't exist, but should" + end + + instance.find_elements(query)[0].location + end + +=begin + set type of task (closeTab, closeNextInOverview, stayOnTab) task_type( @@ -1735,6 +1769,15 @@ wait untill text in selector disabppears ) end + if data[:group_direction] + select( + browser: instance, + css: '.modal select[name="group_direction"]', + value: data[:group_direction], + mute_log: true, + ) + end + instance.find_elements(css: '.modal button.js-submit')[0].click modal_disappear(browser: instance) 11.times do @@ -1839,6 +1882,15 @@ wait untill text in selector disabppears ) end + if data[:group_direction] + select( + browser: instance, + css: '.modal select[name="group_direction"]', + value: data[:group_direction], + mute_log: true, + ) + end + instance.find_elements(css: '.modal button.js-submit')[0].click modal_disappear(browser: instance) 11.times do diff --git a/test/controllers/overviews_controller_test.rb b/test/controllers/overviews_controller_test.rb index d2bcb36c0..639a04ec8 100644 --- a/test/controllers/overviews_controller_test.rb +++ b/test/controllers/overviews_controller_test.rb @@ -181,4 +181,41 @@ class OverviewsControllerTest < ActionDispatch::IntegrationTest assert_equal(1, overview2.prio) end + test 'create an overview with group_by direction' do + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin', 'adminpw') + + params = { + name: 'Overview2', + link: 'my_overview', + roles: Role.where(name: 'Agent').pluck(:name), + condition: { + 'ticket.state_id' => { + operator: 'is', + value: [1, 2, 3], + }, + }, + order: { + by: 'created_at', + direction: 'DESC', + }, + group_by: 'priority', + group_direction: 'ASC', + view: { + d: %w[title customer state created_at], + s: %w[number title customer state created_at], + m: %w[number title customer state created_at], + view_mode_default: 's', + }, + } + + post '/api/v1/overviews', params: params.to_json, headers: @headers.merge('Authorization' => credentials) + assert_response(201) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + assert_equal('Overview2', result['name']) + assert_equal('my_overview', result['link']) + assert_equal('priority', result['group_by']) + assert_equal('ASC', result['group_direction']) + end + end