diff --git a/.gitignore b/.gitignore index 080b946b0ae0..0e99e2666e32 100644 --- a/.gitignore +++ b/.gitignore @@ -4,83 +4,83 @@ # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile ~/.gitignore_global -# Ignore .swp files -.*.swp +# Configuration ================================================================ +# These files will differ from one user to another; +# committing them may cause Zammad to behave unexpectedly on other machines -# Ignore bundler config -/.bundle - -# Ignore mac stuff -.DS_Store - -# Ignore Rubymine config -/.idea - -# Ignore .project files -/.project - -# Ignore .rbenv-vars -/.rbenv-vars - -# exclude the figaro ENV setting helper file - run 'bundle exec figaro install' for creating your own -# see: https://github.com/laserlemon/figaro -# Ignore application configuration -/config/application.yml - -# Ignore .envrc for direnv -/.envrc +# Zammad ----------------------------------------------------------------------- -# Ignore database config +# database (copy from config/database/database.yml, or use `rails bs:init`) /config/database.yml -# Ignore translation cache files -/config/locales*.yml -/config/translations/*.yml +# Third-Party ------------------------------------------------------------------ +# The config files / dev tools listed below are optional +# and may not be present on most users' machines -# Ignore generated documentation -/doc +# Bundler +/.bundle +/Gemfile.local -# Ignore coverage stuff -/coverage +# Clutter ====================================================================== +# These files are automatically generated; +# in most cases, committing them won't do anything other than bloat the repo -# Ignore the default SQLite database. -/db/*.sqlite3 +# Zammad ----------------------------------------------------------------------- -# Ignore local changes to schema.rb (e. g. through extentions) +# database files +/db/*.sqlite3 /db/schema.rb -# Ignore custom gem file -/Gemfile.local +# translation cache files +/config/locales*.yml +/config/translations/*.yml -# Ignore node modules +# NPM / Yarn /node_modules -# Ignore all logfiles and tempfiles. +# logfiles and tempfiles /log /public/assets/*.* /public/assets/app /public/assets/custom /public/assets/chat/node_modules /tmp/* -/tmp/pids/* -/storage/fs - -# except /tmp/pids/ which is needed for certain Zammad processes -!/tmp !/tmp/pids +/tmp/pids/* !/tmp/pids/.keep +/storage/fs -# ignore doorkeeper auto generated folder +# doorkeeper (OAuth 2) /public/assets/doorkeeper -# ignore all automatically downloaded images except the default ones +# images /public/assets/images/* -!/public/assets/images/icons +!/public/assets/images/icons/ !/public/assets/images/avatar-bg.png !/public/assets/images/chat-demo-avatar.png !/public/assets/images/eyedropper.gif !/public/assets/images/icons.svg !/public/assets/images/logo.svg -# ignore byebug history -/.byebug_history \ No newline at end of file +# Third-Party ------------------------------------------------------------------ + +# macOS +.DS_Store + +# vim +.*.sw[pon] + +# RubyMine +/.idea + +# Eclipse +/.project + +# Byebug +/.byebug_history + +# SimpleCov +/coverage + +# RDoc / YARD +/doc diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1bdd0f0d77a6..c38300881fc7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -78,7 +78,6 @@ test:unit:mysql: - rake db:migrate - rake db:seed - rake test:units - - rake test:controllers - ruby -I test/ test/integration/object_manager_test.rb - ruby -I test/ test/integration/object_manager_attributes_controller_test.rb - ruby -I test/ test/integration/package_test.rb @@ -96,7 +95,6 @@ test:unit:postgresql: - rake db:migrate - rake db:seed - rake test:units - - rake test:controllers - ruby -I test/ test/integration/object_manager_test.rb - ruby -I test/ test/integration/object_manager_attributes_controller_test.rb - ruby -I test/ test/integration/package_test.rb @@ -240,12 +238,10 @@ test:integration:es_mysql: - export ES_URL="http://localhost:9200" - rake db:create - rake db:migrate + - ruby -I test/ test/integration/elasticsearch_active_test.rb - ruby -I test/ test/integration/elasticsearch_test.rb - - ruby -I test/ test/controllers/search_controller_test.rb - ruby -I test/ test/integration/report_test.rb - - ruby -I test/ test/controllers/form_controller_test.rb - - ruby -I test/ test/controllers/user_controller_test.rb - - ruby -I test/ test/controllers/organization_controller_test.rb + - bundle exec rspec --tag searchindex - rake db:drop test:integration:es_postgresql: @@ -259,12 +255,10 @@ test:integration:es_postgresql: - export ES_URL="http://localhost:9200" - rake db:create - rake db:migrate + - ruby -I test/ test/integration/elasticsearch_active_test.rb - ruby -I test/ test/integration/elasticsearch_test.rb - - ruby -I test/ test/controllers/search_controller_test.rb - ruby -I test/ test/integration/report_test.rb - - ruby -I test/ test/controllers/form_controller_test.rb - - ruby -I test/ test/controllers/user_controller_test.rb - - ruby -I test/ test/controllers/organization_controller_test.rb + - bundle exec rspec --tag searchindex - rake db:drop test:integration:zendesk_mysql: @@ -300,7 +294,7 @@ test:integration:otrs_6_mysql: - mysql script: - export RAILS_ENV=test - - export IMPORT_OTRS_ENDPOINT="http://vz1185.test.znuny.com/otrs/public.pl?Action=ZammadMigrator" + - export IMPORT_OTRS_ENDPOINT="https://vz1185.test.znuny.com/otrs/public.pl?Action=ZammadMigrator" - rake db:create - rake db:migrate - ruby -I test/ test/integration/otrs_import_test.rb @@ -313,7 +307,7 @@ test:integration:otrs_6_postgresql: - postgresql script: - export RAILS_ENV=test - - export IMPORT_OTRS_ENDPOINT="http://vz1185.test.znuny.com/otrs/public.pl?Action=ZammadMigrator" + - export IMPORT_OTRS_ENDPOINT="https://vz1185.test.znuny.com/otrs/public.pl?Action=ZammadMigrator" - rake db:create - rake db:migrate - ruby -I test/ test/integration/otrs_import_test.rb @@ -405,13 +399,13 @@ test:browser:integration:api_client_ruby: script: - RAILS_ENV=test rake db:create - cp contrib/auto_wizard_test.json auto_wizard.json - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - git clone git@github.com:zammad/zammad-api-client-ruby.git || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - git clone git@github.com:zammad/zammad-api-client-ruby.git || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 - cd zammad-api-client-ruby - bundle install --jobs 8 - export TEST_URL=http://$IP:$BROWSER_PORT/ - - bundle exec rspec || (cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1) - - cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - bundle exec rspec || (cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0) + - cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 test:browser:integration:api_client_php: stage: browser-core @@ -422,16 +416,16 @@ test:browser:integration:api_client_php: script: - RAILS_ENV=test rake db:create - cp contrib/auto_wizard_test.json auto_wizard.json - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - git clone git@github.com:zammad/zammad-api-client-php || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - git clone git@github.com:zammad/zammad-api-client-php || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 - wget http://$IP:$BROWSER_PORT/api/v1/getting_started/auto_wizard - cd zammad-api-client-php - composer install - export ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_URL=http://$IP:$BROWSER_PORT - export ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_USERNAME=master@example.com - export ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_PASSWORD=test - - vendor/bin/phpunit || (cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1) - - cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - vendor/bin/phpunit || (cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0) + - cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 test:browser:twitter_ff: stage: browser-integration @@ -443,12 +437,12 @@ test:browser:twitter_ff: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - RAILS_ENV=test rake db:create - cp contrib/auto_wizard_test.json auto_wizard.json - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - ruby -I test/ test/integration/twitter_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - ruby -I test/ test/integration/twitter_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 test:browser:facebook_ff: stage: browser-integration @@ -460,12 +454,12 @@ test:browser:facebook_ff: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - RAILS_ENV=test rake db:create - cp contrib/auto_wizard_test.json auto_wizard.json - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - ruby -I test/ test/integration/facebook_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - ruby -I test/ test/integration/facebook_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 test:browser:autowizard_ff: stage: browser-core @@ -476,12 +470,12 @@ test:browser:autowizard_ff: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - RAILS_ENV=test rake db:create - cp contrib/auto_wizard_example.json auto_wizard.json - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - ruby -I test/ test/integration/auto_wizard_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - ruby -I test/ test/integration/auto_wizard_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_1_mysql: stage: browser-core @@ -493,16 +487,16 @@ test:browser:core:ff_1_mysql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 1 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_2_mysql: stage: browser-core @@ -514,16 +508,16 @@ test:browser:core:ff_2_mysql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 2 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_3_mysql: stage: browser-core @@ -535,12 +529,12 @@ test:browser:core:ff_3_mysql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - script/build/test_slice_tests.sh 3 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_4_mysql: stage: browser-core @@ -552,16 +546,16 @@ test:browser:core:ff_4_mysql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 4 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_5_mysql: stage: browser-core @@ -573,16 +567,16 @@ test:browser:core:ff_5_mysql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 5 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_6_mysql: stage: browser-core @@ -594,16 +588,16 @@ test:browser:core:ff_6_mysql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 6 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_1_postgresql: stage: browser-core @@ -615,16 +609,16 @@ test:browser:core:ff_1_postgresql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 1 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_2_postgresql: stage: browser-core @@ -636,16 +630,16 @@ test:browser:core:ff_2_postgresql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 2 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_3_postgresql: stage: browser-core @@ -657,12 +651,12 @@ test:browser:core:ff_3_postgresql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - script/build/test_slice_tests.sh 3 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_4_postgresql: stage: browser-core @@ -674,16 +668,16 @@ test:browser:core:ff_4_postgresql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 4 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_5_postgresql: stage: browser-core @@ -695,16 +689,16 @@ test:browser:core:ff_5_postgresql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 5 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:ff_6_postgresql: stage: browser-core @@ -716,16 +710,16 @@ test:browser:core:ff_6_postgresql: script: - export BROWSER=firefox - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 6 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_1_mysql: stage: browser-core @@ -737,16 +731,16 @@ test:browser:core:chrome_1_mysql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 1 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_2_mysql: stage: browser-core @@ -758,16 +752,16 @@ test:browser:core:chrome_2_mysql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 2 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_3_mysql: stage: browser-core @@ -779,16 +773,16 @@ test:browser:core:chrome_3_mysql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 3 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_4_mysql: stage: browser-core @@ -800,16 +794,16 @@ test:browser:core:chrome_4_mysql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 4 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_5_mysql: stage: browser-core @@ -821,16 +815,16 @@ test:browser:core:chrome_5_mysql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 5 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_6_mysql: stage: browser-core @@ -842,16 +836,16 @@ test:browser:core:chrome_6_mysql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 6 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_1_postgresql: stage: browser-core @@ -863,16 +857,16 @@ test:browser:core:chrome_1_postgresql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 1 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_2_postgresql: stage: browser-core @@ -884,16 +878,16 @@ test:browser:core:chrome_2_postgresql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 2 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_3_postgresql: stage: browser-core @@ -905,16 +899,16 @@ test:browser:core:chrome_3_postgresql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 3 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_4_postgresql: stage: browser-core @@ -926,16 +920,16 @@ test:browser:core:chrome_4_postgresql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 4 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_5_postgresql: stage: browser-core @@ -947,16 +941,16 @@ test:browser:core:chrome_5_postgresql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 5 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:core:chrome_6_postgresql: stage: browser-core @@ -968,16 +962,16 @@ test:browser:core:chrome_6_postgresql: script: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" + - export APP_RESTART_CMD="script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0 0 && script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 0" - unset MAILBOX_AUTO1 - unset MAILBOX_AUTO2 - unset MAILBOX_MANUAL1 - unset MAILBOX_MANUAL2 - script/build/test_slice_tests.sh 6 - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - rake test:browser || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:autowizard_chrome: stage: browser-core @@ -990,9 +984,9 @@ test:browser:autowizard_chrome: - export BROWSER_URL=http://$IP:$BROWSER_PORT - RAILS_ENV=test rake db:create - cp contrib/auto_wizard_example.json auto_wizard.json - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - ruby -I test/ test/integration/auto_wizard_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 + - ruby -I test/ test/integration/auto_wizard_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 1 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 1 test:browser:integration:twitter_chrome: stage: browser-integration @@ -1006,9 +1000,9 @@ test:browser:integration:twitter_chrome: - export BROWSER_URL=http://$IP:$BROWSER_PORT - RAILS_ENV=test rake db:create - cp contrib/auto_wizard_test.json auto_wizard.json - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - ruby -I test/ test/integration/twitter_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - ruby -I test/ test/integration/twitter_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 test:browser:integration:facebook_chrome: stage: browser-integration @@ -1022,9 +1016,9 @@ test:browser:integration:facebook_chrome: - export BROWSER_URL=http://$IP:$BROWSER_PORT - RAILS_ENV=test rake db:create - cp contrib/auto_wizard_test.json auto_wizard.json - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - ruby -I test/ test/integration/facebook_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - ruby -I test/ test/integration/facebook_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 test:browser:integration:otrs_chrome: stage: browser-integration @@ -1036,9 +1030,9 @@ test:browser:integration:otrs_chrome: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - ruby -I test/ test/integration/otrs_import_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - ruby -I test/ test/integration/otrs_import_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 test:browser:integration:zendesk_chrome: stage: browser-integration @@ -1050,6 +1044,21 @@ test:browser:integration:zendesk_chrome: - export BROWSER=chrome - export BROWSER_URL=http://$IP:$BROWSER_PORT - RAILS_ENV=test rake db:create - - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 - - ruby -I test/ test/integration/zendesk_import_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 - - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - ruby -I test/ test/integration/zendesk_import_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 + +test:browser:integration:idoit_chrome: + stage: browser-integration + dependencies: + - browser:build + tags: + - browser + script: + - export BROWSER=chrome + - export BROWSER_URL=http://$IP:$BROWSER_PORT + - RAILS_ENV=test rake db:create + - cp contrib/auto_wizard_test.json auto_wizard.json + - script/build/test_startup.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 0 + - ruby -I test/ test/integration/idoit_browser_test.rb || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1 0 + - script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1 0 diff --git a/.travis.yml b/.travis.yml index 59b08827a93c..fc3afe9bbd9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,7 +65,6 @@ script: - bundle exec rspec - rake db:reset - rake test:units - - rake test:controllers - ruby -I test/ test/integration/object_manager_test.rb - ruby -I test/ test/integration/package_test.rb after_success: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0afee119e7c4..6f90af7148e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [2.6.0](https://github.com/zammad/zammad/tree/2.6.0) (2018-xx-xx) -[Full Changelog](https://github.com/zammad/zammad/compare/2.5.0...2.6.0) +## [2.7.0](https://github.com/zammad/zammad/tree/2.7.0) (2018-xx-xx) +[Full Changelog](https://github.com/zammad/zammad/compare/2.6.0...2.7.0) **Implemented enhancements:** @@ -12,5 +12,4 @@ - \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/Gemfile b/Gemfile index b7a61f748628..29b129d0d9fa 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ ruby '2.4.4' gem 'rails', '5.1.5' # core - rails additions +gem 'activerecord-import' gem 'activerecord-session_store' gem 'composite_primary_keys' gem 'json' @@ -80,7 +81,7 @@ gem 'twitter' # channels - email additions gem 'htmlentities' -gem 'mail', '>= 2.7.1.rc1' +gem 'mail', '2.6.6' gem 'mime-types' gem 'rchardet', '>= 1.8.0' gem 'valid_email2' @@ -158,14 +159,15 @@ group :development, :test do # changelog generation gem 'github_changelog_generator' - # Setting ENV for testing purposes - gem 'figaro' - # Use Factory Bot for generating random test data gem 'factory_bot_rails' # mock http calls gem 'webmock' + + # record and replay TCP/HTTP transactions + gem 'tcr' + gem 'vcr' end # Want to extend Zammad with additional gems? @@ -174,4 +176,5 @@ end # without having your changes overwritten during upgrades.) # ZAMMAD DEVS: Consult the internal wiki # (or else risk pushing unwanted changes to Gemfile.lock!) +# https://git.znuny.com/zammad/zammad/wikis/Tips#user-content-customizing-the-gemfile eval_gemfile 'Gemfile.local' if File.exist?('Gemfile.local') diff --git a/Gemfile.lock b/Gemfile.lock index 77042459404e..592e36c5f7c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -49,6 +49,8 @@ GEM activemodel (= 5.1.5) activesupport (= 5.1.5) arel (~> 8.0) + activerecord-import (0.25.0) + activerecord (>= 3.2) activerecord-nulldb-adapter (0.3.7) activerecord (>= 2.0.0) activerecord-session_store (1.1.0) @@ -108,7 +110,7 @@ GEM thor crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.3) + crass (1.0.4) daemons (1.2.5) dalli (2.7.6) debug_inspector (0.0.3) @@ -122,7 +124,7 @@ GEM docile (1.1.5) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - doorkeeper (4.2.6) + doorkeeper (4.4.0) railties (>= 4.2) eco (1.0.0) coffee-script @@ -145,12 +147,10 @@ GEM multipart-post (>= 1.2, < 3) faraday-http-cache (2.0.0) faraday (~> 0.8) - ffi (1.9.23) + ffi (1.9.25) ffi-compiler (0.1.3) ffi (>= 1.0.0) rake - figaro (1.1.1) - thor (~> 0.14) formatador (0.2.5) github_changelog_generator (1.14.3) activesupport @@ -222,15 +222,14 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.5.9) lumberjack (1.0.12) - mail (2.7.1.rc1) - mini_mime (>= 0.1.1) + mail (2.6.6) + mime-types (>= 1.16, < 4) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) method_source (0.9.0) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) - mini_mime (1.0.0) mini_portile2 (2.3.0) minitest (5.11.3) multi_json (1.12.2) @@ -243,7 +242,7 @@ GEM net-ldap (0.16.1) netrc (0.11.0) nio4r (2.3.0) - nokogiri (1.8.2) + nokogiri (1.8.4) mini_portile2 (~> 2.3.0) nori (2.6.0) notiffany (0.1.1) @@ -320,7 +319,7 @@ GEM rack (2.0.5) rack-livereload (0.3.16) rack - rack-test (1.0.0) + rack-test (1.1.0) rack (>= 1.0, < 3) rails (5.1.5) actioncable (= 5.1.5) @@ -360,23 +359,23 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) retriable (2.1.0) - rspec-core (3.7.0) - rspec-support (~> 3.7.0) - rspec-expectations (3.7.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-mocks (3.7.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-rails (3.7.2) + rspec-support (~> 3.8.0) + rspec-rails (3.8.0) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-support (~> 3.7.0) - rspec-support (3.7.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) rubocop (0.54.0) parallel (~> 1.10) parser (>= 2.5) @@ -386,7 +385,7 @@ GEM unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) ruby_dep (1.5.0) - rubyzip (1.2.1) + rubyzip (1.2.2) safe_yaml (1.0.4) sass (3.5.3) sass-listen (~> 4.0.0) @@ -402,7 +401,7 @@ GEM sawyer (0.8.1) addressable (>= 2.3.5, < 2.6) faraday (~> 0.8, < 1.0) - selenium-webdriver (3.11.0) + selenium-webdriver (3.13.1) childprocess (~> 0.5) rubyzip (~> 1.2) shellany (0.0.1) @@ -424,6 +423,7 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.13) + tcr (0.2.0) telegramAPI (1.4.2) rest-client (~> 2.0, >= 2.0.2) telephone_number (1.3.0) @@ -463,6 +463,7 @@ GEM valid_email2 (2.1.0) activemodel (>= 3.2) mail (~> 2.5) + vcr (4.0.0) viewpoint (1.1.0) httpclient logging @@ -487,6 +488,7 @@ PLATFORMS ruby DEPENDENCIES + activerecord-import activerecord-nulldb-adapter activerecord-session_store argon2 @@ -511,7 +513,6 @@ DEPENDENCIES eventmachine execjs factory_bot_rails - figaro github_changelog_generator guard guard-livereload @@ -522,7 +523,7 @@ DEPENDENCIES json koala libv8 - mail (>= 2.7.1.rc1) + mail (= 2.6.6) mime-types mysql2 net-ldap @@ -559,6 +560,7 @@ DEPENDENCIES slack-notifier sprockets sqlite3 + tcr telegramAPI telephone_number test-unit @@ -567,6 +569,7 @@ DEPENDENCIES uglifier unicorn valid_email2 + vcr viewpoint webmock writeexcel diff --git a/VERSION b/VERSION index 3c8067655b26..e17ae7579770 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.x +2.7.x diff --git a/app/assets/javascripts/app/controllers/_application_controller.coffee b/app/assets/javascripts/app/controllers/_application_controller.coffee index 56f4a520a922..f51d9094b550 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.coffee @@ -77,10 +77,6 @@ class App.Controller extends Spine.Controller if @ajaxCalls for callId in @ajaxCalls App.Ajax.abort(callId) - @userTicketPopupsDestroy() - @ticketPopupsDestroy() - @userPopupsDestroy() - @organizationPopupsDestroy() release: -> # release custom bindings after it got removed from dom @@ -182,11 +178,11 @@ class App.Controller extends Spine.Controller formParam: (form) -> App.ControllerForm.params(form) - formDisable: (form) -> - App.ControllerForm.disable(form) + formDisable: (form, type) -> + App.ControllerForm.disable(form, type) - formEnable: (form) -> - App.ControllerForm.enable(form) + formEnable: (form, type) -> + App.ControllerForm.enable(form, type) formValidate: (data) -> App.ControllerForm.validate(data) @@ -295,228 +291,6 @@ class App.Controller extends Spine.Controller item.attr('title', App.i18n.translateTimestamp(timestamp)) item.html(time) - ticketPopups: (position = 'right') -> - - # open ticket in new task if curent user agent - if @permissionCheck('ticket.agent') - @$('div.ticket-popover, span.ticket-popover').bind('click', (e) => - id = $(e.target).data('id') - return if !id - ticket = App.Ticket.findNative(id) - @navigate ticket.uiUrl() - ) - - @ticketPopupsDestroy() - - # show ticket popup - ui = @ - @ticketPopupsList = @el.find('.ticket-popover').popover( - trigger: 'hover' - container: 'body' - html: true - animation: false - delay: 100 - placement: position - title: -> - ticketId = $(@).data('id') - ticket = App.Ticket.find(ticketId) - App.Utils.htmlEscape(ticket.title) - content: -> - ticketId = $(@).data('id') - ticket = App.Ticket.fullLocal(ticketId) - html = $(App.view('popover/ticket')( - ticket: ticket - )) - html.find('.humanTimeFromNow').each(-> - ui.frontendTimeUpdateItem($(@)) - ) - html - ) - - ticketPopupsDestroy: => - if @ticketPopupsList - @ticketPopupsList.popover('destroy') - - userPopups: (position = 'right') -> - - # open user in new task if current user is agent - return if !@permissionCheck('ticket.agent') - @$('div.user-popover, span.user-popover').bind('click', (e) => - id = $(e.target).data('id') - return if !id - user = App.User.findNative(id) - @navigate user.uiUrl() - ) - - @userPopupsDestroy() - - # show user popup - @userPopupsList = @el.find('.user-popover').popover( - trigger: 'hover' - container: 'body' - html: true - animation: false - delay: 100 - placement: "auto #{position}" - title: -> - userId = $(@).data('id') - user = App.User.find(userId) - headline = App.Utils.htmlEscape(user.displayName()) - if user.isOutOfOffice() - headline += " (#{App.Utils.htmlEscape(user.outOfOfficeText())})" - headline - content: -> - userId = $(@).data('id') - user = App.User.fullLocal(userId) - - # get display data - userData = [] - for attributeName, attributeConfig of App.User.attributesGet('view') - - # check if value for _id exists - name = attributeName - nameNew = name.substr(0, name.length - 3) - if nameNew of user - name = nameNew - - # add to show if value exists - if user[name] && attributeConfig.shown - - # do not show firstname and lastname / already show via diplayName() - if name isnt 'firstname' && name isnt 'lastname' && name isnt 'organization' - userData.push attributeConfig - - # insert data - App.view('popover/user')( - user: user - userData: userData - ) - ) - - userPopupsDestroy: => - if @userPopupsList - @userPopupsList.popover('destroy') - - organizationPopups: (position = 'right') -> - - # open org in new task if current user agent - return if !@permissionCheck('ticket.agent') - - @$('div.organization-popover, span.organization-popover').bind('click', (e) => - id = $(e.target).data('id') - return if !id - organization = App.Organization.find(id) - @navigate organization.uiUrl() - ) - - @organizationPopupsDestroy() - - # show organization popup - @organizationPopupsList = @el.find('.organization-popover').popover( - trigger: 'hover' - container: 'body' - html: true - animation: false - delay: 100 - placement: "auto #{position}" - title: -> - organization_id = $(@).data('id') - organization = App.Organization.find(organization_id) - App.Utils.htmlEscape(organization.name) - content: -> - organization_id = $(@).data('id') - organization = App.Organization.fullLocal(organization_id) - - # get display data - organizationData = [] - for attributeName, attributeConfig of App.Organization.attributesGet('view') - - # check if value for _id exists - name = attributeName - nameNew = name.substr(0, name.length - 3) - if nameNew of organization - name = nameNew - - # add to show if value exists - if organization[name] && attributeConfig.shown - - # do not show firstname and lastname / already show via diplayName() - if name isnt 'name' - organizationData.push attributeConfig - - # insert data - App.view('popover/organization')( - organization: organization, - organizationData: organizationData, - ) - ) - - organizationPopupsDestroy: => - if @organizationPopupsList - @organizationPopupsList.popover('destroy') - - userTicketPopups: (params) -> - - show = (data, ticket_list) => - - if !data.position - data.position = 'left' - - @userTicketPopupsDestroy() - - # show user popup - ui = @ - @userTicketPopupsList = @el.find(data.selector).popover( - trigger: 'hover' - container: 'body' - html: true - animation: false - delay: 100 - placement: "auto #{data.position}" - title: -> - $(@).find('[title="*"]').val() - - content: -> - type = $(@).filter('[data-type]').data('type') - tickets = [] - if ticket_list[type] - for ticketId in ticket_list[type] - tickets.push App.Ticket.fullLocal(ticketId) - - # insert data - html = $(App.view('popover/user_ticket_list')( - tickets: tickets - )) - html.find('.humanTimeFromNow').each( -> - ui.frontendTimeUpdateItem($(@)) - ) - html - ) - - fetch = (params) => - @ajax( - type: 'GET' - url: "#{@Config.get('api_path')}/ticket_customer" - data: - customer_id: params.user_id - processData: true - success: (data, status, xhr) -> - App.Collection.loadAssets(data.assets) - show(params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed }) - ) - - # get data - fetch(params) - - userTicketPopupsDestroy: => - if @userTicketPopupsList - @userTicketPopupsList.popover('destroy') - - anyPopoversDestroy: -> - - # do not remove permanent .popover--notifications widget - $('.popover:not(.popover--notifications)').remove() - recentView: (object, o_id) => params = object: object @@ -556,6 +330,10 @@ class App.Controller extends Spine.Controller stopPropagation: (e) -> e.stopPropagation() + preventDefaultAndstopPropagation: (e) -> + e.preventDefault() + e.stopPropagation() + startLoading: (el) => return if @initLoadingDone && !el @initLoadingDone = true diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.coffee index ed8b58084f9a..b7e43d5752cb 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.coffee @@ -3,7 +3,6 @@ class App.ControllerForm extends App.Controller super for key, value of params @[key] = value - if !@handlers @handlers = [] @@ -14,7 +13,6 @@ class App.ControllerForm extends App.Controller @handlers.push @showHideToggle @handlers.push @requiredMandantoryToggle - if !@model @model = {} if !@attributes @@ -64,9 +62,7 @@ class App.ControllerForm extends App.Controller fieldset = @el else fieldset = $('
') - return fieldset if _.isEmpty(@model) - # collect form attributes @attributes = [] if @model.attributesGet @@ -330,7 +326,7 @@ class App.ControllerForm extends App.Controller bookmarkable: @bookmarkable ) ) - fullItem.find('.controls').prepend( item ) + fullItem.find('.controls').prepend(item) # hide/show item if attribute.hide @@ -632,48 +628,49 @@ class App.ControllerForm extends App.Controller App.Log.error 'ControllerForm', 'no form found!', form form - @disable: (form) -> + @disable: (form, type = 'form') -> lookupForm = @findForm(form) - if lookupForm + if lookupForm && type is 'form' if lookupForm.is('button, input, select, textarea, div, span') + console.log(2) App.Log.debug 'ControllerForm', 'disable item...', lookupForm - lookupForm.attr('readonly', true) - lookupForm.attr('disabled', true) + lookupForm.prop('readonly', true) + lookupForm.prop('disabled', true) return App.Log.debug 'ControllerForm', 'disable form...', lookupForm # set forms to read only during communication with backend - lookupForm.find('button, input, select, textarea').attr('readonly', true) + lookupForm.find('button, input, select, textarea').prop('readonly', true) # disable additionals submits - lookupForm.find('button').attr('disabled', true) + lookupForm.find('button').prop('disabled', true) else App.Log.debug 'ControllerForm', 'disable item...', form - form.attr('readonly', true) - form.attr('disabled', true) + form.prop('readonly', true) + form.prop('disabled', true) - @enable: (form) -> + @enable: (form, type = 'form') -> lookupForm = @findForm(form) - if lookupForm + if lookupForm && type is 'form' if lookupForm.is('button, input, select, textarea, div, span') App.Log.debug 'ControllerForm', 'disable item...', lookupForm - lookupForm.attr('readonly', false) - lookupForm.attr('disabled', false) + lookupForm.prop('readonly', false) + lookupForm.prop('disabled', false) return App.Log.debug 'ControllerForm', 'enable form...', lookupForm # enable fields again - lookupForm.find('button, input, select, textarea').attr('readonly', false) + lookupForm.find('button, input, select, textarea').prop('readonly', false) # enable submits again - lookupForm.find('button').attr('disabled', false) + lookupForm.find('button').prop('disabled', false) else App.Log.debug 'ControllerForm', 'enable item...', form - form.attr('readonly', false) - form.attr('disabled', false) + form.prop('readonly', false) + form.prop('disabled', false) @validate: (data) -> diff --git a/app/assets/javascripts/app/controllers/_application_controller_form_organization.coffee b/app/assets/javascripts/app/controllers/_application_controller_form_organization.coffee new file mode 100644 index 000000000000..2415a2cbb74f --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller_form_organization.coffee @@ -0,0 +1,10 @@ +class App.ControllerFormOrganization extends App.ControllerForm + constructor: (params) -> + @user = App.User.find(App.Session.get('id')) + @organizations = [] + if @user.organization_id + @organizations.push @user.organization_id + if @user.organization_ids + @organizations = @organizations.concat @user.organization_ids + params['filter']['organization_id'] = @organizations + super \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_application_controller_generic.coffee b/app/assets/javascripts/app/controllers/_application_controller_generic.coffee index 9a9855a45843..2f9bff2fc9db 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_generic.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_generic.coffee @@ -545,6 +545,9 @@ class App.ControllerNavSidbar extends App.Controller sidebar: @$('.sidebar').scrollTop() class App.GenericHistory extends App.ControllerModal + @extend App.PopoverProvidable + @registerPopovers 'User' + buttonClose: true buttonCancel: false buttonSubmit: false @@ -568,7 +571,7 @@ class App.GenericHistory extends App.ControllerModal content onShown: => - @userPopups() + @renderPopovers() sortorder: => @items = @items.reverse() @@ -1403,4 +1406,4 @@ class App.ImportResult extends App.ControllerModal content onSubmit: (e) => - @close() \ No newline at end of file + @close() diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.coffee b/app/assets/javascripts/app/controllers/_application_controller_table.coffee index d32477773108..6240cca77b36 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/_channel/email.coffee b/app/assets/javascripts/app/controllers/_channel/email.coffee index e5e4ba32115c..6889e4c14819 100644 --- a/app/assets/javascripts/app/controllers/_channel/email.coffee +++ b/app/assets/javascripts/app/controllers/_channel/email.coffee @@ -60,68 +60,23 @@ class App.ChannelEmailFilter extends App.Controller new: (e) => e.preventDefault() - new App.ChannelEmailFilterEdit( + new App.ControllerGenericNew( + pageData: + object: 'Postmaster Filter' + genericObject: 'PostmasterFilter' container: @el.closest('.content') + callback: @load ) - + edit: (id, e) => e.preventDefault() - new App.ChannelEmailFilterEdit( - object: App.PostmasterFilter.find(id) + new App.ControllerGenericEdit( + id: id, + pageData: + object: 'Postmaster Filter' + genericObject: 'PostmasterFilter' container: @el.closest('.content') - ) - -class App.ChannelEmailFilterEdit extends App.ControllerModal - buttonClose: true - buttonCancel: true - buttonSubmit: true - head: 'Postmaster Filter' - - content: => - if @object - @form = new App.ControllerForm( - model: App.PostmasterFilter, - params: @object, - autofocus: true, - ) - else - @form = new App.ControllerForm( - model: App.PostmasterFilter, - autofocus: true, - ) - - @form.form - - onSubmit: (e) => - e.preventDefault() - - # get params - params = @formParam(e.target) - params['channel'] = 'email' - - object = @object || new App.PostmasterFilter - object.load(params) - - # validate form - errors = @form.validate(params) - - # show errors in form - if errors - @log 'error', errors - @formValidate(form: e.target, errors: errors) - return false - - # disable form - @formDisable(e) - - # save object - object.save( - done: => - @close() - fail: (settings, details) => - @log 'errors', details - @formEnable(e) - @form.showAlert(details.error_human || details.error || 'Unable to create object!') + callback: @load ) class App.ChannelEmailSignature extends App.Controller diff --git a/app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.coffee b/app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.coffee index cc272b47f43b..546d3e29b605 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.coffee @@ -52,11 +52,9 @@ class App.UiElement.ApplicationUiElement row.name = App.i18n.translateInline(row.name) attribute.options.push row else - order = _.sortBy( - _.keys(selection), (item) -> - return '' if !selection[item] || !selection[item].toString - selection[item].toString().toLowerCase() - ) + forceString = (s) -> + return if !selection[s] || !selection[s].toString then '' else selection[s].toString() + order = _.keys(selection).sort( (a, b) -> forceString(a).localeCompare(forceString(b)) ) for key in order name_new = selection[key] if attribute.translate diff --git a/app/assets/javascripts/app/controllers/_ui_element/autocompletion_multiple_ajax.coffee b/app/assets/javascripts/app/controllers/_ui_element/autocompletion_multiple_ajax.coffee new file mode 100644 index 000000000000..48ec08a4dbbd --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/autocompletion_multiple_ajax.coffee @@ -0,0 +1,19 @@ +class App.UiElement.autocompletion_multiple_ajax + @render: (attribute, params = {}) -> + if params[attribute.name]?.length > 0 || attribute.value?.length > 0 + object = App[attribute.relation].find(params[attribute.name][0] || attribute.value[0]) + valueName = object.displayName() + + # selectable search + searchableAjaxSelectObject = new App.SearchableMultipleSelect( + attribute: + value: params[attribute.name] || attribute.value + valueName: valueName + name: attribute.name + id: attribute.value + placeholder: App.i18n.translateInline('Search...') + limit: 40 + object: attribute.relation + ajax: true + ) + searchableAjaxSelectObject.element() \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/basedate.coffee b/app/assets/javascripts/app/controllers/_ui_element/basedate.coffee new file mode 100644 index 000000000000..2603e6a1011e --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/basedate.coffee @@ -0,0 +1,156 @@ +# coffeelint: disable=camel_case_classes +# Base class for providing date picker. Must be extended +class App.UiElement.basedate + @templateName: -> + throw 'Must override in a subclass' + + @render: (attributeOrig) -> + attribute = _.clone(attributeOrig) + attribute.nameRaw = attribute.name + attribute.name = "{#{@templateName()}}#{attribute.name}" + + item = $( App.view("generic/#{@templateName()}")( + attribute: attribute + ) ) + + # set our custom template + $.fn.datepicker.defaults.template = App.view('generic/datepicker')() + + # apply date widgets + $.fn.datepicker.dates['custom'] = @buildCustomDates() + + @applyPickers(item, attribute) + @bindEvents(item, attribute) + + item + + @log: (name, args...) -> + App.Log.debug "Ui.element.#{@templateName()}.#{name}", args... + + @applyPickers: (item, attribute) -> + item.find('.js-datepicker').datepicker( + weekStart: 1 + autoclose: true + todayBtn: 'linked' + todayHighlight: true + format: App.i18n.timeFormat().date + rtl: App.i18n.dir() is 'rtl' + container: item + language: 'custom' + ) + + @setNewTimeInitial(item, attribute) + + # observer changes / update needed to forece rerender to get correct today shown + @bindEvents: (item, attribute) -> + item + .find('input') + .bind('focus', (e) -> + item.find('.js-datepicker').datepicker('rerender') + ).bind('keyup blur change', (e) => + @setNewTime(item, attribute, 0) + @validation(item, attribute, true) + ) + + item.bind('validate', (e) => + @validation(item, attribute) + ) + + @setNewTime: (item, attribute, tolerant = false) -> + currentInput = @currentInput(item, attribute) + return if !currentInput + + if !@validateInput(currentInput) + item.find("[name=\"#{attribute.name}\"]").val('') + return + + item.find("[name=\"#{attribute.name}\"]").val(@buildTimestamp(currentInput)) + + # returns array with date or false if cannot get date + @currentInput: (item, attribute) -> + datetime = item.find('.js-datepicker').datepicker('getDate') + if !datetime || datetime.toString() is 'Invalid Date' + item.find("[name=\"#{attribute.name}\"]").val('') + return false + + @log 'setNewTime', datetime + + year = datetime.getFullYear() + month = datetime.getMonth() + 1 + day = datetime.getDate() + date = "#{App.Utils.formatTime(year)}-#{App.Utils.formatTime(month,2)}-#{App.Utils.formatTime(day,2)}" + [date] + + @validateInput: (currentInput) -> + currentInput[0] isnt '' + + @buildTimestamp: (currentInput) -> + throw 'Must override in a subclass' + + @dateSetter: -> + throw 'Must override in a subclass' + + @setNewTimeInitial: (item, attribute) -> + timestamp = item.find("[name=\"#{attribute.name}\"]").val() + @log 'setNewTimeInitial', timestamp + if !timestamp + @setNoTimestamp(item) + return + + timeObject = new Date( Date.parse( timestamp ) ) + + @log 'setNewTimeInitial', timestamp, timeObject + @setTimestamp(item, timeObject) + item.find('.js-datepicker').datepicker('update') + + @setNoTimestamp: (item) -> + return + + @setTimestamp: (item, timeObject) -> + item.find('.js-datepicker').datepicker(@dateSetter(), timeObject) + + @validation: (item, attribute, runtime) -> + # remove old validation + if attribute.validationContainer is 'self' + item.find('.js-datepicker').removeClass('has-error') + else + item.closest('.form-group').removeClass('has-error') + item.find('.has-error').removeClass('has-error') + item.find('.help-inline').html('') + item.closest('.form-group').find('.help-inline').html('') + + timestamp = item.find("[name=\"#{attribute.name}\"]").val() + + # check required attributes + errors = {} + if !timestamp + if !attribute.null + errors[attribute.name] = 'missing' + else + timeObject = new Date( Date.parse( timestamp ) ) + + + @log 'validation', errors + return if _.isEmpty(errors) + + # show invalid options + if attribute.validationContainer is 'self' + item.find('.js-datepicker').addClass('has-error') + else + formGroup = item.closest('.form-group') + for key, value of errors + formGroup.addClass('has-error') + + @buildCustomDates: -> + data = { + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + daysMin: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + months: ['January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December'], + monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + today: 'today', + clear: 'clear' + } + + App.i18n.translateDeep(data) diff --git a/app/assets/javascripts/app/controllers/_ui_element/date.coffee b/app/assets/javascripts/app/controllers/_ui_element/date.coffee index e70a6d8347dd..0e48d3041afc 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/date.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/date.coffee @@ -1,161 +1,11 @@ # coffeelint: disable=camel_case_classes -class App.UiElement.date - @render: (attributeOrig) -> +# Provides date-only picker +class App.UiElement.date extends App.UiElement.basedate + @templateName: -> + 'date' - attribute = _.clone(attributeOrig) - attribute.nameRaw = attribute.name - attribute.name = "{date}#{attribute.name}" + @buildTimestamp: (currentInput) -> + currentInput[0] - item = $( App.view('generic/date')( - attribute: attribute - ) ) - - # set our custom template - $.fn.datepicker.defaults.template = App.view('generic/datepicker')() - - # apply date widgets - $.fn.datepicker.dates['custom'] = - days: [ - App.i18n.translateInline('Sunday'), - App.i18n.translateInline('Monday'), - App.i18n.translateInline('Tuesday'), - App.i18n.translateInline('Wednesday'), - App.i18n.translateInline('Thursday'), - App.i18n.translateInline('Friday'), - App.i18n.translateInline('Saturday'), - App.i18n.translateInline('Sunday'), - ], - daysMin: [ - App.i18n.translateInline('Sun'), - App.i18n.translateInline('Mon'), - App.i18n.translateInline('Tue'), - App.i18n.translateInline('Wed'), - App.i18n.translateInline('Thu'), - App.i18n.translateInline('Fri'), - App.i18n.translateInline('Sat'), - App.i18n.translateInline('Sun') - ], - daysShort: [ - App.i18n.translateInline('Sun'), - App.i18n.translateInline('Mon'), - App.i18n.translateInline('Tue'), - App.i18n.translateInline('Wed'), - App.i18n.translateInline('Thu'), - App.i18n.translateInline('Fri'), - App.i18n.translateInline('Sat'), - App.i18n.translateInline('Sun') - ], - months: [ - App.i18n.translateInline('January'), - App.i18n.translateInline('February'), - App.i18n.translateInline('March'), - App.i18n.translateInline('April'), - App.i18n.translateInline('May'), - App.i18n.translateInline('June'), - App.i18n.translateInline('July'), - App.i18n.translateInline('August'), - App.i18n.translateInline('September'), - App.i18n.translateInline('October'), - App.i18n.translateInline('November'), - App.i18n.translateInline('December'), - ], - monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - today: App.i18n.translateInline('today'), - clear: App.i18n.translateInline('clear') - currentDate = undefined - - item.find('.js-datepicker').datepicker( - weekStart: 1 - autoclose: true - todayBtn: 'linked' - todayHighlight: true - format: App.i18n.timeFormat().date - rtl: App.i18n.dir() is 'rtl' - container: item - language: 'custom' - ) - - # set initial date time - @setNewTimeInitial(item, attribute) - - # observer changes / update needed to forece rerender to get correct today shown - item.find('input').bind('focus', (e) -> - item.find('.js-datepicker').datepicker('rerender') - ) - item.find('input').bind('keyup blur change', (e) => - @setNewTime(item, attribute, 0) - @validation(item, attribute, true) - ) - item.bind('validate', (e) => - @validation(item, attribute) - ) - - item - - @setNewTime: (item, attribute, tolerant = false) -> - - datetime = item.find('.js-datepicker').datepicker('getDate') - if !datetime || datetime.toString() is 'Invalid Date' - App.Log.debug 'UiElement.date.setNewTime', datetime - item.find("[name=\"#{attribute.name}\"]").val('') - return - - App.Log.debug 'UiElement.date.setNewTime', datetime - year = datetime.getFullYear() - month = datetime.getMonth() + 1 - day = datetime.getDate() - date = "#{App.Utils.formatTime(year)}-#{App.Utils.formatTime(month,2)}-#{App.Utils.formatTime(day,2)}" - - if date is '' - item.find("[name=\"#{attribute.name}\"]").val('') - return - - App.Log.debug 'UiElement.date.setNewTime', date - item.find("[name=\"#{attribute.name}\"]").val(date) - - @setNewTimeInitial: (item, attribute) -> - App.Log.debug 'UiElement.date.setNewTimeInitial', timestamp - timestamp = item.find("[name=\"#{attribute.name}\"]").val() - return if !timestamp - - timeObject = new Date( Date.parse( timestamp ) ) - - hour = timeObject.getHours() - minute = timeObject.getMinutes() - - App.Log.debug 'UiElement.date.setNewTimeInitial', timestamp, timeObject - item.find('.js-datepicker').datepicker('setUTCDate', timeObject) - item.find('.js-datepicker').datepicker('update') - - @validation: (item, attribute, runtime) -> - - # remove old validation - if attribute.validationContainer is 'self' - item.find('.js-datepicker').removeClass('has-error') - else - item.closest('.form-group').removeClass('has-error') - item.find('.has-error').removeClass('has-error') - item.find('.help-inline').html('') - item.closest('.form-group').find('.help-inline').html('') - - timestamp = item.find("[name=\"#{attribute.name}\"]").val() - - # check required attributes - errors = {} - if !timestamp - if !attribute.null - errors[attribute.name] = 'missing' - else - timeObject = new Date( Date.parse( timestamp ) ) - - - App.Log.debug 'UiElement.date.validation', errors - return if _.isEmpty(errors) - - # show invalid options - if attribute.validationContainer is 'self' - item.find('.js-datepicker').addClass('has-error') - else - formGroup = item.closest('.form-group') - for key, value of errors - formGroup.addClass('has-error') + @dateSetter: -> + 'setUTCDate' diff --git a/app/assets/javascripts/app/controllers/_ui_element/datetime.coffee b/app/assets/javascripts/app/controllers/_ui_element/datetime.coffee index 6e8397572a34..648aa9981e92 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/datetime.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/datetime.coffee @@ -1,174 +1,45 @@ # coffeelint: disable=camel_case_classes -class App.UiElement.datetime - @render: (attributeOrig) -> +# Provides full date and time picker +class App.UiElement.datetime extends App.UiElement.basedate + @templateName: -> + 'datetime' - attribute = _.clone(attributeOrig) - attribute.nameRaw = attribute.name - attribute.name = "{datetime}#{attribute.name}" + @applyPickers: (item, attribute) -> + super(item, attribute) - item = $( App.view('generic/datetime')( - attribute: attribute - ) ) - - # set our custom template - $.fn.datepicker.defaults.template = App.view('generic/datepicker')() - - # apply date widgets - $.fn.datepicker.dates['custom'] = - days: [ - App.i18n.translateInline('Sunday'), - App.i18n.translateInline('Monday'), - App.i18n.translateInline('Tuesday'), - App.i18n.translateInline('Wednesday'), - App.i18n.translateInline('Thursday'), - App.i18n.translateInline('Friday'), - App.i18n.translateInline('Saturday'), - App.i18n.translateInline('Sunday'), - ], - daysMin: [ - App.i18n.translateInline('Sun'), - App.i18n.translateInline('Mon'), - App.i18n.translateInline('Tue'), - App.i18n.translateInline('Wed'), - App.i18n.translateInline('Thu'), - App.i18n.translateInline('Fri'), - App.i18n.translateInline('Sat'), - App.i18n.translateInline('Sun') - ], - daysShort: [ - App.i18n.translateInline('Sun'), - App.i18n.translateInline('Mon'), - App.i18n.translateInline('Tue'), - App.i18n.translateInline('Wed'), - App.i18n.translateInline('Thu'), - App.i18n.translateInline('Fri'), - App.i18n.translateInline('Sat'), - App.i18n.translateInline('Sun') - ], - months: [ - App.i18n.translateInline('January'), - App.i18n.translateInline('February'), - App.i18n.translateInline('March'), - App.i18n.translateInline('April'), - App.i18n.translateInline('May'), - App.i18n.translateInline('June'), - App.i18n.translateInline('July'), - App.i18n.translateInline('August'), - App.i18n.translateInline('September'), - App.i18n.translateInline('October'), - App.i18n.translateInline('November'), - App.i18n.translateInline('December'), - ], - monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - today: App.i18n.translateInline('today'), - clear: App.i18n.translateInline('clear') - currentDate = undefined - - item.find('.js-datepicker').datepicker( - weekStart: 1 - autoclose: true - todayBtn: 'linked' - todayHighlight: true - format: App.i18n.timeFormat().date - rtl: App.i18n.dir() is 'rtl' - container: item - language: 'custom' - ) - - # set initial date time - @setNewTimeInitial(item, attribute) - - # apply time widgets item.find('.js-timepicker').timepicker() - # observer changes / update needed to forece rerender to get correct today shown - item.find('input').bind('focus', (e) -> - item.find('.js-datepicker').datepicker('rerender') - ) - item.find('input').bind('keyup blur change', (e) => - @setNewTime(item, attribute, 0) - @validation(item, attribute, true) - ) - item.bind('validate', (e) => - @validation(item, attribute) - ) - item + # returns array with date and time or false if cannot get date + @currentInput: (item, attribute) -> + result = super(item, attribute) - @setNewTime: (item, attribute, tolerant = false) -> + if _.isArray(result) + result.push item.find('.js-timepicker').val() - datetime = item.find('.js-datepicker').datepicker('getDate') - if !datetime || datetime.toString() is 'Invalid Date' - App.Log.debug 'UiElement.datetime.setNewTime', datetime - item.find("[name=\"#{attribute.name}\"]").val('') - return + result - App.Log.debug 'UiElement.datetime.setNewTime', datetime - year = datetime.getFullYear() - month = datetime.getMonth() + 1 - day = datetime.getDate() - date = "#{App.Utils.formatTime(year)}-#{App.Utils.formatTime(month,2)}-#{App.Utils.formatTime(day,2)}" - time = item.find('.js-timepicker').val() - - if date is '' || time is '' - item.find("[name=\"#{attribute.name}\"]").val('') - return - - timestamp = "#{date}T#{time}:00.000Z" - time = new Date( Date.parse(timestamp) ) - time.setMinutes( time.getMinutes() + time.getTimezoneOffset() ) - App.Log.debug 'UiElement.datetime.setNewTime', time.toString() - timestamp = time.toISOString().replace(/\d\d\.\d\d\dZ$/, '00.000Z') - item.find("[name=\"#{attribute.name}\"]").val(timestamp) + @validateInput: (currentInput) -> + currentInput[0] isnt '' || currentInput[1] isnt '' - @setNewTimeInitial: (item, attribute) -> - App.Log.debug 'UiElement.datetime.setNewTimeInitial', timestamp - timestamp = item.find("[name=\"#{attribute.name}\"]").val() - if !timestamp - item.find('.js-timepicker').val('08:00') - return + @setNoTimestamp: (item) -> + item.find('.js-timepicker').val('08:00') - timeObject = new Date( Date.parse( timestamp ) ) + @setTimestamp: (item, timeObject) -> + super(item, timeObject) hour = timeObject.getHours() minute = timeObject.getMinutes() time = "#{App.Utils.formatTime(hour,2)}:#{App.Utils.formatTime(minute,2)}" - App.Log.debug 'UiElement.datetime.setNewTimeInitial', timestamp, timeObject - item.find('.js-datepicker').datepicker('setUTCDate', timeObject) item.find('.js-timepicker').val(time) - item.find('.js-datepicker').datepicker('update') - - @validation: (item, attribute, runtime) -> - - # remove old validation - if attribute.validationContainer is 'self' - item.find('.js-datepicker').removeClass('has-error') - else - item.closest('.form-group').removeClass('has-error') - item.find('.has-error').removeClass('has-error') - item.find('.help-inline').html('') - item.closest('.form-group').find('.help-inline').html('') - - timestamp = item.find("[name=\"#{attribute.name}\"]").val() - - # check required attributes - errors = {} - if !timestamp - if !attribute.null - errors[attribute.name] = 'missing' - else - timeObject = new Date( Date.parse( timestamp ) ) - - App.Log.debug 'UiElement.datetime.validation', errors - return if _.isEmpty(errors) - - # show invalid options - if attribute.validationContainer is 'self' - item.find('.js-datepicker').addClass('has-error') - else - formGroup = item.closest('.form-group') - for key, value of errors - formGroup.addClass('has-error') + @buildTimestamp: (currentInput) -> + timestamp = "#{currentInput[0]}T#{currentInput[1]}:00.000Z" + time = new Date( Date.parse(timestamp) ) + time.setMinutes( time.getMinutes() + time.getTimezoneOffset() ) + @log 'setNewTime', time.toString() + time.toISOString().replace(/\d\d\.\d\d\dZ$/, '00.000Z') + @dateSetter: -> + 'setDate' diff --git a/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee b/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee index 71fd11be64db..99ae033f6187 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee @@ -7,6 +7,10 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi if params.data_option_new && !_.isEmpty(params.data_option_new) params.data_option = params.data_option_new + if attribute.value == 'select' && params.data_option? && params.data_option.options? + sorted = _.map params.data_option.options, (value, key) -> [key.toString(), value.toString()] + params.data_option.sorted = sorted.sort( (a, b) -> a[1].localeCompare(b[1]) ) + item = $(App.view('object_manager/attribute')(attribute: attribute)) updateDataMap = (localParams, localAttribute, localAttributes, localClassname, localForm, localA) => diff --git a/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.coffee b/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.coffee index 25e29568a413..b3803ed49577 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/postmaster_set.coffee @@ -72,10 +72,7 @@ class App.UiElement.postmaster_set elements = {} for groupKey, groupMeta of groups - if !App[groupMeta.model] - elements["#{groupKey}.email"] = { name: 'email', display: 'Email' } - else - + if groupMeta.model && App[groupMeta.model] for row in App[groupMeta.model].configure_attributes # ignore passwords and relations @@ -117,32 +114,39 @@ class App.UiElement.postmaster_set [elements, groups] + @placeholder: (elementFull, attribute, params = {}, groups) -> + item = $( App.view('generic/postmaster_set_row')( attribute: attribute ) ) + selector = @buildAttributeSelector(elementFull, groups, attribute, item) + item.find('.js-attributeSelector').prepend(selector) + item + @render: (attribute, params = {}) -> [elements, groups] = @defaults() - selector = @buildAttributeSelector(groups, attribute) - # scaffold of match elements item = $( App.view('generic/postmaster_set')( attribute: attribute ) ) - item.find('.js-attributeSelector').prepend(selector) # add filter - item.find('.js-add').bind('click', (e) -> + item.on('click', '.js-add', (e) => element = $(e.target).closest('.js-filterElement') - elementClone = element.clone(true) - element.after(elementClone) - elementClone.find('.js-attributeSelector select').trigger('change') + placeholder = @placeholder(item, attribute, params, groups) + if element.get(0) + element.after(placeholder) + else + item.append(placeholder) + placeholder.find('.js-attributeSelector select').trigger('change') ) # remove filter - item.find('.js-remove').bind('click', (e) => + item.on('click', '.js-remove', (e) => + return if $(e.currentTarget).hasClass('is-disabled') $(e.target).closest('.js-filterElement').remove() @rebuildAttributeSelectors(item) ) # change attribute selector - item.find('.js-attributeSelector select').bind('change', (e) => + item.on('change', '.js-attributeSelector select', (e) => key = $(e.target).find('option:selected').attr('value') elementRow = $(e.target).closest('.js-filterElement') groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value') @@ -152,27 +156,20 @@ class App.UiElement.postmaster_set ) # build inital params - if !_.isEmpty(params[attribute.name]) - - selectorExists = false - for key, meta of params[attribute.name] - selectorExists = true - operator = meta.operator - value = meta.value + if _.isEmpty(params[attribute.name]) + item.append(@placeholder(item, attribute, params, groups)) + return item - # get selector rows - elementFirst = item.find('.js-filterElement').first() - elementLast = item.find('.js-filterElement').last() + for key, meta of params[attribute.name] + operator = meta.operator + value = meta.value - # clone, rebuild and append - elementClone = elementFirst.clone(true) - @rebuildAttributeSelectors(item, elementClone, key, attribute) - @buildValue(item, elementClone, key, groups, value, operator, attribute) - elementLast.after(elementClone) + # build and append + element = @placeholder(item, attribute, params, groups) + @rebuildAttributeSelectors(item, element, key, attribute) + @buildValue(item, element, key, groups, value, operator, attribute) - # remove first dummy row - if selectorExists - item.find('.js-filterElement').first().remove() + item.append(element) item @@ -197,7 +194,15 @@ class App.UiElement.postmaster_set item = App.UiElement[config.tag].render(config, {}) elementRow.find('.js-value').html(item) - @buildAttributeSelector: (groups, attribute) -> + @buildAttributeSelector: (elementFull, groups, attribute) -> + + # find first possible attribute + selectedValue = '' + elementFull.find('.js-attributeSelector select option').each(-> + if !selectedValue && !$(@).prop('disabled') + selectedValue = $(@).val() + ) + selection = $('') for groupKey, groupMeta of groups displayName = App.i18n.translateInline(groupMeta.name) @@ -205,7 +210,10 @@ class App.UiElement.postmaster_set optgroup = selection.find("optgroup.js-#{groupKey}") for entry in groupMeta.options displayName = App.i18n.translateInline(entry.name) - optgroup.append("") + selected = '' + if entry.value is selectedValue + selected = 'selected="selected"' + optgroup.append("") selection @rebuildAttributeSelectors: (elementFull, elementRow, key, attribute) -> @@ -240,11 +248,11 @@ class App.UiElement.postmaster_set selection = $("") attributeConfig = elements[groupAndAttribute] - if !attributeConfig.operator + if !attributeConfig || !attributeConfig.operator elementRow.find('.js-operator').addClass('hide') else elementRow.find('.js-operator').removeClass('hide') - if attributeConfig.operator + if attributeConfig && attributeConfig.operator for operator in attributeConfig.operator operatorName = App.i18n.translateInline(operator) selected = '' diff --git a/app/assets/javascripts/app/controllers/_ui_element/richtext.coffee b/app/assets/javascripts/app/controllers/_ui_element/richtext.coffee index 387a35a8f1d2..b43483c3190b 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/richtext.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/richtext.coffee @@ -73,12 +73,14 @@ class App.UiElement.richtext @attachmentPlaceholder.addClass('hide') @attachmentUpload.removeClass('hide') @cancelContainer.removeClass('hide') + item.find('[contenteditable]').trigger('fileUploadStart') App.Log.debug 'UiElement.richtext', 'upload start' onAborted: => @attachmentPlaceholder.removeClass('hide') @attachmentUpload.addClass('hide') item.find('input').val('') + item.find('[contenteditable]').trigger('fileUploadStop', ['aborted']) # Called after received response from the server onCompleted: (response) => @@ -93,7 +95,7 @@ class App.UiElement.richtext renderFile(response.data) item.find('input').val('') - + item.find('[contenteditable]').trigger('fileUploadStop', ['completed']) App.Log.debug 'UiElement.richtext', 'upload complete', response.data # Called during upload progress, first parameter diff --git a/app/assets/javascripts/app/controllers/_ui_element/select.coffee b/app/assets/javascripts/app/controllers/_ui_element/select.coffee index da812722e4fb..e8dbdf1b38c5 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/select.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/select.coffee @@ -8,6 +8,9 @@ class App.UiElement.select extends App.UiElement.ApplicationUiElement else attribute.multiple = '' + # add deleted historical options if required + @addDeletedOptions(attribute, params) + # build options list based on config @getConfigOptionList(attribute, params) @@ -31,3 +34,20 @@ class App.UiElement.select extends App.UiElement.ApplicationUiElement # return item $( App.view('generic/select')(attribute: attribute) ) + + # 1. If attribute.value is not among the current options, then search within historical options + # 2. If attribute.value is not among current and historical options, then add the value itself as an option + @addDeletedOptions: (attribute) -> + return if !_.isEmpty(attribute.relation) # do not apply for attributes with relation, relations will fill options automatically + value = attribute.value + return if !value + return if _.isArray(value) + return if !attribute.options + return if !_.isObject(attribute.options) + return if value of attribute.options + return if value in (temp for own prop, temp of attribute.options) + + if attribute.historical_options && value of attribute.historical_options + attribute.options[value] = attribute.historical_options[value] + else + attribute.options[value] = value diff --git a/app/assets/javascripts/app/controllers/_ui_element/select_organization.coffee b/app/assets/javascripts/app/controllers/_ui_element/select_organization.coffee new file mode 100644 index 000000000000..6f8f4ea760ee --- /dev/null +++ b/app/assets/javascripts/app/controllers/_ui_element/select_organization.coffee @@ -0,0 +1,5 @@ +class App.UiElement.select_organization extends App.UiElement.select + @render: (attribute, params) -> + if attribute['default'] == 0 + attribute['default'] = attribute['filter']&[0] + super(attribute, params) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee index 8ca71006a923..f9ac78d3e5c5 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee @@ -16,7 +16,7 @@ class App.UiElement.ticket_perform_action # megre config elements = {} for groupKey, groupMeta of groups - if !App[groupMeta.model] + if !groupMeta.model || !App[groupMeta.model] elements["#{groupKey}.email"] = { name: 'email', display: 'Email' } else @@ -45,92 +45,82 @@ class App.UiElement.ticket_perform_action [defaults, groups, elements] + @placeholder: (elementFull, attribute, params, groups, elements) -> + item = $( App.view('generic/ticket_perform_action/row')( attribute: attribute ) ) + selector = @buildAttributeSelector(elementFull, groups, elements) + item.find('.js-attributeSelector').prepend(selector) + item + @render: (attribute, params = {}) -> [defaults, groups, elements] = @defaults(attribute) - selector = @buildAttributeSelector(groups, elements) - # return item item = $( App.view('generic/ticket_perform_action/index')( attribute: attribute ) ) - item.find('.js-attributeSelector').prepend(selector) # add filter - item.find('.js-add').bind('click', (e) => + item.on('click', '.js-add', (e) => element = $(e.target).closest('.js-filterElement') - elementClone = element.clone(true) - element.after(elementClone) - elementClone.find('.js-attributeSelector select').trigger('change') + placeholder = @placeholder(item, attribute, params, groups, elements) + if element.get(0) + element.after(placeholder) + else + item.append(placeholder) + placeholder.find('.js-attributeSelector select').trigger('change') @updateAttributeSelectors(item) ) # remove filter - item.find('.js-remove').bind('click', (e) => + item.on('click', '.js-remove', (e) => return if $(e.currentTarget).hasClass('is-disabled') $(e.target).closest('.js-filterElement').remove() @updateAttributeSelectors(item) ) # change attribute selector - item.find('.js-attributeSelector select').bind('change', (e) => + item.on('change', '.js-attributeSelector select', (e) => elementRow = $(e.target).closest('.js-filterElement') groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value') @rebuildAttributeSelectors(item, elementRow, groupAndAttribute, elements, {}, attribute) @updateAttributeSelectors(item) ) - # build inital params - if !_.isEmpty(params[attribute.name]) - - selectorExists = false - for groupAndAttribute, meta of params[attribute.name] - selectorExists = true - - # get selector rows - elementFirst = item.find('.js-filterElement').first() - elementLast = item.find('.js-filterElement').last() - - # clone, rebuild and append - elementClone = elementFirst.clone(true) - @rebuildAttributeSelectors(item, elementClone, groupAndAttribute, elements, meta, attribute) - elementLast.after(elementClone) + # change operator selector + item.on('change', '.js-operator select', (e) => + elementRow = $(e.target).closest('.js-filterElement') + groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value') + @buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute) + ) - # remove first dummy row - if selectorExists - item.find('.js-filterElement').first().remove() + # build inital params + if _.isEmpty(params[attribute.name]) - else for groupAndAttribute in defaults - # get selector rows - elementFirst = item.find('.js-filterElement').first() - elementLast = item.find('.js-filterElement').last() + # build and append + element = @placeholder(item, attribute, params, groups, elements) + item.append(element) + @rebuildAttributeSelectors(item, element, groupAndAttribute, elements, {}, attribute) - # clone, rebuild and append - elementClone = elementFirst.clone(true) - @rebuildAttributeSelectors(item, elementClone, groupAndAttribute, elements, {}, attribute) + return item - elementLast.after(elementClone) - item.find('.js-filterElement').first().remove() + for groupAndAttribute, meta of params[attribute.name] - # change attribute selector - item.find('.js-attributeSelector select').bind('change', (e) => - elementRow = $(e.target).closest('.js-filterElement') - groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value') - @rebuildAttributeSelectors(item, elementRow, groupAndAttribute, elements, {}, attribute) - @updateAttributeSelectors(item) - ) + # build and append + element = @placeholder(item, attribute, params, groups, elements) + @rebuildAttributeSelectors(item, element, groupAndAttribute, elements, meta, attribute) + item.append(element) + item - # change operator selector - item.on('change', '.js-operator select', (e) => - elementRow = $(e.target).closest('.js-filterElement') - groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value') - @buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute) - ) + @buildAttributeSelector: (elementFull, groups, elements) -> - item + # find first possible attribute + selectedValue = '' + elementFull.find('.js-attributeSelector select option').each(-> + if !selectedValue && !$(@).prop('disabled') + selectedValue = $(@).val() + ) - @buildAttributeSelector: (groups, elements) -> selection = $('') for groupKey, groupMeta of groups displayName = App.i18n.translateInline(groupMeta.name) @@ -141,7 +131,11 @@ class App.UiElement.ticket_perform_action if spacer[0] is groupKey attributeConfig = elements[elementKey] displayName = App.i18n.translateInline(attributeConfig.display) - optgroup.append("") + + selected = '' + if elementKey is selectedValue + selected = 'selected="selected"' + optgroup.append("") selection @updateAttributeSelectors: (elementFull) -> @@ -191,11 +185,11 @@ class App.UiElement.ticket_perform_action selection = $("") attributeConfig = elements[groupAndAttribute] - if !attributeConfig.operator + if !attributeConfig || !attributeConfig.operator elementRow.find('.js-operator').addClass('hide') else elementRow.find('.js-operator').removeClass('hide') - if attributeConfig.operator + if attributeConfig && attributeConfig.operator for operator in attributeConfig.operator operatorName = App.i18n.translateInline(operator) selected = '' diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee index fc202802db2b..d16d113d1e80 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee @@ -256,6 +256,8 @@ class App.TicketCreate extends App.Controller params = template.options else if App.TaskManager.get(@taskKey) && !_.isEmpty(App.TaskManager.get(@taskKey).state) params = App.TaskManager.get(@taskKey).state + params.attachments = App.TaskManager.get(@taskKey).attachments + if !_.isEmpty(params['form_id']) @formId = params['form_id'] @@ -308,6 +310,9 @@ class App.TicketCreate extends App.Controller form_id: @formId model: App.TicketArticle screen: 'create_top' + events: + 'fileUploadStart .richtext': => @submitDisable() + 'fileUploadStop .richtext': => @submitEnable() params: params taskKey: @taskKey ) @@ -503,8 +508,12 @@ class App.TicketCreate extends App.Controller if !confirm(App.i18n.translateContent('You use %s in text but no attachment is attached. Do you want to continue?', matchingWord)) return + # add sidebar params + if @sidebarWidget && @sidebarWidget.postParams + @sidebarWidget.postParams(ticket: ticket) + # disable form - @formDisable(e) + @submitDisable(e) ui = @ ticket.save( done: -> @@ -536,7 +545,7 @@ class App.TicketCreate extends App.Controller fail: (settings, details) -> ui.log 'errors', details - ui.formEnable(e) + ui.submitEnable(e) ui.notify( type: 'error' msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to create object!') @@ -544,6 +553,18 @@ class App.TicketCreate extends App.Controller ) ) + submitDisable: (e) => + if e + @formDisable(e) + return + @formDisable(@$('.js-submit'), 'button') + + submitEnable: (e) => + if e + @formEnable(e) + return + @formEnable(@$('.js-submit'), 'button') + class Router extends App.ControllerPermanent requiredPermission: 'ticket.agent' constructor: (params) -> diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar.coffee index 66f811ee3d9c..550467ebeb9e 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar.coffee @@ -13,6 +13,11 @@ class App.TicketCreateSidebar extends App.Controller if backend && backend.commit backend.commit(args) + postParams: (args) => + for key, backend of @sidebarBackends + if backend && backend.postParams + backend.postParams(args) + render: (params) => if params @params = params diff --git a/app/assets/javascripts/app/controllers/cti.coffee b/app/assets/javascripts/app/controllers/cti.coffee index a637ad7e00ea..5ae318b38480 100644 --- a/app/assets/javascripts/app/controllers/cti.coffee +++ b/app/assets/javascripts/app/controllers/cti.coffee @@ -1,4 +1,7 @@ class App.CTI extends App.Controller + @extend App.PopoverProvidable + @registerPopovers 'User' + elements: '.js-callerLog': 'callerLog' events: @@ -25,6 +28,13 @@ class App.CTI extends App.Controller @delay(delay, 500, 'cti_list_push_render') 'cti_list_push' ) + @bind('cti_event', (data) => + return if data.state isnt 'newCall' + return if data.direction isnt 'in' + return if @switch() isnt true + @notify(data) + 'cti_event' + ) @bind('auth', (data) => @meta.counter = 0 ) @@ -71,6 +81,7 @@ class App.CTI extends App.Controller # render new caller list if data.list @list = data.list + @updateNavMenu() if @renderDone @renderCallerLog() return @@ -145,16 +156,17 @@ class App.CTI extends App.Controller if item.comment item.state_human += ", #{item.comment}" - if item.start && item.end - item.duration = format((Date.parse(item.end) - Date.parse(item.start))/1000) + if item.start_at && item.end_at + item.duration = format((Date.parse(item.end_at) - Date.parse(item.start_at))/1000) diff_in_min = ((Date.now() - Date.parse(item.created_at)) / 1000) / 60 if diff_in_min > 1 item.disabled = false - @userPopupsDestroy() + @removePopovers() @callerLog.html( App.view('cti/caller_log')(list: @list)) - @userPopups() + @renderPopovers() + @updateNavMenu() done: (e) => @@ -198,9 +210,9 @@ class App.CTI extends App.Controller counter: => count = 0 for item in @list - if item.state is 'hangup' && !item.done + if !item.done count++ - @meta.counter + count + @meta.counter = count switch: (state = undefined) => diff --git a/app/assets/javascripts/app/controllers/customer_ticket_create.coffee b/app/assets/javascripts/app/controllers/customer_ticket_create.coffee index d3ab6240d482..245454af6844 100644 --- a/app/assets/javascripts/app/controllers/customer_ticket_create.coffee +++ b/app/assets/javascripts/app/controllers/customer_ticket_create.coffee @@ -60,6 +60,9 @@ class Index extends App.ControllerContent form_id: @form_id model: App.TicketArticle screen: 'create_top' + events: + 'fileUploadStart .richtext': => @submitDisable() + 'fileUploadStop .richtext': => @submitEnable() filter: @formMeta.filter formMeta: @formMeta params: defaults @@ -141,6 +144,7 @@ class Index extends App.ControllerContent sender_id: sender.id form_id: @form_id content_type: 'text/html' + organization_id: params.organization_id } ticket.load(params) @@ -177,7 +181,7 @@ class Index extends App.ControllerContent else # disable form - @formDisable(e) + @submitDisable(e) ui = @ ticket.save( done: -> @@ -187,7 +191,7 @@ class Index extends App.ControllerContent fail: (settings, details) -> ui.log 'errors', details - ui.formEnable(e) + ui.submitEnable(e) ui.notify( type: 'error' msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to create object!') @@ -195,5 +199,17 @@ class Index extends App.ControllerContent ) ) + submitDisable: (e) => + if e + @formDisable(e) + return + @formDisable(@$('.js-submit'), 'button') + + submitEnable: (e) => + if e + @formEnable(e) + return + @formEnable(@$('.js-submit'), 'button') + App.Config.set('customer_ticket_new', Index, 'Routes') App.Config.set('CustomerTicketNew', { prio: 8003, parent: '#new', name: 'New Ticket', translate: true, target: '#customer_ticket_new', permission: ['ticket.customer'], setting: ['customer_ticket_create'], divider: true }, 'NavBarRight') diff --git a/app/assets/javascripts/app/controllers/idoit_object_selector.coffee b/app/assets/javascripts/app/controllers/idoit_object_selector.coffee index 2a7053e5079f..d50519ba68ba 100644 --- a/app/assets/javascripts/app/controllers/idoit_object_selector.coffee +++ b/app/assets/javascripts/app/controllers/idoit_object_selector.coffee @@ -22,11 +22,11 @@ class App.IdoitObjectSelector extends App.ControllerModal @contentInline.find('.js-typeSelect').html(@renderTypeSelector(result)) - @contentInline.filter('.js-search').on('change', 'select, input', (e) => + @contentInline.on('change', 'input.js-shadow', (e) => params = @formParam(e.target) @search(params) ) - @contentInline.filter('.js-search').on('keyup', 'input', (e) => + @contentInline.on('keyup', 'input.js-searchField', (e) => params = @formParam(e.target) @search(params) ) diff --git a/app/assets/javascripts/app/controllers/navigation.coffee b/app/assets/javascripts/app/controllers/navigation.coffee index 55ad1234ceef..d3b0efbe69d7 100644 --- a/app/assets/javascripts/app/controllers/navigation.coffee +++ b/app/assets/javascripts/app/controllers/navigation.coffee @@ -1,4 +1,7 @@ class App.Navigation extends App.ControllerWidgetPermanent + @extend App.PopoverProvidable + @registerAllPopovers() + className: 'navigation vertical' elements: @@ -157,6 +160,7 @@ class App.Navigation extends App.ControllerWidgetPermanent ) renderResult: (result = []) => + @removePopovers() # remove result if not result exists if _.isEmpty(result) @@ -174,14 +178,7 @@ class App.Navigation extends App.ControllerWidgetPermanent # show result list @searchContainer.addClass('open') - # start ticket popups - @ticketPopups() - - # start user popups - @userPopups() - - # start oorganization popups - @organizationPopups() + @renderPopovers() render: -> @@ -206,7 +203,7 @@ class App.Navigation extends App.ControllerWidgetPermanent searchFocus: (e) => @query = '' # reset query cache @searchContainer.addClass('focused') - @anyPopoversDestroy() + App.PopoverProvidable.anyPopoversDestroy() @search() searchBlur: (e) => @@ -288,14 +285,14 @@ class App.Navigation extends App.ControllerWidgetPermanent @searchContainer.removeClass('filled').removeClass('open').removeClass('focused') @globalSearch.close() - # remove not needed popovers - @delay(@anyPopoversDestroy, 100, 'removePopovers') + @delayedRemoveAnyPopover() andClose: => @searchInput.blur() @searchContainer.removeClass('open') @globalSearch.close() - @delay(@anyPopoversDestroy, 100, 'removePopovers') + + @delayedRemoveAnyPopover() search: => query = @searchInput.val().trim() diff --git a/app/assets/javascripts/app/controllers/organization_profile.coffee b/app/assets/javascripts/app/controllers/organization_profile.coffee index d3ed34840b00..b4c5f59e3faa 100644 --- a/app/assets/javascripts/app/controllers/organization_profile.coffee +++ b/app/assets/javascripts/app/controllers/organization_profile.coffee @@ -195,6 +195,7 @@ class Member extends App.ObserverController lastname: true login: true email: true + active: true globalRerender: false render: (user) => diff --git a/app/assets/javascripts/app/controllers/report.coffee b/app/assets/javascripts/app/controllers/report.coffee index c8cc04620de7..51d9704cfb6c 100644 --- a/app/assets/javascripts/app/controllers/report.coffee +++ b/app/assets/javascripts/app/controllers/report.coffee @@ -550,9 +550,8 @@ class Sidebar extends App.Controller @render() render: => - metrics = @config.metric - profiles = App.ReportProfile.all() + profiles = App.ReportProfile.search(filter: { active: true }) @html App.view('report/sidebar')( metrics: metrics params: @params diff --git a/app/assets/javascripts/app/controllers/search.coffee b/app/assets/javascripts/app/controllers/search.coffee index 4c66dcf2f13e..acd4b492fbb9 100644 --- a/app/assets/javascripts/app/controllers/search.coffee +++ b/app/assets/javascripts/app/controllers/search.coffee @@ -1,4 +1,6 @@ class App.Search extends App.Controller + @extend App.PopoverProvidable + elements: '.js-search': 'searchInput' @@ -112,8 +114,7 @@ class App.Search extends App.Controller @updateFilledClass() @updateTask() - # remove not needed popovers - @delay(@anyPopoversDestroy, 100, 'removePopovers') + @delayedRemoveAnyPopover() search: (force = false) => query = @searchInput.val().trim() diff --git a/app/assets/javascripts/app/controllers/test.coffee b/app/assets/javascripts/app/controllers/test.coffee index 64fe57aeb692..c1886ff7926c 100644 --- a/app/assets/javascripts/app/controllers/test.coffee +++ b/app/assets/javascripts/app/controllers/test.coffee @@ -1,9 +1,10 @@ class App.TestController1 extends App.Controller constructor: -> super - @showState = false - @hideState = false - @activeState = false + @showState = false + @hideState = false + @activeState = false + @changedState ||= false @render() meta: -> @@ -21,6 +22,9 @@ class App.TestController1 extends App.Controller @activeState = state @render() + changed: => + @changedState + render: -> @html "
some test controller message:'#{@message}',show:'#{@showState}',hide:'#{@hideState}',active:'#{@activeState}'
" diff --git a/app/assets/javascripts/app/controllers/ticket_overview.coffee b/app/assets/javascripts/app/controllers/ticket_overview.coffee index 420f4e9f8dc5..1cd9cf4a0d54 100644 --- a/app/assets/javascripts/app/controllers/ticket_overview.coffee +++ b/app/assets/javascripts/app/controllers/ticket_overview.coffee @@ -1,3 +1,47 @@ +ValidUsersForTicketSelectionMethods = + validUsersForTicketSelection: -> + items = $('.content.active .table-overview .table').find('[name="bulk"]:checked') + + # we want to display all users for which we can assign the tickets directly + # for this we need to get the groups of all selected tickets + # after we got those we need to check which users are available in all groups + # users that are not in all groups can't get the tickets assigned + ticket_ids = _.map(items, (el) -> $(el).val() ) + ticket_group_ids = _.map(App.Ticket.findAll(ticket_ids), (ticket) -> ticket.group_id) + users = @usersInGroups(ticket_group_ids) + + # get the list of possible groups for the current user + # from the TicketCreateCollection + # (filled for e.g. the TicketCreation or TicketZoom assignment) + # and order them by name + group_ids = _.keys(@formMeta?.dependencies?.group_id) + groups = App.Group.findAll(group_ids) + groups_sorted = _.sortBy(groups, (group) -> group.name) + + # get the number of visible users per group + # from the TicketCreateCollection + # (filled for e.g. the TicketCreation or TicketZoom assignment) + for group in groups + group.valid_users_count = @formMeta?.dependencies?.group_id?[group.id]?.owner_id.length || 0 + + { + users: users + groups: groups_sorted + } + + usersInGroups: (group_ids) -> + ids_by_group = _.chain(@formMeta?.dependencies?.group_id) + .pick(group_ids) + .values() + .map( (e) -> e.owner_id) + .value() + + # Underscore's intersection doesn't work when chained + ids_in_all_groups = _.intersection(ids_by_group...) + + users = App.User.findAll(ids_in_all_groups) + _.sortBy(users, (user) -> user.firstname) + class App.TicketOverview extends App.Controller className: 'overviews' activeFocus: 'nav' @@ -25,6 +69,8 @@ class App.TicketOverview extends App.Controller 'mouseenter .js-batch-hover-target': 'highlightBatchEntry' 'mouseleave .js-batch-hover-target': 'unhighlightBatchEntry' + @include ValidUsersForTicketSelectionMethods + constructor: -> super @batchSupport = @permissionCheck('ticket.agent') @@ -34,6 +80,11 @@ class App.TicketOverview extends App.Controller @bind 'ui:rerender', => @renderBatchOverlay() + load = (data) => + App.Collection.loadAssets(data.assets) + @formMeta = data.form_meta + @bindId = App.TicketCreateCollection.bind(load) + startDragItem: (event) => return if !@batchSupport @grabbedItem = $(event.currentTarget) @@ -413,17 +464,10 @@ class App.TicketOverview extends App.Controller groupId = @hoveredBatchEntry.attr('data-id') group = App.Group.find(groupId) - users = [] - - for user_id in _.uniq(group.user_ids) - if App.User.exists(user_id) - user = App.User.find(user_id) - if user.active is true - users.push user @batchAssignGroupName.text group.displayName() @batchAssignGroupInner.html $(App.view('ticket_overview/batch_overlay_user_group')( - users: users + users: @usersInGroups([groupId]) groups: [] groupId: groupId )) @@ -589,47 +633,17 @@ class App.TicketOverview extends App.Controller @refreshElements() renderOptions: => - macros = App.Macro.findAllByAttribute('active', true) - groups = App.Group.findAllByAttribute('active', true) - users = [] - items = @el.find('[name="bulk"]:checked') - - # find all possible owners for selected tickets - possibleUsers = {} - possibleUserGroups = {} - for item in items - #console.log "selected items with id ", $(item).val() - ticket = App.Ticket.find($(item).val()) - if !possibleUserGroups[ticket.group_id.toString()] - group = App.Group.find(ticket.group_id) - for user_id in group.user_ids - if !possibleUserGroups[ticket.group_id.toString()] - possibleUsers[user_id.toString()] = true - else - hit = false - for user_id, exists of possibleUsers - if possibleUsers[user_id.toString()] - hit = true - if !hit - delete possibleUsers[user_id.toString()] - possibleUserGroups[ticket.group_id.toString()] = true - for user_id, _exists of possibleUsers - if App.User.exists(user_id) - user = App.User.find(user_id) - if user.active is true - users.push user - for group in groups - valid_user_ids = [] - for user_id in _.uniq(group.user_ids) - if App.User.exists(user_id) - if App.User.find(user_id).active is true - valid_user_ids.push user_id - group.valid_user_ids = valid_user_ids + @renderOptionsGroups() + @renderOptionsMacros() + renderOptionsGroups: => @batchAssignInner.html $(App.view('ticket_overview/batch_overlay_user_group')( - users: users - groups: groups + @validUsersForTicketSelection() )) + + renderOptionsMacros: => + macros = App.Macro.search(filter: { active: true }, sortBy:'name', order:'DESC') + @batchMacro.html $(App.view('ticket_overview/batch_overlay_macro')( macros: macros )) @@ -695,9 +709,10 @@ class App.TicketOverview extends App.Controller changed: -> false - release: -> + release: => @keyboardOff() super + App.TicketCreateCollection.unbindById(@bindId) keyboardOn: => $(window).off 'keydown.overview_navigation' @@ -911,6 +926,9 @@ class Navbar extends App.Controller @autoFoldTabs() class Table extends App.Controller + @extend App.PopoverProvidable + @registerPopovers 'Organization', 'User' + events: 'click [data-type=settings]': 'settings' 'click [data-type=viewmode]': 'viewmode' @@ -962,6 +980,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 ) @@ -1078,10 +1097,11 @@ class Table extends App.Controller id: object.organization_id value callbackCheckbox = (id, checked, e) => - if @$('table').find('input[name="bulk"]:checked').length == 0 - @bulkForm.hide() - else + if @shouldShowBulkForm() + @bulkForm.render() @bulkForm.show() + else + @bulkForm.hide() if @lastChecked && e.shiftKey # check items in a row @@ -1127,6 +1147,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' @@ -1156,11 +1177,7 @@ class Table extends App.Controller 'click': callbackCheckbox ) - # start user popups - @userPopups() - - # start organization popups - @organizationPopups() + @renderPopovers() @bulkForm = new BulkForm( holder: @el @@ -1174,11 +1191,11 @@ class Table extends App.Controller # show/hide bulk action @$('.table-overview').delegate('input[name="bulk"], input[name="bulk_all"]', 'change', (e) => - if @$('.table-overview').find('input[name="bulk"]:checked').length == 0 + if @shouldShowBulkForm() + @bulkForm.show() + else @bulkForm.hide() @bulkForm.reset() - else - @bulkForm.show() ) # deselect bulk_all if one item is uncheck observ @@ -1198,6 +1215,17 @@ class Table extends App.Controller bulkAll.prop('indeterminate', true) ) + shouldShowBulkForm: => + items = @$('table').find('input[name="bulk"]:checked') + return false if items.length == 0 + + ticket_ids = _.map(items, (el) -> $(el).val() ) + ticket_group_ids = _.map(App.Ticket.findAll(ticket_ids), (ticket) -> ticket.group_id) + ticket_group_ids = _.uniq(ticket_group_ids) + user_permissions = App.Session.get('group_ids') + group_permissions = ticket_group_ids.map (id) -> user_permissions[id] + _.every(group_permissions, (list) -> 'full' in list || 'change' in list) + viewmode: (e) => e.preventDefault() @view_mode = $(e.target).data('mode') @@ -1224,6 +1252,8 @@ class BulkForm extends App.Controller 'click .js-confirm': 'confirm' 'click .js-cancel': 'reset' + @include ValidUsersForTicketSelectionMethods + constructor: -> super @@ -1257,6 +1287,12 @@ class BulkForm extends App.Controller handlers = @Config.get('TicketZoomFormHandler') + for attribute in @configure_attributes_ticket + continue if attribute.name != 'owner_id' + {users, groups} = @validUsersForTicketSelection() + options = _.map(users, (user) -> {value: user.id, name: user.displayName()} ) + attribute.possible_groups_owners = options + new App.ControllerForm( el: @$('#form-ticket-bulk') model: @@ -1522,7 +1558,7 @@ class App.OverviewSettings extends App.ControllerModal }, { name: 'order::direction' - display: 'Direction' + display: 'Order by Direction' tag: 'select' default: @overview.order.direction null: false @@ -1540,7 +1576,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 } @@ -1565,6 +1612,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/controllers/ticket_zoom.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.coffee index ccd5c1e0a5d8..28f86a5be9f5 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.coffee @@ -1,4 +1,6 @@ class App.TicketZoom extends App.Controller + @include App.TicketNavigable + elements: '.main': 'main' '.ticketZoom': 'ticketZoom' @@ -291,6 +293,7 @@ class App.TicketZoom extends App.Controller hide: => @activeState = false + $('body > .modal').modal('hide') @positionPageHeaderStop() @autosaveStop() @shortcutNavigationstop() @@ -383,6 +386,7 @@ class App.TicketZoom extends App.Controller # if page header is not possible to use - mainScrollHeigth to low - hide page header if not mainScrollHeigth > mainHeigth + headerHeight @scrollPageHeader.css('transform', "translateY(#{-headerHeight}px)") + return if scroll > headerHeight scroll = headerHeight @@ -439,17 +443,19 @@ class App.TicketZoom extends App.Controller #if @shown # @attributeBar.start() - @form_id = App.ControllerForm.formId() + @form_id = @taskGet('article').form_id || App.ControllerForm.formId() @articleNew = new App.TicketZoomArticleNew( - ticket: @ticket - ticket_id: @ticket_id - el: elLocal.find('.article-new') - formMeta: @formMeta - form_id: @form_id - defaults: @taskGet('article') - taskKey: @taskKey - ui: @ + ticket: @ticket + ticket_id: @ticket_id + el: elLocal.find('.article-new') + formMeta: @formMeta + form_id: @form_id + defaults: @taskGet('article') + taskKey: @taskKey + ui: @ + callbackFileUploadStart: @submitDisable + callbackFileUploadStop: @submitEnable ) @highligher = new App.TicketZoomHighlighter( @@ -605,6 +611,7 @@ class App.TicketZoom extends App.Controller body: '' internal: internal in_reply_to: '' + subtype: '' if @permissionCheck('ticket.customer') currentStore.article.internal = '' @@ -633,6 +640,11 @@ class App.TicketZoom extends App.Controller # and the default is was not set before return if @isDefaultFollowUpStateSet + # and only if ticket is not in "new" state + if @ticket && @ticket.state_id + state = App.TicketState.findByAttribute('id', @ticket.state_id) + return if state && state.default_create is true + # prevent multiple changes for the default follow up state @isDefaultFollowUpStateSet = true @@ -660,7 +672,6 @@ class App.TicketZoom extends App.Controller else delete currentParams.article.attachments - # remove not needed attributes delete currentParams.article.form_id if @permissionCheck('ticket.customer') @@ -730,16 +741,28 @@ class App.TicketZoom extends App.Controller resetButton.removeClass('hide') + submitDisable: (e) => + if e + @formDisable(e) + return + @formDisable(@$('.js-submitDropdown')) + + submitEnable: (e) => + if e + @formEnable(e) + return + @formEnable(@$('.js-submitDropdown')) + submit: (e, macro = {}) => e.stopPropagation() e.preventDefault() # disable form - @formDisable(e) + @submitDisable(e) # validate new article if !@articleNew.validate() - @formEnable(e) + @submitEnable(e) return ticketParams = @formParam(@$('.edit')) @@ -757,19 +780,20 @@ class App.TicketZoom extends App.Controller for key, value of ticketParams ticket[key] = value - App.Ticket.macro( - macro: macro - ticket: ticket - callback: - tagAdd: (tag) => - return if !@sidebarWidget - return if !@sidebarWidget.reload - @sidebarWidget.reload(tagAdd: tag, source: 'macro') - tagRemove: (tag) => - return if !@sidebarWidget - return if !@sidebarWidget.reload - @sidebarWidget.reload(tagRemove: tag) - ) + if macro.perform + App.Ticket.macro( + macro: macro.perform + ticket: ticket + callback: + tagAdd: (tag) => + return if !@sidebarWidget + return if !@sidebarWidget.reload + @sidebarWidget.reload(tagAdd: tag, source: 'macro') + tagRemove: (tag) => + return if !@sidebarWidget + return if !@sidebarWidget.reload + @sidebarWidget.reload(tagRemove: tag) + ) # set defaults if !@permissionCheck('ticket.customer') @@ -794,7 +818,7 @@ class App.TicketZoom extends App.Controller errors: errors screen: 'edit' ) - @formEnable(e) + @submitEnable(e) @autosaveStart() return @@ -810,47 +834,53 @@ class App.TicketZoom extends App.Controller errors: errors screen: 'edit' ) - @formEnable(e) + @submitEnable(e) @autosaveStart() return ticket.article = article + # add sidebar params + if @sidebarWidget && @sidebarWidget.postParams + @sidebarWidget.postParams(ticket: ticket) + if !ticket.article - @submitPost(e, ticket) + @submitPost(e, ticket, macro) return # verify if time accounting is enabled if @Config.get('time_accounting') isnt true - @submitPost(e, ticket) + @submitPost(e, ticket, macro) return # verify if time accounting is active for ticket time_accounting_selector = @Config.get('time_accounting_selector') if !App.Ticket.selector(ticket, time_accounting_selector['condition']) - @submitPost(e, ticket) + @submitPost(e, ticket, macro) return # time tracking if @permissionCheck('ticket.customer') - @submitPost(e, ticket) + @submitPost(e, ticket, macro) return new App.TicketZoomTimeAccounting( container: @el.closest('.content') ticket: ticket cancelCallback: => - @formEnable(e) + @submitEnable(e) submitCallback: (params) => if params.time_unit ticket.article.time_unit = params.time_unit - @submitPost(e, ticket) + @submitPost(e, ticket, macro) ) - submitPost: (e, ticket) => - + submitPost: (e, ticket, macro) => taskAction = @$('.js-secondaryActionButtonLabel').data('type') + if macro && macro.ux_flow_next_up + taskAction = macro.ux_flow_next_up + # submit changes @ajax( id: "ticket_update_#{ticket.id}" @@ -872,59 +902,33 @@ class App.TicketZoom extends App.Controller if @sidebarWidget @sidebarWidget.commit() - if taskAction is 'closeNextInOverview' - if @overview_id - current_position = 0 - overview = App.Overview.find(@overview_id) - list = App.OverviewListCollection.get(overview.link) - for ticket in list.tickets - current_position += 1 - if ticket.id is @ticket_id - next = list.tickets[current_position] - if next - # close task - App.TaskManager.remove(@taskKey) - - # open task via task manager to get overview information - App.TaskManager.execute( - key: 'Ticket-' + next.id - controller: 'TicketZoom' - params: - ticket_id: next.id - overview_id: @overview_id - show: true - ) - @navigate "ticket/zoom/#{next.id}" - return - - # fallback, close task - taskAction = 'closeTab' - - if taskAction is 'closeTab' - App.TaskManager.remove(@taskKey) - nextTaskUrl = App.TaskManager.nextTaskUrl() - if nextTaskUrl - @navigate nextTaskUrl - return - - @navigate '#' + if taskAction is 'closeNextInOverview' || taskAction is 'next_from_overview' + App.Event.trigger('overview:fetch') + @taskOpenNextTicketInOverview() + return + + if taskAction is 'closeTab' || taskAction is 'next_task' + App.Event.trigger('overview:fetch') + @taskCloseTicket(true) return @autosaveStart() @muteTask() - @formEnable(e) - App.Event.trigger('overview:fetch') + @submitEnable(e) error: (settings, details) => + error = undefined + if settings && settings.responseJSON && settings.responseJSON.error + error = settings.responseJSON.error App.Event.trigger 'notify', { type: 'error' - msg: App.i18n.translateContent(details.error_human || details.error || settings.responseJSON.error || 'Unable to update!') + msg: App.i18n.translateContent(details.error_human || details.error || error || 'Unable to update!') timeout: 2000 } @autosaveStart() @muteTask() @fetch() - @formEnable(e) + @submitEnable(e) ) bookmark: (e) -> @@ -960,6 +964,10 @@ class App.TicketZoom extends App.Controller taskGet: (area) => return {} if !App.TaskManager.get(@taskKey) @localTaskData = App.TaskManager.get(@taskKey).state || {} + + if _.isObject(@localTaskData.article) && _.isArray(App.TaskManager.get(@taskKey).attachments) + @localTaskData.article['attachments'] = App.TaskManager.get(@taskKey).attachments + if area if !@localTaskData[area] @localTaskData[area] = {} @@ -974,10 +982,15 @@ class App.TicketZoom extends App.Controller taskUpdateAll: (data) => @localTaskData = data + @localTaskData.article['form_id'] = @form_id App.TaskManager.update(@taskKey, { 'state': @localTaskData }) # reset task state taskReset: => + @form_id = App.ControllerForm.formId() + @articleNew.form_id = @form_id + @articleNew.render() + @localTaskData = ticket: {} article: {} diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee index 204dbd7d41b1..700fa08ce42d 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee @@ -30,11 +30,12 @@ class EmailReply extends App.Controller # remove system addresses localAddresses = App.EmailAddress.all() - forgeinRecipients = [] + foreignRecipients = [] recipientUsed = {} for recipient in recipients if !_.isEmpty(recipient.address) localRecipientAddress = recipient.address.toString().toLowerCase() + if !recipientUsed[localRecipientAddress] recipientUsed[localRecipientAddress] = true localAddress = false @@ -43,10 +44,10 @@ class EmailReply extends App.Controller recipientUsed[localRecipientAddress] = true localAddress = true if !localAddress - forgeinRecipients.push recipient + foreignRecipients.push recipient # check if reply all is neede - if forgeinRecipients.length > 1 + if foreignRecipients.length > 1 actions.push { name: 'reply all' type: 'emailReplyAll' @@ -148,7 +149,11 @@ class EmailReply extends App.Controller selected = App.Utils.text2html(selected) if selected - selected = "


#{selected}

" + date = @date_format(article.created_at) + name = article.updated_by.displayName() + email = article.updated_by.email + quote_header = App.i18n.translateInline('On %s, %s wrote:', date, name) + selected = "


#{quote_header}

#{selected}

" # add selected text to body body = selected + body @@ -157,6 +162,8 @@ class EmailReply extends App.Controller type = App.TicketArticleType.findByAttribute(name:'email') + articleNew.subtype = 'reply' + App.Event.trigger('ui::ticket::setArticleType', { ticket: ticket type: type @@ -166,6 +173,15 @@ class EmailReply extends App.Controller true + @date_format: (date_string) -> + options = { + weekday: 'long' + month: 'long' + day: 'numeric' + year: 'numeric' + } + new Date(date_string).toLocaleTimeString('en-US', options) + @emailForward: (ticket, article, ui) -> ui.scrollToCompose() @@ -187,12 +203,14 @@ class EmailReply extends App.Controller if ui.Config.get('ui_ticket_zoom_article_email_subject') if _.isEmpty(article.subject) - articleNew.subject = "FW: #{ticket.title}" + articleNew.subject = ticket.title else - articleNew.subject = "FW: #{article.subject}" + articleNew.subject = article.subject type = App.TicketArticleType.findByAttribute(name:'email') + articleNew.subtype = 'forward' + App.Event.trigger('ui::ticket::setArticleType', { ticket: ticket type: type @@ -278,6 +296,7 @@ class EmailReply extends App.Controller App.Utils.htmlStrip(signature) if signaturePosition is 'top' body.prepend(signature) + body.prepend('
') else body.append(signature) ui.$('[data-name=body]').replaceWith(body) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee index a8138a5d3fb1..d24dc366df90 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee @@ -47,6 +47,10 @@ class App.TicketZoomArticleNew extends App.Controller if @defaults.body or @isIE10() @openTextarea(null, true) + if _.isArray(@defaults.attachments) + for attachment in @defaults.attachments + @renderAttachment(attachment) + # set article type and expand text area @bind('ui::ticket::setArticleType', (data) => return if data.ticket.id.toString() isnt @ticket_id.toString() @@ -175,7 +179,7 @@ class App.TicketZoomArticleNew extends App.Controller key: 'File' data: form_id: @form_id - maxSimultaneousUploads: 1, + maxSimultaneousUploads: 1 onFileAdded: (file) => file.on( @@ -185,11 +189,17 @@ class App.TicketZoomArticleNew extends App.Controller @attachmentUpload.removeClass('hide') @cancelContainer.removeClass('hide') + if @callbackFileUploadStart + @callbackFileUploadStart() + onAborted: => @attachmentPlaceholder.removeClass('hide') @attachmentUpload.addClass('hide') @$('.article-attachment input').val('') + if @callbackFileUploadStop + @callbackFileUploadStop() + # Called after received response from the server onCompleted: (response) => @@ -206,6 +216,9 @@ class App.TicketZoomArticleNew extends App.Controller @renderAttachment(response.data) @$('.article-attachment input').val('') + if @callbackFileUploadStop + @callbackFileUploadStop() + # Called during upload progress, first parameter # is decimal value from 0 to 100. onProgress: (progress, fileSize, uploadedBytes) => diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee index e281199726df..7c1f39ca3cb4 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee @@ -148,13 +148,13 @@ class ArticleViewItem extends App.ObserverController body.splice(article.preferences.signature_detection, 0, signatureDetected) body = body.join('
') else - body = App.Utils.signatureIdentify(body) + body = App.Utils.signatureIdentifyByHtml(body) article['html'] = body else # client signature detection bodyHtml = App.Utils.text2html(article.body) - article['html'] = App.Utils.signatureIdentify(bodyHtml) + article['html'] = App.Utils.signatureIdentifyByPlaintext(bodyHtml) # if no signature detected or within frist 25 lines, check if signature got detected in backend if article['html'] is bodyHtml || (article.preferences && article.preferences.signature_detection < 25) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee index c147c47bf2a7..79e9a79a3911 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee @@ -1,13 +1,11 @@ class App.TicketZoomAttributeBar extends App.Controller - @include App.TicketNavigable - elements: '.js-submitDropdown': 'buttonDropdown' '.js-reset': 'resetButton' events: 'mousedown .js-openDropdownMacro': 'toggleMacroMenu' - 'click .js-openDropdownMacro': 'stopPropagation' + 'click .js-openDropdownMacro': 'preventDefaultAndstopPropagation' 'mouseup .js-dropdownActionMacro': 'performTicketMacro' 'mouseenter .js-dropdownActionMacro': 'onActionMacroMouseEnter' 'mouseleave .js-dropdownActionMacro': 'onActionMacroMouseLeave' @@ -72,7 +70,10 @@ class App.TicketZoomAttributeBar extends App.Controller @render() toggleMacroMenu: => - if @buttonDropdown.hasClass('is-open') then @closeMacroMenu() else @openMacroMenu() + if @buttonDropdown.hasClass('is-open') + @closeMacroMenu() + return + @openMacroMenu() openMacroMenu: => @buttonDropdown.addClass 'is-open' @@ -86,19 +87,8 @@ class App.TicketZoomAttributeBar extends App.Controller macroId = $(e.currentTarget).data('id') macro = App.Macro.find(macroId) - @callback(e, macro.perform) + @callback(e, macro) @closeMacroMenu() - @replaceTabWith(macro.ux_flow_next_up) - - replaceTabWith: (dest) => - switch dest - when 'none' - return - when 'next_task' - @closeTab() - when 'next_from_overview' - @closeTab() - @openNextTicketInOverview() onActionMacroMouseEnter: (e) => @$(e.currentTarget).addClass('is-active') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/overview_navigator.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/overview_navigator.coffee index 094bb62b357e..18615eb5b6e2 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/overview_navigator.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/overview_navigator.coffee @@ -70,4 +70,4 @@ class App.TicketZoomOverviewNavigator extends App.Controller else return - @openTicket(id, url) + @taskOpenTicket(id, url) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/owner_handler_dependencies.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/owner_handler_dependencies.coffee new file mode 100644 index 000000000000..74a853876268 --- /dev/null +++ b/app/assets/javascripts/app/controllers/ticket_zoom/owner_handler_dependencies.coffee @@ -0,0 +1,26 @@ +class OwnerFormHandlerDependencies + + # central method, is getting called on every ticket form change + @run: (params, attribute, attributes, classname, form, ui) -> + return if 'group_id' not of params + return if 'owner_id' not of params + + owner_attribute = _.find(attributes, (o) -> o.name == 'owner_id') + return if !owner_attribute + return if 'possible_groups_owners' not of owner_attribute + + # fetch contents using User relation if a Group has been selected, otherwise render possible_groups_owners + if params.group_id + owner_attribute.relation = 'User' + delete owner_attribute['options'] + else + owner_attribute.options = owner_attribute.possible_groups_owners + delete owner_attribute['relation'] + + # replace new option list + owner_attribute.default = params[owner_attribute.name] + owner_attribute.newValue = params[owner_attribute.name] + newElement = ui.formGenItem(owner_attribute, classname, form) + form.find('select[name="owner_id"]').closest('.form-group').replaceWith(newElement) + +App.Config.set('150-ticketFormChanges', OwnerFormHandlerDependencies, 'TicketZoomFormHandler') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee index 78491dbdb5e9..4ebb1ab8dc1b 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee @@ -14,6 +14,11 @@ class App.TicketZoomSidebar extends App.ObserverController if backend && backend.commit backend.commit(args) + postParams: (args) => + for key, backend of @sidebarBackends + if backend && backend.postParams + backend.postParams(args) + render: (ticket) => @sidebarBackends ||= {} @sidebarItems = [] diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_idoit.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_idoit.coffee index cdeb42e46125..9f3da418c397 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_idoit.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_idoit.coffee @@ -92,6 +92,9 @@ class SidebarIdoit extends App.Controller showError: (message) => @html App.i18n.translateInline(message) + reload: => + @showObjectsContent() + delete: (objectId) => localObjects = [] for localObjectId in @objectIds @@ -102,13 +105,14 @@ class SidebarIdoit extends App.Controller @updateTicket(@ticket.id, @objectIds) @showObjectsContent() - commit: (args) => - return if @ticket && @ticket.id + postParams: (args) => + return if !args.ticket + return if args.ticket.created_at return if !@objectIds return if _.isEmpty(@objectIds) - return if !args - return if !args.ticket_id - @updateTicket(args.ticket_id, @objectIds) + args.ticket.preferences ||= {} + args.ticket.preferences.idoit ||= {} + args.ticket.preferences.idoit.object_ids = @objectIds updateTicket: (ticket_id, objectIds, callback) => App.Ajax.request( diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/time_accounting.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/time_accounting.coffee index 8c4c01546ae5..49e3bf379e4d 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/time_accounting.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/time_accounting.coffee @@ -29,6 +29,10 @@ class App.TicketZoomTimeAccounting extends App.ControllerModal return if !@cancelCallback @cancelCallback() + onClose: -> + return if !@cancelCallback + @cancelCallback() + onSubmit: => @$('[name="time_unit"]').removeClass('has-error') params = @formParams() diff --git a/app/assets/javascripts/app/controllers/translation.coffee b/app/assets/javascripts/app/controllers/translation.coffee index a1a70da11cf0..9745c597048c 100644 --- a/app/assets/javascripts/app/controllers/translation.coffee +++ b/app/assets/javascripts/app/controllers/translation.coffee @@ -79,6 +79,14 @@ class Index extends App.ControllerSubContent @toggleAction() ) + show: => + # see https://github.com/zammad/zammad/issues/2056 + @untranslatedAtLastRender ||= $.extend({}, App.i18n.getNotTranslated(@locale)) + return if _.isEqual(@untranslatedAtLastRender, App.i18n.getNotTranslated(@locale)) + + @untranslatedAtLastRender = $.extend({}, App.i18n.getNotTranslated(@locale)) + App.Event.trigger('ui:rerender') + hide: => @rerender() diff --git a/app/assets/javascripts/app/controllers/user_profile.coffee b/app/assets/javascripts/app/controllers/user_profile.coffee index 3a2614c72eb8..e22fa9618ca9 100644 --- a/app/assets/javascripts/app/controllers/user_profile.coffee +++ b/app/assets/javascripts/app/controllers/user_profile.coffee @@ -52,6 +52,13 @@ class App.UserProfile extends App.Controller el: elLocal.find('.js-organization') ) + if user.organization_ids + for id in user.organization_ids + a = new Organization( + object_id: id + el: elLocal.find('.js-organization-' + id) + ) + new Object( el: elLocal.find('.js-object-container') object_id: user.id diff --git a/app/assets/javascripts/app/controllers/users.coffee b/app/assets/javascripts/app/controllers/users.coffee index 32a4d4db9366..2aca442d067e 100644 --- a/app/assets/javascripts/app/controllers/users.coffee +++ b/app/assets/javascripts/app/controllers/users.coffee @@ -27,23 +27,20 @@ class Index extends App.ControllerSubContent e.preventDefault() $(e.target).toggleClass('active') query = @searchInput.val().trim() - if query - @delay( @search, 220, 'search' ) - return - @recent() + @query = query + @delay(@search, 220, 'search') ) # start search @searchInput.bind( 'keyup', (e) => query = @searchInput.val().trim() - return if !query return if query is @query @query = query - @delay( @search, 220, 'search' ) + @delay(@search, 220, 'search') ) # show last 20 users - @recent() + @search() renderResult: (user_ids = []) -> @stopLoading() @@ -143,40 +140,18 @@ class Index extends App.ControllerSubContent App.Ajax.request( id: 'search' type: 'GET' - url: "#{@apiPath}/users/search" + url: "#{@apiPath}/users/search?sort_by=created_at" data: - query: @query - limit: 140 + query: @query || '*' + limit: 50 role_ids: role_ids full: true processData: true, - success: (data, status, xhr) => - App.Collection.loadAssets(data.assets) - @renderResult(data.user_ids) - done: => - @stopLoading() - ) - - recent: => - role_ids = [] - @$('.tab.active').each( (i,d) -> - role_ids.push $(d).data('id') - ) - @startLoading(@$('.table-overview')) - App.Ajax.request( - id: 'search' - type: 'GET' - url: "#{@apiPath}/users/recent" - data: - limit: 50 - role_ids: role_ids - full: true - processData: true success: (data, status, xhr) => App.Collection.loadAssets(data.assets) @renderResult(data.user_ids) @stopLoading() - error: => + done: => @stopLoading() ) @@ -191,7 +166,7 @@ class Index extends App.ControllerSubContent navupdate: '#users' genericObject: 'User' container: @el.closest('.content') - callback: @recent + callback: @search ) import: (e) -> diff --git a/app/assets/javascripts/app/controllers/widget/avatar.coffee b/app/assets/javascripts/app/controllers/widget/avatar.coffee index e6bc84d35543..9dae76b9a2f9 100644 --- a/app/assets/javascripts/app/controllers/widget/avatar.coffee +++ b/app/assets/javascripts/app/controllers/widget/avatar.coffee @@ -1,4 +1,7 @@ class App.WidgetAvatar extends App.ObserverController + @extend App.PopoverProvidable + @registerPopovers 'User' + model: 'User' observe: login: true @@ -17,4 +20,4 @@ class App.WidgetAvatar extends App.ObserverController render: (user) => @html(user.avatar(@size, @position, @cssClass, false, false, @type)) - @userPopups(@position) + @renderPopovers() diff --git a/app/assets/javascripts/app/controllers/widget/link.coffee b/app/assets/javascripts/app/controllers/widget/link.coffee index 20dab6a91dd2..7af8f6d6dc0a 100644 --- a/app/assets/javascripts/app/controllers/widget/link.coffee +++ b/app/assets/javascripts/app/controllers/widget/link.coffee @@ -1,4 +1,10 @@ class App.WidgetLink extends App.Controller + @extend App.PopoverProvidable + @registerPopovers 'Ticket' + + @popoversDefaults: + position: 'left' + events: 'click .js-add': 'add' 'click .js-delete': 'delete' @@ -54,7 +60,8 @@ class App.WidgetLink extends App.Controller @html App.view('link/info')( links: list ) - @ticketPopups('left') + + @renderPopovers() delete: (e) => e.preventDefault() diff --git a/app/assets/javascripts/app/controllers/widget/organization.coffee b/app/assets/javascripts/app/controllers/widget/organization.coffee index 64e63a44bc4c..77ba6b3ad4e2 100644 --- a/app/assets/javascripts/app/controllers/widget/organization.coffee +++ b/app/assets/javascripts/app/controllers/widget/organization.coffee @@ -1,4 +1,7 @@ class App.WidgetOrganization extends App.Controller + @extend App.PopoverProvidable + @registerPopovers 'User' + events: 'focusout [contenteditable]': 'update' @@ -26,10 +29,10 @@ class App.WidgetOrganization extends App.Controller name = nameNew # add to show if value exists - if ( organization[name] || attributeConfig.tag is 'richtext' ) && attributeConfig.shown + if ( organization[name]? || attributeConfig.tag is 'richtext' ) && attributeConfig.shown # do not show firstname and lastname / already show via diplayName() - if name isnt 'name' + if name isnt 'name' && organization[name] isnt '' organizationData.push attributeConfig # insert userData @@ -44,16 +47,7 @@ class App.WidgetOrganization extends App.Controller maxlength: 250 ) - # enable user popups - @userPopups() - - ### - @userTicketPopups( - selector: '.user-tickets' - user_id: user.id - position: 'right' - ) - ### + @renderPopovers() update: (e) => name = $(e.target).attr('data-name') diff --git a/app/assets/javascripts/app/controllers/widget/ticket_list.coffee b/app/assets/javascripts/app/controllers/widget/ticket_list.coffee index 9ab9ef4c141d..d4a99405952c 100644 --- a/app/assets/javascripts/app/controllers/widget/ticket_list.coffee +++ b/app/assets/javascripts/app/controllers/widget/ticket_list.coffee @@ -1,4 +1,7 @@ class App.TicketList extends App.Controller + @extend App.PopoverProvidable + @registerPopovers 'Organization', 'User' + constructor: -> super @@ -85,8 +88,4 @@ class App.TicketList extends App.Controller radio: @radio ) - # start user popups - @userPopups() - - # start organization popups - @organizationPopups() + @renderPopovers() diff --git a/app/assets/javascripts/app/controllers/widget/ticket_stats.coffee b/app/assets/javascripts/app/controllers/widget/ticket_stats.coffee index 67d8b4b5b6bf..44c1d27267c0 100644 --- a/app/assets/javascripts/app/controllers/widget/ticket_stats.coffee +++ b/app/assets/javascripts/app/controllers/widget/ticket_stats.coffee @@ -138,6 +138,9 @@ class App.TicketStats extends App.Controller ) class App.TicketStatsList extends App.Controller + @extend App.PopoverProvidable + @registerPopovers 'Ticket' + events: 'click .js-showAll': 'showAll' @@ -166,7 +169,7 @@ class App.TicketStatsList extends App.Controller limit: @limit ) - @ticketPopups() + @renderPopovers() showAll: (e) => e.preventDefault() diff --git a/app/assets/javascripts/app/controllers/widget/user.coffee b/app/assets/javascripts/app/controllers/widget/user.coffee index d7836e23af94..07631843ca98 100644 --- a/app/assets/javascripts/app/controllers/widget/user.coffee +++ b/app/assets/javascripts/app/controllers/widget/user.coffee @@ -1,4 +1,7 @@ class App.WidgetUser extends App.Controller + @extend App.PopoverProvidable + @registerPopovers 'UserTicket' + events: 'focusout [contenteditable]': 'update' @@ -28,10 +31,10 @@ class App.WidgetUser extends App.Controller name = nameNew # add to show if value exists - if ( user[name] || attributeConfig.tag is 'richtext' ) && attributeConfig.shown + if ( user[name]? || attributeConfig.tag is 'richtext' ) && attributeConfig.shown # do not show firstname and lastname / already show via displayName() - if name isnt 'firstname' && name isnt 'lastname' && name isnt 'organization' + if name isnt 'firstname' && name isnt 'lastname' && name isnt 'organization' && user[name] isnt '' userData.push attributeConfig if user.preferences @@ -76,10 +79,9 @@ class App.WidgetUser extends App.Controller maxlength: 250 ) - @userTicketPopups( - selector: '.user-tickets' + @renderPopovers( + selector: '.user-tickets', user_id: user.id - position: 'right' ) update: (e) => diff --git a/app/assets/javascripts/app/index.coffee b/app/assets/javascripts/app/index.coffee index 4c2852680f8e..64e60859c222 100644 --- a/app/assets/javascripts/app/index.coffee +++ b/app/assets/javascripts/app/index.coffee @@ -85,6 +85,10 @@ class App extends Spine.Controller S: (key) -> App.Session.get(key) + # define view helper for rendering partial views + V: (name, params) -> + App.view(name)(params) + # define address line helper AddressLine: (line) -> return '' if !line diff --git a/app/assets/javascripts/app/lib/app_post/i18n.coffee b/app/assets/javascripts/app/lib/app_post/i18n.coffee index dbfbb72c1d03..b8c385409b26 100644 --- a/app/assets/javascripts/app/lib/app_post/i18n.coffee +++ b/app/assets/javascripts/app/lib/app_post/i18n.coffee @@ -5,6 +5,11 @@ class App.i18n @init: (args) -> _instance ?= new _i18nSingleton(args) + @translateDeep: (input, args...) -> + if _instance == undefined + _instance ?= new _i18nSingleton() + _instance.translateDeep(input, args) + @translateContent: (string, args...) -> if _instance == undefined _instance ?= new _i18nSingleton() @@ -203,6 +208,19 @@ class _i18nSingleton extends Spine.Module return string if !string @translate(string, args, true) + translateDeep: (input, args) => + if _.isArray(input) + _.map input, (item) => + @translateDeep(item, args) + else if _.isObject(input) + _.reduce _.keys(input), (memo, item) => + memo[item] = @translateDeep(input[item]) + memo + , {} + else + @translateInline(input, args) + + translateContent: (string, args) => return string if !string diff --git a/app/assets/javascripts/app/lib/app_post/popover_provider/_popover_provider.coffee b/app/assets/javascripts/app/lib/app_post/popover_provider/_popover_provider.coffee new file mode 100644 index 000000000000..75def93aee02 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/popover_provider/_popover_provider.coffee @@ -0,0 +1,77 @@ +class App.PopoverProvider + @selectorCssClassPrefix = null # needs to be overrided + @templateName = null # needs to be overrided + @permission = 'ticket.agent' + + @providers = {} + + @providersConfigKey = 'PopoverProviders' + + @registerProvider: (key, klass) -> + @providers[key] = klass + App.Config.set(key, klass, @providersConfigKey) + + @defaults = + position: 'right' + parentController: null + popovers = null + + constructor: (params) -> + if params.parentController is null + throw 'Parent controller needs to be set' + + @params = _.extend {}, @constructor.defaults, params + + build: (buildParams) -> + return if !@checkPermissions() + @clear(@popovers) + @bind() + @popovers = @buildPopovers() + + checkPermissions: -> + @params.parentController.permissionCheck(@constructor.permission) + + cssClass: -> + "#{@constructor.selectorCssClassPrefix}-popover" + + bind: -> + + buildPopovers: (supplementaryData = {}) -> + context = @ + + selector = supplementaryData.selector || ".#{@cssClass()}" + + @params.parentController.el.find(selector).popover( + trigger: 'hover' + container: 'body' + html: true + animation: false + delay: 100 + placement: "auto #{@params.position}" + title: -> + context.buildTitleFor(@, supplementaryData) + content: -> + context.buildContentFor(@, supplementaryData) + ) + + clear: -> + return if !@popovers + @popovers.popover('destroy') + + buildTitleFor: (elem) -> + 'title' + + buildContentFor: (elem) -> + 'content' + + buildHtmlContent: (params) -> + html = $(App.view("popover/#{@constructor.templateName}")(params)) + + html.find('.humanTimeFromNow').each => + @params.parentController.frontendTimeUpdateItem($(@)) + + html + + displayTitleUsing: (object) -> + throw 'please override' + diff --git a/app/assets/javascripts/app/lib/app_post/popover_provider/_single_object_popover_provider.coffee b/app/assets/javascripts/app/lib/app_post/popover_provider/_single_object_popover_provider.coffee new file mode 100644 index 000000000000..70caa45b1811 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/popover_provider/_single_object_popover_provider.coffee @@ -0,0 +1,45 @@ +class App.SingleObjectPopoverProvider extends App.PopoverProvider + @klass = null # needs to be overrided + @ignoredAttributes = [] + @includeData = true + @templateName = 'single_object_generic' + + fullCssSelector: -> + "div.#{@cssClass()}, span.#{@cssClass()}" + + bind: -> + @params.parentController.$(@fullCssSelector()).bind('click', (e) => + id = @objectIdFor(e.target) + return if !id + object = @constructor.klass.find(id) + @params.parentController.navigate object.uiUrl() + ) + + objectIdFor: (elem) -> + $(elem).data('id') + + buildTitleFor: (elem) -> + object = @constructor.klass.find(@objectIdFor(elem)) + App.Utils.htmlEscape(@displayTitleUsing(object)) + + buildContentFor: (elem) -> + id = @objectIdFor(elem) + object = @constructor.klass.fullLocal(id) + + # get display data + data = _.values(@constructor.klass.attributesGet('view')) + .filter (attr) -> + # check if value for _id exists + name = attr.name + nameNew = name.substr(0, name.length - 3) + if nameNew of object + name = nameNew + + # add to show if value exists + # do not show ignroed attributes + object[name] && attr.shown && !_.include(@constructor.ignoredAttributes, name) + + @buildHtmlContent( + object: object + attributes: data + ) diff --git a/app/assets/javascripts/app/lib/app_post/popover_provider/organization_popover_provider.coffee b/app/assets/javascripts/app/lib/app_post/popover_provider/organization_popover_provider.coffee new file mode 100644 index 000000000000..7e275de1f9d2 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/popover_provider/organization_popover_provider.coffee @@ -0,0 +1,10 @@ +class Organization extends App.SingleObjectPopoverProvider + @klass = App.Organization + @selectorCssClassPrefix = 'organization' + @templateName = 'organization' + @ignoredAttributes = ['name'] + + displayTitleUsing: (object) -> + object.name + +App.PopoverProvider.registerProvider('Organization', Organization) diff --git a/app/assets/javascripts/app/lib/app_post/popover_provider/ticket_popover_provider.coffee b/app/assets/javascripts/app/lib/app_post/popover_provider/ticket_popover_provider.coffee new file mode 100644 index 000000000000..50160b4a75d0 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/popover_provider/ticket_popover_provider.coffee @@ -0,0 +1,10 @@ +class Ticket extends App.SingleObjectPopoverProvider + @klass = App.Ticket + @selectorCssClassPrefix = 'ticket' + @templateName = 'ticket' + @includeData = false + + displayTitleUsing: (object) -> + object.title + +App.PopoverProvider.registerProvider('Ticket', Ticket) diff --git a/app/assets/javascripts/app/lib/app_post/popover_provider/user_popover_provider.coffee b/app/assets/javascripts/app/lib/app_post/popover_provider/user_popover_provider.coffee new file mode 100644 index 000000000000..7a608c0f3e4a --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/popover_provider/user_popover_provider.coffee @@ -0,0 +1,13 @@ +class User extends App.SingleObjectPopoverProvider + @klass = App.User + @selectorCssClassPrefix = 'user' + @templateName = 'user' + @ignoredAttributes = ['firstname', 'lastname', 'organization'] + + displayTitleUsing: (object) -> + output = object.displayName() + if object.isOutOfOffice() + output += " (#{object.outOfOfficeText()})" + output + +App.PopoverProvider.registerProvider('User', User) diff --git a/app/assets/javascripts/app/lib/app_post/popover_provider/user_ticket_popover_provider.coffee b/app/assets/javascripts/app/lib/app_post/popover_provider/user_ticket_popover_provider.coffee new file mode 100644 index 000000000000..5cecf064b2f0 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/popover_provider/user_ticket_popover_provider.coffee @@ -0,0 +1,37 @@ +class App.UserTicketPopoverProvider extends App.PopoverProvider + @templateName = 'user_ticket_list' + + fetch: (buildParams) -> + @params.parentController.ajax( + type: 'GET' + url: "#{App.Config.get('api_path')}/ticket_customer" + data: + customer_id: buildParams.user_id + processData: true + success: (data, status, xhr) => + App.Collection.loadAssets(data.assets) + + ticketsList = { open: data.ticket_ids_open, closed: data.ticket_ids_closed } + @callback(ticketsList: ticketsList, selector: buildParams.selector) + ) + + build: (buildParams) -> + return if !@checkPermissions() + @fetch(buildParams) + + callback: (supplementaryData) -> + @clear(@popovers) + @popovers = @buildPopovers(supplementaryData) + + buildTitleFor: (elem) -> + $(elem).find('[title="*"]').val() + + buildContentFor: (elem, supplementaryData) -> + type = $(elem).filter('[data-type]').data('type') + ticket_ids = supplementaryData.ticketsList[type] || [] + + tickets = ticket_ids.map (ticketId) -> App.Ticket.fullLocal(ticketId) + + # insert data + @buildHtmlContent(tickets: tickets) + diff --git a/app/assets/javascripts/app/lib/app_post/pretty_date.coffee b/app/assets/javascripts/app/lib/app_post/pretty_date.coffee index fc2711985a2f..d60b1071df83 100644 --- a/app/assets/javascripts/app/lib/app_post/pretty_date.coffee +++ b/app/assets/javascripts/app/lib/app_post/pretty_date.coffee @@ -31,6 +31,14 @@ class App.PrettyDate if type is undefined && window.App && window.App.Config type = window.App.Config.get('pretty_date_format') + + # YYYY-MM-DD HH::MM + if type is 'timestamp' + string = App.i18n.translateTimestamp(time) + if escalation + string = "#{string}" + return string + if type is 'absolute' && (direction is 'past' || direction is 'future') weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] weekday = weekdays[created.getDay()] diff --git a/app/assets/javascripts/app/lib/app_post/searchable_select.coffee b/app/assets/javascripts/app/lib/app_post/searchable_select.coffee index 81092895423f..8598fe89a4a0 100644 --- a/app/assets/javascripts/app/lib/app_post/searchable_select.coffee +++ b/app/assets/javascripts/app/lib/app_post/searchable_select.coffee @@ -36,7 +36,6 @@ class App.SearchableSelect extends Spine.Controller render: -> firstSelected = _.find @attribute.options, (option) -> option.selected - if firstSelected @attribute.valueName = firstSelected.name @attribute.value = firstSelected.value @@ -44,7 +43,6 @@ class App.SearchableSelect extends Spine.Controller @attribute.valueName = @attribute.value else if @hasSubmenu @attribute.options @attribute.valueName = @getName @attribute.value, @attribute.options - @html App.view('generic/searchable_select') attribute: @attribute options: @renderAllOptions '', @attribute.options, 0 @@ -140,11 +138,13 @@ class App.SearchableSelect extends Spine.Controller when 39 then @autocompleteOrNavigateIn event # right when 37 then @autocompleteOrNavigateOut event # left when 13 then @onEnter event - when 27 then @onEscape() + when 27 then @onEscape event when 9 then @onTab event onEscape: -> - @toggle() if @isOpen + if @isOpen + event.stopPropagation() # if the input is in a modal, prevent the modal from closing + @toggle() getCurrentOptions: -> @currentMenu.find('.js-option, .js-enter, .js-back') diff --git a/app/assets/javascripts/app/lib/app_post/task_manager.coffee b/app/assets/javascripts/app/lib/app_post/task_manager.coffee index affb98afcad8..6dcd1c987112 100644 --- a/app/assets/javascripts/app/lib/app_post/task_manager.coffee +++ b/app/assets/javascripts/app/lib/app_post/task_manager.coffee @@ -83,7 +83,20 @@ class App.TaskManager return if !_instance _instance.preferencesTrigger(key) + @tasksAutoCleanupDelayTime: (key) -> + return if !_instance + if !key + return _instance.tasksAutoCleanupDelayTime + _instance.tasksAutoCleanupDelayTime = key + + @tasksAutoCleanupTaskMax: (key) -> + return if !_instance + if !key + return _instance.maxTaskCount + _instance.maxTaskCount = key + class _taskManagerSingleton extends App.Controller + @extend App.PopoverProvidable @include App.LogInclude constructor: (params = {}) -> @@ -108,9 +121,11 @@ class _taskManagerSingleton extends App.Controller @tasksToUpdate = {} @tasksPreferences = {} @tasksPreferencesCallbacks = {} + @tasksAutoCleanupDelayTime = 12000 @activeTaskHistory = [] @queue = [] @queueRunning = false + @maxTaskCount = 30 all: -> @@ -380,7 +395,7 @@ class _taskManagerSingleton extends App.Controller if controller.hide && _.isFunction(controller.hide) controller.hide() - @anyPopoversDestroy() + @delayedRemoveAnyPopover() true @@ -599,7 +614,7 @@ class _taskManagerSingleton extends App.Controller tasksAutoCleanupDelay: => delay = => @tasksAutoCleanup() - App.Delay.set(delay, 12000, 'task-autocleanup', undefined, true) + App.Delay.set(delay, @tasksAutoCleanupDelayTime, 'task-autocleanup', undefined, true) tasksAutoCleanup: => @@ -607,13 +622,19 @@ class _taskManagerSingleton extends App.Controller currentTaskCount = => Object.keys(@allTasksByKey).length - maxTaskCount = 30 - if currentTaskCount() > maxTaskCount - for task in App.Taskbar.search(sortBy:'updated_at', order:'ASC') - if currentTaskCount() > maxTaskCount + if currentTaskCount() > @maxTaskCount + if @offlineModus + tasks = @all() + else + tasks = App.Taskbar.search(sortBy:'updated_at', order:'ASC') + for task in tasks + if currentTaskCount() > @maxTaskCount if !task.active - if _.isEmpty(task.state) || (_.isEmpty(task.state.ticket) && _.isEmpty(task.state.article)) - @log 'notice', "More then #{maxTaskCount} tasks open, close oldest untouched task #{task.key}" + worker = App.TaskManager.worker(task.key) + if worker + if worker.changed && worker.changed() + continue + @log 'notice', "More then #{@maxTaskCount} tasks open, close oldest untouched task #{task.key}" @remove(task.key) tasksInitial: => diff --git a/app/assets/javascripts/app/lib/app_post/user_ticket_popover_provider.coffee b/app/assets/javascripts/app/lib/app_post/user_ticket_popover_provider.coffee new file mode 100644 index 000000000000..b5c1f1cd4183 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/user_ticket_popover_provider.coffee @@ -0,0 +1,38 @@ +class UserTicket extends App.PopoverProvider + @templateName = 'user_ticket_list' + + fetch: (buildParams) -> + @params.parentController.ajax( + type: 'GET' + url: "#{App.Config.get('api_path')}/ticket_customer" + data: + customer_id: buildParams.user_id + processData: true + success: (data, status, xhr) => + App.Collection.loadAssets(data.assets) + + ticketsList = { open: data.ticket_ids_open, closed: data.ticket_ids_closed } + @callback(ticketsList: ticketsList, selector: buildParams.selector) + ) + + build: (buildParams) -> + return if !@checkPermissions() + @fetch(buildParams) + + callback: (supplementaryData) -> + @clear(@popovers) + @popovers = @buildPopovers(supplementaryData) + + buildTitleFor: (elem) -> + $(elem).find('[title="*"]').val() + + buildContentFor: (elem, supplementaryData) -> + type = $(elem).filter('[data-type]').data('type') + ticket_ids = supplementaryData.ticketsList[type] || [] + + tickets = ticket_ids.map (ticketId) -> App.Ticket.fullLocal(ticketId) + + # insert data + @buildHtmlContent(tickets: tickets) + +App.PopoverProvider.registerProvider('UserTicket', UserTicket) diff --git a/app/assets/javascripts/app/lib/app_post/utils.coffee b/app/assets/javascripts/app/lib/app_post/utils.coffee index 7307bcf3dbb9..d199a05775ee 100644 --- a/app/assets/javascripts/app/lib/app_post/utils.coffee +++ b/app/assets/javascripts/app/lib/app_post/utils.coffee @@ -262,6 +262,10 @@ class App.Utils # remove word markup @_removeWordMarkup(html) + # strip out browser-inserted (broken) link + # (see https://github.com/zammad/zammad/issues/2019) + @_stripDoubleDomainAnchors(html) + # remove tags, keep content html.find('font, small, time, form, label').replaceWith( -> $(@).contents() @@ -395,6 +399,15 @@ class App.Utils return window.word_filter(html) html + @_stripDoubleDomainAnchors: (html) -> + html.find('a').each( -> + origHref = $(@).attr('href') + return if !origHref? + + fixedHref = origHref.replace(/^https?:\/\/.*(?=(https?|#{config.http_type}):\/\/)/, '') + if origHref != fixedHref then $(@).attr('href', fixedHref) + ) + # signatureNeeded = App.Utils.signatureCheck(message, signature) @signatureCheck: (message, signature) -> messageText = $('
' + message + '
').text().trim() @@ -412,8 +425,8 @@ class App.Utils else true - # messageWithMarker = App.Utils.signatureIdentify(message, false) - @signatureIdentify: (message, test = false, internal = false) -> + # messageWithMarker = App.Utils.signatureIdentifyByPlaintext(message, false) + @signatureIdentifyByPlaintext: (message, test = false, internal = false) -> textToSearch = @html2text(message) # if we do have less then 10 lines and less then 300 chars ignore this @@ -632,6 +645,64 @@ class App.Utils regex = new RegExp("\>(\s{0,10}#{quote(App.Utils.htmlEscape(markers[0].line))})") message.replace(regex, ">#{markerTemplate}\$1") + @isMicrosoftOffice: (message) -> + regex = new RegExp('-----(Ursprüngliche Nachricht|Original Message|Mensaje original|Message d\'origine|Messaggio originale|邮件原件|原始郵件)-----') + message.match(regex) + + # messageWithMarker = App.Utils.signatureIdentifyByHtml(message) + @signatureIdentifyByHtml: (message) -> + # use the plaintext fallback method if message is composed by Microsoft Office + if @isMicrosoftOffice message + return @signatureIdentifyByPlaintext message + + message_element = $($.parseHTML(message)) + if message_element.length == 1 && $(message_element[0])?.children()?.length + message_element[0].innerHTML = @signatureIdentifyByHtmlHelper(message_element[0].innerHTML) + return message_element[0].outerHTML + + @signatureIdentifyByHtmlHelper(message) + + @signatureIdentifyByHtmlHelper: (message, internal = false) -> + # blockquotes and signature blocks are considered "dismiss nodes" and their indice will be stored + dismissNodes = [] + contentNodes = [] + res = [] + + isQuoteOrSignature = (el) -> + el = $(el) + tag = el.prop("tagName") + return true if tag is 'BLOCKQUOTE' + # detect Zammad's own
marker + return true if tag is 'DIV' && (el.data('signature') || el.prop('class') is 'yahoo_quoted') + _.some el.children(), (el) -> isQuoteOrSignature el + + $('
').html(message).contents().each (index, node) -> + text = $(node).text() + if node.nodeType == Node.TEXT_NODE + res.push text + if text.trim().length + contentNodes.push index + else if node.nodeType == Node.ELEMENT_NODE + res.push node.outerHTML + if isQuoteOrSignature node + dismissNodes.push index + else if text.trim().length + contentNodes.push index + + # filter out all dismiss nodes smaller than the largest content node + max_content = _.max contentNodes || 0 + dismissNodes = _.filter dismissNodes, (x) -> x >= max_content + + # return the message unchanged if there are no nodes to dismiss + return message if !dismissNodes.length + + # insert marker template at the earliest valid location + markerIndex = _.min dismissNodes + markerTemplate = '' + + res.splice(markerIndex, 0, markerTemplate) + res.join('') + # textReplaced = App.Utils.replaceTags( template, { user: { firstname: 'Bob', lastname: 'Smith' } } ) @replaceTags: (template, objects) -> template = template.replace( /#\{\s{0,2}(.+?)\s{0,2}\}/g, (index, key) -> @@ -1021,32 +1092,20 @@ class App.Utils # filter for uniq recipients recipientAddresses = {} addAddresses = (addressLine, line) -> - lineNew = '' recipients = App.Utils.parseAddressListLocal(addressLine) - if !_.isEmpty(recipients) - for recipient in recipients - if !_.isEmpty(recipient) - localRecipientAddress = recipient.toString().toLowerCase() - - # check if address is not local - if !isLocalAddress(localRecipientAddress) - - # filter for uniq recipients - if !recipientAddresses[localRecipientAddress] - recipientAddresses[localRecipientAddress] = true - - # add recipient - if lineNew - lineNew = lineNew + ', ' - lineNew = lineNew + localRecipientAddress - - lineNew - if !_.isEmpty(line) - if !_.isEmpty(lineNew) - lineNew += ', ' - lineNew += line - lineNew + recipients = recipients.map((r) -> r.toString().toLowerCase()) + recipients = _.reject(recipients, (r) -> _.isEmpty(r)) + recipients = _.reject(recipients, (r) -> isLocalAddress(r)) + recipients = _.reject(recipients, (r) -> recipientAddresses[r]) + recipients = _.each(recipients, (r) -> recipientAddresses[r] = true) + + recipients.push(line) if !_.isEmpty(line) + + # see https://github.com/zammad/zammad/issues/2154 + recipients = recipients.map((a) -> a.replace(/'(\S+@\S+\.\S+)'/, '$1')) + + recipients.join(', ') if articleNew.to articleNew.to = addAddresses(articleNew.to) diff --git a/app/assets/javascripts/app/lib/app_post/z_searchable_multiple_select.coffee b/app/assets/javascripts/app/lib/app_post/z_searchable_multiple_select.coffee new file mode 100644 index 000000000000..9e2521b27d20 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/z_searchable_multiple_select.coffee @@ -0,0 +1,43 @@ +class App.SearchableMultipleSelect extends App.SearchableAjaxSelect + + events: + 'click .selected_item': 'removeItem' + + elements: + '.items': 'itemList' + + render: -> + @attribute.valueName = '' + display_names = {} + for value in @attribute.value + display_names[value] = App[@attribute.object].findNative(value).name + @attribute.display_names = display_names + + @html App.view('generic/searchable_multiple_select') + attribute: @attribute + options: @renderAllOptions '', @attribute.options, 0 + submenus: @renderSubmenus @attribute.options + + # initial data + @currentMenu = @findMenuContainingValue @attribute.value + @level = @getIndex @currentMenu + + selectItem: (event) -> + return if !event.currentTarget.textContent + @input.val event.currentTarget.textContent.trim() + @input.trigger('change') + data_value = event.currentTarget.getAttribute('data-value') + @input.val '' + js_shadow_ids = $('.js-shadow-ids') + ids = [] + if js_shadow_ids.length > 0 + js_shadow_ids.each( -> ids.push $(this).val() ) + if !ids.includes(data_value) && $(".js-shadow[name='"+@attribute.name+"']").val() != data_value + html = App.view('generic/searchable_multiple_select_item') + title: event.currentTarget.getAttribute('title') + data_value: parseInt(data_value) + name: @attribute.name + @itemList.append html + + removeItem: (event) -> + $(event.currentTarget).closest('div').remove() \ No newline at end of file diff --git a/app/assets/javascripts/app/lib/mixins/popover_providable.coffee b/app/assets/javascripts/app/lib/mixins/popover_providable.coffee new file mode 100644 index 000000000000..40e013f2167b --- /dev/null +++ b/app/assets/javascripts/app/lib/mixins/popover_providable.coffee @@ -0,0 +1,56 @@ +InstanceMethods = + # do not call directly + initializePopovers: -> + @el.on('remove', @removePopovers) + + params = _.extend {}, @constructor.popoversDefaults, + parentController: @ + + @initializedPopovers = @selectedPopovers().map (key) -> + klass = App.Config.get(App.PopoverProvider.providersConfigKey)[key] + new klass(params) + + # returns all or selected popovers + selectedPopovers: -> + if @constructor.allPopovers + popoversConfig = App.Config.get(App.PopoverProvider.providersConfigKey) + return Object.keys(popoversConfig) + + return @constructor.registeredPopovers || [] + + # do not call directly + buildPopovers: (buildParams) -> + for popover in @initializedPopovers + popover.build(buildParams) + + renderPopovers: (buildParams = {}) -> + if !@initializedPopovers + @initializePopovers() + + @buildPopovers(buildParams) + + removePopovers: -> + return if !@initializedPopovers + + for popover in @initializedPopovers + popover.clear() + + @initializedPopovers = undefined + + delayedRemoveAnyPopover: -> + @delay(@constructor.anyPopoversDestroy, 100, 'removePopovers') + +App.PopoverProvidable = + registerPopovers: (klasses...) -> + @allPopovers = undefined + @registeredPopovers = klasses + + registerAllPopovers: -> + @allPopovers = true + + anyPopoversDestroy: -> + # do not remove permanent .popover--notifications widget + $('.popover:not(.popover--notifications)').remove() + + extended: -> + @include InstanceMethods diff --git a/app/assets/javascripts/app/lib/mixins/ticket_navigable.coffee b/app/assets/javascripts/app/lib/mixins/ticket_navigable.coffee index b444923457a3..2ccf557312e0 100644 --- a/app/assets/javascripts/app/lib/mixins/ticket_navigable.coffee +++ b/app/assets/javascripts/app/lib/mixins/ticket_navigable.coffee @@ -5,23 +5,39 @@ # # Relies on @overview_id and @ticket_id instance variables App.TicketNavigable = - openTicket: (ticket_id, url) -> + taskOpenTicket: (ticket_id, url) -> # coerce Ticket objects to id ticket_id = ticket_id.id if (ticket_id instanceof App.Ticket) - - @loadTicketTask(ticket_id) - @navigate url ? "ticket/zoom/#{ticket_id}" + @taskLoadTicket(ticket_id) + @navigate(url ? "ticket/zoom/#{ticket_id}") # preserves overview information - loadTicketTask: (ticket_id) -> + taskLoadTicket: (ticket_id) -> App.TaskManager.execute( key: "Ticket-#{ticket_id}" controller: 'TicketZoom' - params: { ticket_id: ticket_id, overview_id: @overview_id } + params: + ticket_id: ticket_id + overview_id: @overview_id show: true ) - openNextTicketInOverview: -> - return if !(@overview_id? && @ticket?) + taskOpenNextTicketInOverview: -> + if !(@overview_id? && @ticket?) + @taskCloseTicket(true) + return next_ticket = App.Overview.find(@overview_id).nextTicket(@ticket) - @openTicket(next_ticket.id) + if next_ticket + @taskCloseTicket() + @taskLoadTicket(next_ticket.id) + return + @taskCloseTicket(true) + + taskCloseTicket: (openNext = false) -> + App.TaskManager.remove(@taskKey) + return if !openNext + nextTaskUrl = App.TaskManager.nextTaskUrl() + if nextTaskUrl + @navigate nextTaskUrl + return + @navigate '#' diff --git a/app/assets/javascripts/app/models/_application_model.coffee b/app/assets/javascripts/app/models/_application_model.coffee index 50724313b100..12631a50c762 100644 --- a/app/assets/javascripts/app/models/_application_model.coffee +++ b/app/assets/javascripts/app/models/_application_model.coffee @@ -69,12 +69,15 @@ class App.Model extends Spine.Model return @login return '???' + # shows the icon representing the object itself (e. g. the organization icon in organization profile or ticket sidebar) icon: (user) -> '' + # shows icons in a list of objects (e. g. the traffic light rings in the ticket list in user profile) iconTitle: (user) -> '' + # shows icons in the activity stream (e. g. show ! if the activity stream icon is belonging to the session user) iconActivity: (user) -> '' diff --git a/app/assets/javascripts/app/models/overview.coffee b/app/assets/javascripts/app/models/overview.coffee index f5126170a1c1..ed109c5f2838 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', 'out_of_office', '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/app/assets/javascripts/app/models/postmaster_filter.coffee b/app/assets/javascripts/app/models/postmaster_filter.coffee index 81092a047e98..28a7eda6c36a 100644 --- a/app/assets/javascripts/app/models/postmaster_filter.coffee +++ b/app/assets/javascripts/app/models/postmaster_filter.coffee @@ -21,3 +21,6 @@ class App.PostmasterFilter extends App.Model 'name', ] @configure_clone = true + + @on 'create', (newRecord) -> + newRecord.channel = 'email' \ No newline at end of file diff --git a/app/assets/javascripts/app/models/taskbar.coffee b/app/assets/javascripts/app/models/taskbar.coffee index 6dfb7d9f05dc..142c1d16302f 100644 --- a/app/assets/javascripts/app/models/taskbar.coffee +++ b/app/assets/javascripts/app/models/taskbar.coffee @@ -1,5 +1,5 @@ class App.Taskbar extends App.Model - @configure 'Taskbar', 'key', 'client_id', 'callback', 'state', 'params', 'prio', 'notify', 'active', 'updated_at' + @configure 'Taskbar', 'key', 'client_id', 'callback', 'state', 'params', 'prio', 'notify', 'active', 'attachments', 'updated_at' # @extend Spine.Model.Local @extend Spine.Model.Ajax @url: @apiPath + '/taskbar' diff --git a/app/assets/javascripts/app/models/ticket.coffee b/app/assets/javascripts/app/models/ticket.coffee index 90e56db84cff..2077615bf30e 100644 --- a/app/assets/javascripts/app/models/ticket.coffee +++ b/app/assets/javascripts/app/models/ticket.coffee @@ -1,12 +1,12 @@ class App.Ticket extends App.Model - @configure 'Ticket', 'number', 'title', 'group_id', 'owner_id', 'customer_id', 'state_id', 'priority_id', 'article', 'tags', 'links', 'updated_at' + @configure 'Ticket', 'number', 'title', 'group_id', 'owner_id', 'customer_id', 'state_id', 'priority_id', 'article', 'tags', 'links', 'updated_at', 'preferences' @extend Spine.Model.Ajax @url: @apiPath + '/tickets' @configure_attributes = [ { name: 'number', display: '#', tag: 'input', type: 'text', limit: 100, null: true, readonly: 1, width: '68px' }, { name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'customer_id', display: 'Customer', tag: 'input', type: 'text', limit: 100, null: false, autocapitalize: false, relation: 'User' }, - { name: 'organization_id', display: 'Organization', tag: 'select', relation: 'Organization', readonly: 1 }, + { name: 'organization_id', display: 'Organization', tag: 'select', relation: 'Organization' }, { name: 'group_id', display: 'Group', tag: 'select', multiple: false, limit: 100, null: false, relation: 'Group', width: '10%', edit: true }, { name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, limit: 100, null: true, relation: 'User', width: '12%', edit: true }, { name: 'state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', width: '12%', edit: true, customer: true }, diff --git a/app/assets/javascripts/app/models/ticket_article.coffee b/app/assets/javascripts/app/models/ticket_article.coffee index 7c3523ace7b2..99a89a95719c 100644 --- a/app/assets/javascripts/app/models/ticket_article.coffee +++ b/app/assets/javascripts/app/models/ticket_article.coffee @@ -1,5 +1,5 @@ class App.TicketArticle extends App.Model - @configure 'TicketArticle', 'from', 'to', 'cc', 'subject', 'body', 'content_type', 'ticket_id', 'type_id', 'sender_id', 'internal', 'in_reply_to', 'form_id', 'time_unit', 'preferences', 'updated_at' + @configure 'TicketArticle', 'from', 'to', 'cc', 'subject', 'body', 'content_type', 'ticket_id', 'type_id', 'sender_id', 'internal', 'in_reply_to', 'form_id', 'subtype', 'time_unit', 'preferences', 'updated_at' @extend Spine.Model.Ajax @url: @apiPath + '/ticket_articles' @configure_attributes = [ @@ -8,7 +8,7 @@ class App.TicketArticle extends App.Model { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true }, { name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true }, { name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true }, - { name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false, searchable: false }, + { name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false, searchable: true }, { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: false, relation: 'TicketArticleType', default: '' }, { name: 'sender_id', display: 'Sender', tag: 'select', multiple: false, null: false, relation: 'TicketArticleSender', default: '' }, { name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' } }, diff --git a/app/assets/javascripts/app/models/user.coffee b/app/assets/javascripts/app/models/user.coffee index 9ee5ac0b5fd6..788a724dd618 100644 --- a/app/assets/javascripts/app/models/user.coffee +++ b/app/assets/javascripts/app/models/user.coffee @@ -1,5 +1,5 @@ class App.User extends App.Model - @configure 'User', 'login', 'firstname', 'lastname', 'email', 'web', 'password', 'phone', 'fax', 'mobile', 'street', 'zip', 'city', 'country', 'organization_id', 'department', 'note', 'role_ids', 'group_ids', 'active', 'invite', 'signup', 'updated_at' + @configure 'User', 'login', 'firstname', 'lastname', 'email', 'web', 'password', 'phone', 'fax', 'mobile', 'street', 'zip', 'city', 'country', 'organization_id', 'organization_ids', 'department', 'note', 'role_ids', 'group_ids', 'active', 'invite', 'signup', 'updated_at' @extend Spine.Model.Ajax @url: @apiPath + '/users' @@ -9,15 +9,14 @@ class App.User extends App.Model { name: 'firstname', display: 'Firstname', tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true, invite_customer: true }, { name: 'lastname', display: 'Lastname', tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true, invite_customer: true }, { name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 100, null: false, signup: true, info: true, invite_agent: true, invite_customer: true }, - { name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', signup: false, info: true, invite_customer: true }, + { name: 'organization_ids', display: 'Alternative Organization', tag: 'autocompletion_ajax', multiple: true, nulloption: true, null: true, relation: 'Organization', signup: false, info: true, invite_customer: true}, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_at', display: 'Created at', tag: 'datetime', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_at', display: 'Updated at', tag: 'datetime', readonly: 1 }, ] @configure_overview = [ -# 'login', 'firstname', 'lastname', 'email', 'updated_at', - 'login', 'firstname', 'lastname', 'organization' + 'login', 'firstname', 'lastname', 'organization_id', 'organization_ids' ] uiUrl: -> @@ -126,7 +125,6 @@ class App.User extends App.Model @constructor.apiPath + '/users/image/' + @image @_fillUp: (data) -> - # set socal media links if data['accounts'] for account of data['accounts'] @@ -135,9 +133,6 @@ class App.User extends App.Model if account == 'facebook' data['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + data['accounts'][account]['uid'] - if data.organization_id - data.organization = App.Organization.findNative(data.organization_id) - if data['role_ids'] data['roles'] = [] for role_id in data['role_ids'] @@ -145,6 +140,13 @@ class App.User extends App.Model role = App.Role.findNative(role_id) data['roles'].push role + if data['organization_ids'] + data['organizations'] = [] + for organization_id in data['organization_ids'] + if App.Role.exists(organization_id) + organization = App.Organization.findNative(organization_id) + data['organizations'].push organization + if data['group_ids'] data['groups'] = [] for group_id in data['group_ids'] diff --git a/app/assets/javascripts/app/views/agent_ticket_view/detail.jst.eco b/app/assets/javascripts/app/views/agent_ticket_view/detail.jst.eco index 050a35b40305..ba30a90de093 100644 --- a/app/assets/javascripts/app/views/agent_ticket_view/detail.jst.eco +++ b/app/assets/javascripts/app/views/agent_ticket_view/detail.jst.eco @@ -12,7 +12,7 @@ <%- ticket.customer.avatar("50", "", "userInfo-avatar") %> -

<%= ticket.title %>

+

<%= ticket.title || '-' %>

diff --git a/app/assets/javascripts/app/views/cti/caller_log.jst.eco b/app/assets/javascripts/app/views/cti/caller_log.jst.eco index d00c71e4a409..7a962111c8a7 100644 --- a/app/assets/javascripts/app/views/cti/caller_log.jst.eco +++ b/app/assets/javascripts/app/views/cti/caller_log.jst.eco @@ -25,24 +25,30 @@ <% for caller_id in item.preferences.from: %> <% if caller_id.user_id && App.User.exists(caller_id.user_id): %> <% shown = true %> - <% user = App.User.find(caller_id.user_id) %> + <% user = App.User.fullLocal(caller_id.user_id) %> + <% classes = ['user-popover'] %> + <% classes.push('is-inactive') if !user.active %> <% if caller_id.level isnt 'known': %><%- @T('maybe') %> <% end %> - <%= user.displayNameLong() %>
- <% else if caller_id.comment: %> + <%= user.displayNameLong() %>
+ <% else if !_.isEmpty(caller_id.comment): %> <% shown = true %> <%- @T('maybe') %> <%= caller_id.comment %>
<% end %> <% end %> <% end %> - <% if !shown && item.from_comment: %> + <% if !shown && !_.isEmpty(item.from_comment): %> <% shown = true %> - <% if item.from_comment: %><%= item.from_comment %><% end %> + <%= item.from_comment %>
<% end %> <% if shown: %> <%= item.from_pretty %> <% else: %> - <%= item.from_pretty %> + <% if !_.isEmpty(item.from_pretty): %> + <%= item.from_pretty %> + <% else: %> + <%= item.from %> + <% end %> <% end %> - <% if @params.data_option && @params.data_option.options: %> - <% for key, display of @params.data_option.options: %> + <% if @params.data_option && @params.data_option.sorted: %> + <% for [key, display] in @params.data_option.sorted: %>
@@ -51,24 +57,30 @@ <% for caller_id in item.preferences.to: %> <% if caller_id.user_id && App.User.exists(caller_id.user_id): %> <% shown = true %> - <% user = App.User.find(caller_id.user_id) %> + <% user = App.User.fullLocal(caller_id.user_id) %> + <% classes = ['user-popover'] %> + <% classes.push('is-inactive') if !user.active %> <% if caller_id.level isnt 'known': %><%- @T('maybe') %> <% end %> - <%= user.displayNameLong() %>
- <% else if caller_id.comment: %> + <%= user.displayNameLong() %>
+ <% else if !_.isEmpty(caller_id.comment): %> <% shown = true %> <%- @T('maybe') %> <%= caller_id.comment %>
<% end %> <% end %> <% end %> - <% if !shown && item.to_comment: %> + <% if !shown && !_.isEmpty(item.to_comment): %> <% shown = true %> - <% if item.to_comment: %><%= item.to_comment %><% end %> + <%= item.to_comment %>
<% end %> <% if shown: %> <%= item.to_pretty %> <% else: %> - <%= item.to_pretty %> + <% if !_.isEmpty(item.to_pretty): %> + <%= item.to_pretty %> + <% else: %> + <%= item.to %> + <% end %> <% end %>
diff --git a/app/assets/javascripts/app/views/generic/attachment.jst.eco b/app/assets/javascripts/app/views/generic/attachment.jst.eco index 9b9328a01a07..827312ade3fa 100644 --- a/app/assets/javascripts/app/views/generic/attachment.jst.eco +++ b/app/assets/javascripts/app/views/generic/attachment.jst.eco @@ -2,8 +2,11 @@
- <%- @T('select attachment...') %> - + <% uid = _.uniqueId('fileUpload_') %> + +
diff --git a/app/assets/javascripts/app/views/generic/postmaster_set.jst.eco b/app/assets/javascripts/app/views/generic/postmaster_set.jst.eco index 32f35019bcc5..4016a6f48e8e 100644 --- a/app/assets/javascripts/app/views/generic/postmaster_set.jst.eco +++ b/app/assets/javascripts/app/views/generic/postmaster_set.jst.eco @@ -1,26 +1 @@ -
-
-
-
-
- <%- @Icon('arrow-down', 'dropdown-arrow') %> -
-
-
-
- - <%- @Icon('arrow-down') %> -
-
-
-
-
-
- <%- @Icon('minus-small') %> -
-
- <%- @Icon('plus-small') %> -
-
-
-
+
diff --git a/app/assets/javascripts/app/views/generic/postmaster_set_row.jst.eco b/app/assets/javascripts/app/views/generic/postmaster_set_row.jst.eco new file mode 100644 index 000000000000..a15c52daf99f --- /dev/null +++ b/app/assets/javascripts/app/views/generic/postmaster_set_row.jst.eco @@ -0,0 +1,24 @@ +
+
+
+
+ <%- @Icon('arrow-down', 'dropdown-arrow') %> +
+
+
+
+ + <%- @Icon('arrow-down') %> +
+
+
+
+
+
+ <%- @Icon('minus-small') %> +
+
+ <%- @Icon('plus-small') %> +
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/generic/searchable_multiple_select.jst.eco b/app/assets/javascripts/app/views/generic/searchable_multiple_select.jst.eco new file mode 100644 index 000000000000..6d6170b0cb4b --- /dev/null +++ b/app/assets/javascripts/app/views/generic/searchable_multiple_select.jst.eco @@ -0,0 +1,33 @@ + + +
+ <% if @attribute.value: %> + <% for value in @attribute.value: %> +
+ + <%- @attribute.display_names[value] %> + + +
+ <% end %> + <% end %> +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/generic/searchable_multiple_select_item.jst.eco b/app/assets/javascripts/app/views/generic/searchable_multiple_select_item.jst.eco new file mode 100644 index 000000000000..177f4ec65cf4 --- /dev/null +++ b/app/assets/javascripts/app/views/generic/searchable_multiple_select_item.jst.eco @@ -0,0 +1,4 @@ +
+ <%= @title %> + > +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/generic/searchable_select.jst.eco b/app/assets/javascripts/app/views/generic/searchable_select.jst.eco index ea66976d6fe1..70259c5c82b8 100644 --- a/app/assets/javascripts/app/views/generic/searchable_select.jst.eco +++ b/app/assets/javascripts/app/views/generic/searchable_select.jst.eco @@ -6,6 +6,7 @@ <%= @attribute.required %> <%= @attribute.autofocus %> value="<%= @attribute.value %>" + tabindex="-1" > " diff --git a/app/assets/javascripts/app/views/generic/ticket_perform_action/index.jst.eco b/app/assets/javascripts/app/views/generic/ticket_perform_action/index.jst.eco index d2bb37cbd4ae..2fb4b2817786 100644 --- a/app/assets/javascripts/app/views/generic/ticket_perform_action/index.jst.eco +++ b/app/assets/javascripts/app/views/generic/ticket_perform_action/index.jst.eco @@ -1,21 +1 @@ -
-
-
-
-
- <%- @Icon('arrow-down', 'dropdown-arrow') %> -
-
-
-
-
-
-
- <%- @Icon('minus-small') %> -
-
- <%- @Icon('plus-small') %> -
-
-
-
\ No newline at end of file +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/generic/ticket_perform_action/row.jst.eco b/app/assets/javascripts/app/views/generic/ticket_perform_action/row.jst.eco new file mode 100644 index 000000000000..661b772f8bd9 --- /dev/null +++ b/app/assets/javascripts/app/views/generic/ticket_perform_action/row.jst.eco @@ -0,0 +1,19 @@ +
+
+
+
+ <%- @Icon('arrow-down', 'dropdown-arrow') %> +
+
+
+
+
+
+
+ <%- @Icon('minus-small') %> +
+
+ <%- @Icon('plus-small') %> +
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/integration/idoit_object_selector.jst.eco b/app/assets/javascripts/app/views/integration/idoit_object_selector.jst.eco index fa63a3604103..aa6ded2aa5b5 100644 --- a/app/assets/javascripts/app/views/integration/idoit_object_selector.jst.eco +++ b/app/assets/javascripts/app/views/integration/idoit_object_selector.jst.eco @@ -1,5 +1,5 @@
diff --git a/app/assets/javascripts/app/views/link/info.jst.eco b/app/assets/javascripts/app/views/link/info.jst.eco index 52b191925179..7a757986e433 100644 --- a/app/assets/javascripts/app/views/link/info.jst.eco +++ b/app/assets/javascripts/app/views/link/info.jst.eco @@ -9,7 +9,7 @@ <%- @Icon('task-state', item.iconClass()) %>
- <%= item.title %> + <%= item.title || '-' %> <%- @humanTime(item.created_at) %>
diff --git a/app/assets/javascripts/app/views/object_manager/attribute/select.jst.eco b/app/assets/javascripts/app/views/object_manager/attribute/select.jst.eco index 448859052412..3328f631ece7 100644 --- a/app/assets/javascripts/app/views/object_manager/attribute/select.jst.eco +++ b/app/assets/javascripts/app/views/object_manager/attribute/select.jst.eco @@ -8,8 +8,8 @@
<%- @T('Action') %>
diff --git a/app/assets/javascripts/app/views/popover/organization.jst.eco b/app/assets/javascripts/app/views/popover/organization.jst.eco index 1a25b08b539b..9f6bbef46e84 100644 --- a/app/assets/javascripts/app/views/popover/organization.jst.eco +++ b/app/assets/javascripts/app/views/popover/organization.jst.eco @@ -1,19 +1,11 @@ -
-<% for row in @organizationData: %> - <% if @organization[row.name]: %> -
- - <%- @P( @organization, row.name ) %> -
- <% end %> -<% end %> - -<% if @organization.members: %> +<%- @V('popover/single_object_generic', object: @object, attributes: @attributes) %> + +<% if @object.members: %>
- <% for user in @organization.members: %> + <% for user in @object.members: %>
<%= user.displayName() %>
<% end %>
-<% end %> \ No newline at end of file +<% end %> diff --git a/app/assets/javascripts/app/views/popover/single_object_generic.jst.eco b/app/assets/javascripts/app/views/popover/single_object_generic.jst.eco new file mode 100644 index 000000000000..b317a4e6a4b0 --- /dev/null +++ b/app/assets/javascripts/app/views/popover/single_object_generic.jst.eco @@ -0,0 +1,12 @@ +<% if !_.isEmpty(@attributes): %> +
+<% end %> + +<% for row in @attributes: %> + <% if @object[row.name]: %> +
+ + <%- @P( @object, row.name ) %> +
+ <% end %> +<% end %> diff --git a/app/assets/javascripts/app/views/popover/ticket.jst.eco b/app/assets/javascripts/app/views/popover/ticket.jst.eco index 819ffdaa750c..b124eb3b7eb9 100644 --- a/app/assets/javascripts/app/views/popover/ticket.jst.eco +++ b/app/assets/javascripts/app/views/popover/ticket.jst.eco @@ -1,22 +1,22 @@
- <%- @Icon(@ticket.icon(), @ticket.iconClass()) %> <%- @ticket.iconTitle() %> + <%- @Icon(@object.icon(), @object.iconClass()) %> <%- @object.iconTitle() %>

- <%= @ticket.owner.displayName() %> - <% if @ticket.owner.organization: %> - <%= @ticket.owner.organization.displayName() %> + <%= @object.owner.displayName() %> + <% if @object.owner.organization: %> + <%= @object.owner.organization.displayName() %> <% end %>
- <%= @ticket.customer.displayName() %> - <% if @ticket.customer.organization: %> - <%= @ticket.customer.organization.displayName() %> + <%= @object.customer.displayName() %> + <% if @object.customer.organization: %> + <%= @object.customer.organization.displayName() %> <% end %>
@@ -24,18 +24,18 @@
-
<%- @P(@ticket, 'number') %>
+
<%- @P(@object, 'number') %>
-
<%- @P(@ticket, 'priority') %>
+
<%- @P(@object, 'priority') %>
-
<%- @P(@ticket, 'created_at') %>
+
<%- @P(@object, 'created_at') %>
-
<%- @P(@ticket, 'group') %>
+
<%- @P(@object, 'group') %>
-
\ No newline at end of file + diff --git a/app/assets/javascripts/app/views/popover/user.jst.eco b/app/assets/javascripts/app/views/popover/user.jst.eco index 7fcc636ee71a..b2bc622c7cbb 100644 --- a/app/assets/javascripts/app/views/popover/user.jst.eco +++ b/app/assets/javascripts/app/views/popover/user.jst.eco @@ -1,25 +1,20 @@ -<% if @user['organization']: %> - +<% if @object['organization']: %> + <% end %> -
-<% for row in @userData: %> - <% if @user[row.name]: %> + +<%- @V('popover/single_object_generic', object: @object, attributes: @attributes) %> +<% if !_.isEmpty(@object['accounts']): %> +
- - <%- @P(@user, row.name) %> + + <% for account of @object['accounts']: %> + <%= account %> + <% end %>
- <% end %> -<% end %> -<% if !_.isEmpty(@user['accounts']): %> -
- - <% for account of @user['accounts']: %> - <%= account %> - <% end %> -
<% end %> -<% if !_.isEmpty(@user['links']): %> - <% for link in @user['links']: %> +<% if !_.isEmpty(@object['links']): %> +
+ <% for link in @object['links']: %>
<% for item in link['items']: %> @@ -37,4 +32,4 @@ <% end %>
<% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/assets/javascripts/app/views/popover/user_ticket_list.jst.eco b/app/assets/javascripts/app/views/popover/user_ticket_list.jst.eco index 9216f53d1cd4..65de820b56cb 100644 --- a/app/assets/javascripts/app/views/popover/user_ticket_list.jst.eco +++ b/app/assets/javascripts/app/views/popover/user_ticket_list.jst.eco @@ -5,7 +5,7 @@ <%- @Icon('task-state', ticket.iconClass()) %> diff --git a/app/assets/javascripts/app/views/ticket_overview/batch_overlay_user_group.jst.eco b/app/assets/javascripts/app/views/ticket_overview/batch_overlay_user_group.jst.eco index 2931f9bd3455..db765db7158d 100644 --- a/app/assets/javascripts/app/views/ticket_overview/batch_overlay_user_group.jst.eco +++ b/app/assets/javascripts/app/views/ticket_overview/batch_overlay_user_group.jst.eco @@ -8,6 +8,6 @@
<%- group.avatar(80) %>
<%- group.displayName() %>
-
<%- @T('%s people', group.valid_user_ids.length) %>
+
<%- @T('%s people', group.valid_users_count) %>
<% end %> \ No newline at end of file diff --git a/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco index a4d1ae0b3400..d58e451e3e58 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco @@ -2,6 +2,7 @@ +
@@ -68,9 +69,12 @@
<%- @T('Enter Answer or') %> - - <%- @T('select attachment...') %> - + + <% uid = _.uniqueId('fileUpload_') %> + +
diff --git a/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco index b15af64009fe..348805aa21d9 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco @@ -93,6 +93,7 @@
diff --git a/app/assets/javascripts/app/views/ticket_zoom/attribute_bar.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/attribute_bar.jst.eco index c95c7d83749b..65bec635a647 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/attribute_bar.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/attribute_bar.jst.eco @@ -24,16 +24,18 @@
+
<% if @macroDisabled: %> -
<%- @T('Update') %>
+ <% else: %> -<% end %> \ No newline at end of file +<% end %> +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/ticket_zoom/title.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/title.jst.eco index c4d66f32c6e0..7687f434fe55 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/title.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/title.jst.eco @@ -1 +1 @@ -
<%= @object.title %>
\ No newline at end of file +
<%= @object.title || '-' %>
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/time_accounting/by_ticket.jst.eco b/app/assets/javascripts/app/views/time_accounting/by_ticket.jst.eco index 0f395dbf9295..802dd1c93ac9 100644 --- a/app/assets/javascripts/app/views/time_accounting/by_ticket.jst.eco +++ b/app/assets/javascripts/app/views/time_accounting/by_ticket.jst.eco @@ -20,7 +20,7 @@ <% for row in @rows: %>
<%= row.ticket.number %> - <%= row.ticket.title %> + <%= row.ticket.title || '-' %> <%= row.customer %> <%= row.organization %> <%= row.agent %> diff --git a/app/assets/javascripts/app/views/user_profile/index.jst.eco b/app/assets/javascripts/app/views/user_profile/index.jst.eco index 601eca945600..8ee5b2572b20 100644 --- a/app/assets/javascripts/app/views/user_profile/index.jst.eco +++ b/app/assets/javascripts/app/views/user_profile/index.jst.eco @@ -4,9 +4,12 @@
<%- @user.avatar("80") %>

- <% if @user.organization: %> + <% if @user.organization_id: %>
<% end %> + <% for org in @user.organizations: %> +
+ <% end %>
diff --git a/app/assets/javascripts/app/views/widget/organization.jst.eco b/app/assets/javascripts/app/views/widget/organization.jst.eco index 5935cfdc6767..2b5d2daf9049 100644 --- a/app/assets/javascripts/app/views/widget/organization.jst.eco +++ b/app/assets/javascripts/app/views/widget/organization.jst.eco @@ -8,7 +8,7 @@ <% for row in @organizationData: %> - <% if @organization[row.name] || row.name is 'note': %> + <% if @organization[row.name]? || row.name is 'note': %>
- <%= ticket.title %> + <%= ticket.title || '-' %> <%- @humanTime(ticket.created_at, false, 'time') %>
diff --git a/app/assets/javascripts/app/views/widget/user.jst.eco b/app/assets/javascripts/app/views/widget/user.jst.eco index fbb0a0215683..b38bea0c2d98 100644 --- a/app/assets/javascripts/app/views/widget/user.jst.eco +++ b/app/assets/javascripts/app/views/widget/user.jst.eco @@ -3,7 +3,7 @@

<%= @user.displayName() %>

<% for row in @userData: %> - <% if @user[row.name] || row.name is 'note': %> + <% if @user[row.name]? || row.name is 'note': %>