Merge branch 'stable' of https://github.com/zammad/zammad into antifascista-stable
This commit is contained in:
commit
62f319b589
245 changed files with 5677 additions and 2989 deletions
20
.coffeelint/rules/prevent_underscore_backport.coffee
Normal file
20
.coffeelint/rules/prevent_underscore_backport.coffee
Normal file
|
@ -0,0 +1,20 @@
|
|||
module.exports = class PreventUnderscoreBackport
|
||||
|
||||
rule:
|
||||
name: 'prevent_underscore_backport'
|
||||
level: 'error'
|
||||
message: 'The method __(...) is not available in current stable'
|
||||
description: '''
|
||||
'''
|
||||
|
||||
constructor: ->
|
||||
@callTokens = []
|
||||
|
||||
tokens: ['CALL_START']
|
||||
|
||||
lintToken: (token, tokenApi) ->
|
||||
[type, tokenValue] = token
|
||||
|
||||
p = tokenApi.peek(-1)
|
||||
if p[1] == '__'
|
||||
return { }
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -84,6 +84,9 @@
|
|||
# Eclipse
|
||||
/.project
|
||||
|
||||
# VSCode
|
||||
/.vscode
|
||||
|
||||
# Byebug
|
||||
/.byebug_history
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
default:
|
||||
image: registry.znuny.com/docker/zammad-ci:2.7.4
|
||||
image: $CI_REGISTRY/docker/zammad-ci:2.7.4
|
||||
|
||||
include:
|
||||
- local: '/.gitlab/ci/base.yml'
|
||||
|
@ -55,7 +55,7 @@ cache:
|
|||
# Initialize application env
|
||||
before_script:
|
||||
- source /etc/profile.d/rvm.sh
|
||||
- source /opt/rh/rh-nodejs12/enable
|
||||
- source /opt/rh/rh-nodejs*/enable
|
||||
- bundle install -j $(nproc) --path vendor
|
||||
- bundle exec ruby .gitlab/configure_environment.rb
|
||||
- source .gitlab/environment.env
|
||||
|
|
|
@ -74,35 +74,35 @@
|
|||
|
||||
# DB Docker
|
||||
.docker_mysql: &docker_mysql
|
||||
name: registry.znuny.com/docker/zammad-mysql:stable
|
||||
name: $CI_REGISTRY/docker/zammad-mysql:stable
|
||||
alias: mysql
|
||||
|
||||
.docker_postgresql: &docker_postgresql
|
||||
name: registry.znuny.com/docker/zammad-postgresql:stable
|
||||
name: $CI_REGISTRY/docker/zammad-postgresql:stable
|
||||
alias: postgresql
|
||||
|
||||
.docker_elasticsearch: &docker_elasticsearch
|
||||
name: registry.znuny.com/docker/zammad-elasticsearch:$ELASTICSEARCH_TAG
|
||||
name: $CI_REGISTRY/docker/zammad-elasticsearch:$ELASTICSEARCH_TAG
|
||||
alias: elasticsearch
|
||||
|
||||
.docker_selenium_chrome: &docker_selenium_chrome
|
||||
name: registry.znuny.com/docker/zammad-selenium-chrome:stable
|
||||
name: $CI_REGISTRY/docker/zammad-selenium-chrome:stable
|
||||
alias: selenium-chrome
|
||||
|
||||
.docker_selenium_firefox: &docker_selenium_firefox
|
||||
name: registry.znuny.com/docker/zammad-selenium-firefox:stable
|
||||
name: $CI_REGISTRY/docker/zammad-selenium-firefox:stable
|
||||
alias: selenium-firefox
|
||||
|
||||
.docker_imap: &docker_imap
|
||||
name: registry.znuny.com/docker/zammad-imap:stable
|
||||
name: $CI_REGISTRY/docker/zammad-imap:stable
|
||||
alias: mail
|
||||
|
||||
.docker_redis: &docker_redis
|
||||
name: registry.znuny.com/docker/zammad-redis:stable
|
||||
name: $CI_REGISTRY/docker/zammad-redis:stable
|
||||
alias: redis
|
||||
|
||||
.docker_memcached: &docker_memcached
|
||||
name: registry.znuny.com/docker/zammad-memcached:stable
|
||||
name: $CI_REGISTRY/docker/zammad-memcached:stable
|
||||
alias: memcached
|
||||
command: ["memcached", "-m", "256M"]
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ api_client_php:
|
|||
script:
|
||||
- RAILS_ENV=test bundle exec rake db:create
|
||||
- RAILS_ENV=test bundle exec rake zammad:ci:test:start zammad:setup:auto_wizard
|
||||
- git clone https://github.com/zammad/zammad-api-client-php.git
|
||||
- git clone https://github.com/zammad/zammad-api-client-php.git -b zammad-ci-5.0 # Use state with tests compatible to 5.0
|
||||
- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||
- php composer-setup.php --install-dir=/usr/local/bin
|
||||
- ln -s /usr/local/bin/composer.phar /usr/local/bin/composer
|
||||
|
|
|
@ -7,11 +7,11 @@ otrs_chrome:
|
|||
IMPORT_OTRS_ENDPOINT: "http://zammad-ci-otrsimport-app/otrs/public.pl?Action=ZammadMigrator"
|
||||
TZ: "Europe/Berlin" # Required for the zammad-ci-otrsimport-app containers
|
||||
services:
|
||||
- name: registry.znuny.com/docker/zammad-postgresql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-postgresql:stable
|
||||
alias: postgresql
|
||||
- name: registry.znuny.com/docker/zammad-selenium-chrome:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-selenium-chrome:stable
|
||||
alias: selenium-chrome
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-db:otrs6
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-db:otrs6
|
||||
alias: zammad-ci-otrsimport-db
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-app:otrs6
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs6
|
||||
alias: zammad-ci-otrsimport-app
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
- bundle exec rails test test/integration/elasticsearch_active_test.rb
|
||||
- bundle exec rails test test/integration/elasticsearch_test.rb
|
||||
- bundle exec rspec --tag searchindex --tag ~type:system --profile 10
|
||||
- bundle exec rails test test/integration/report_test.rb
|
||||
|
||||
es:7:
|
||||
<<: *template_integration_es
|
||||
|
|
|
@ -12,71 +12,71 @@
|
|||
otrs:6:
|
||||
<<: *template_integration_otrs
|
||||
services:
|
||||
- name: registry.znuny.com/docker/zammad-mysql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-mysql:stable
|
||||
alias: mysql
|
||||
- name: registry.znuny.com/docker/zammad-postgresql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-postgresql:stable
|
||||
alias: postgresql
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-db:otrs6
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-db:otrs6
|
||||
alias: zammad-ci-otrsimport-db
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-app:otrs6
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs6
|
||||
alias: zammad-ci-otrsimport-app
|
||||
|
||||
otrs:5:
|
||||
<<: *template_integration_otrs
|
||||
services:
|
||||
- name: registry.znuny.com/docker/zammad-mysql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-mysql:stable
|
||||
alias: mysql
|
||||
- name: registry.znuny.com/docker/zammad-postgresql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-postgresql:stable
|
||||
alias: postgresql
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-db:otrs5
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-db:otrs5
|
||||
alias: zammad-ci-otrsimport-db
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-app:otrs5
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs5
|
||||
alias: zammad-ci-otrsimport-app
|
||||
|
||||
otrs:4:
|
||||
<<: *template_integration_otrs
|
||||
services:
|
||||
- name: registry.znuny.com/docker/zammad-mysql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-mysql:stable
|
||||
alias: mysql
|
||||
- name: registry.znuny.com/docker/zammad-postgresql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-postgresql:stable
|
||||
alias: postgresql
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-db:otrs4
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-db:otrs4
|
||||
alias: zammad-ci-otrsimport-db
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-app:otrs4
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs4
|
||||
alias: zammad-ci-otrsimport-app
|
||||
|
||||
otrs:33:
|
||||
<<: *template_integration_otrs
|
||||
services:
|
||||
- name: registry.znuny.com/docker/zammad-mysql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-mysql:stable
|
||||
alias: mysql
|
||||
- name: registry.znuny.com/docker/zammad-postgresql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-postgresql:stable
|
||||
alias: postgresql
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-db:otrs33
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-db:otrs33
|
||||
alias: zammad-ci-otrsimport-db
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-app:otrs33
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs33
|
||||
alias: zammad-ci-otrsimport-app
|
||||
|
||||
otrs:32:
|
||||
<<: *template_integration_otrs
|
||||
services:
|
||||
- name: registry.znuny.com/docker/zammad-mysql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-mysql:stable
|
||||
alias: mysql
|
||||
- name: registry.znuny.com/docker/zammad-postgresql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-postgresql:stable
|
||||
alias: postgresql
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-db:otrs32
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-db:otrs32
|
||||
alias: zammad-ci-otrsimport-db
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-app:otrs32
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs32
|
||||
alias: zammad-ci-otrsimport-app
|
||||
|
||||
otrs:31:
|
||||
<<: *template_integration_otrs
|
||||
services:
|
||||
- name: registry.znuny.com/docker/zammad-mysql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-mysql:stable
|
||||
alias: mysql
|
||||
- name: registry.znuny.com/docker/zammad-postgresql:stable
|
||||
- name: $CI_REGISTRY/docker/zammad-postgresql:stable
|
||||
alias: postgresql
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-db:otrs31
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-db:otrs31
|
||||
alias: zammad-ci-otrsimport-db
|
||||
- name: registry.znuny.com/docker/zammad-ci-otrsimport-app:otrs31
|
||||
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs31
|
||||
alias: zammad-ci-otrsimport-app
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
- .rules_singletest
|
||||
before_script:
|
||||
- source /etc/profile.d/rvm.sh # ensure RVM is loaded
|
||||
- source /opt/rh/rh-nodejs12/enable # ensure Node.js is available
|
||||
- source /opt/rh/rh-nodejs*/enable # ensure Node.js is available
|
||||
|
||||
rubocop:
|
||||
<<: *template_pre
|
||||
|
@ -39,7 +39,7 @@ brakeman:
|
|||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- tmp/brakeman-report.html
|
||||
- tmp/brakeman-report.html
|
||||
when: on_failure
|
||||
script:
|
||||
- bundle install -j $(nproc) --path vendor
|
||||
|
@ -48,7 +48,7 @@ brakeman:
|
|||
coffeelint:
|
||||
<<: *template_pre
|
||||
script:
|
||||
- coffeelint app/
|
||||
- coffeelint --rules ./.coffeelint/rules/* app/
|
||||
|
||||
bundle-audit:
|
||||
<<: *template_pre
|
||||
|
@ -62,8 +62,9 @@ github:
|
|||
tags:
|
||||
- deploy
|
||||
before_script:
|
||||
- "" # no RVM present in deploy ENV
|
||||
- '' # no RVM present in deploy ENV
|
||||
script:
|
||||
- git fetch --unshallow
|
||||
- script/build/sync_repo.sh git@github.com:zammad/zammad.git
|
||||
|
||||
global_refresh_envs:
|
||||
|
@ -77,7 +78,7 @@ global_refresh_envs:
|
|||
artifacts:
|
||||
expire_in: 1 day
|
||||
paths:
|
||||
- fresh.env
|
||||
- fresh.env
|
||||
rules:
|
||||
- if: $CI_MERGE_REQUEST_ID
|
||||
when: never
|
||||
|
|
12
.pkgr.yml
12
.pkgr.yml
|
@ -56,6 +56,18 @@ targets:
|
|||
- libimlib2
|
||||
- libimlib2-dev
|
||||
- shared-mime-info
|
||||
debian-11:
|
||||
dependencies:
|
||||
- curl
|
||||
- elasticsearch|elasticsearch-oss
|
||||
- nginx|apache2
|
||||
- postgresql|mariadb-server
|
||||
- libimlib2
|
||||
- shared-mime-info
|
||||
build_dependencies:
|
||||
- libimlib2
|
||||
- libimlib2-dev
|
||||
- shared-mime-info
|
||||
ubuntu-16.04:
|
||||
dependencies:
|
||||
- curl
|
||||
|
|
|
@ -17,7 +17,7 @@ module RuboCop
|
|||
PATTERN
|
||||
|
||||
def_node_matcher :has_reset?, <<-PATTERN
|
||||
$(send _ {:describe :context :it} (_ ...) (hash ... (pair (sym :db_strategy) (sym {:reset :reset_all}))))
|
||||
$(send _ {:describe :context :it :shared_examples} (_ ...) (hash <(pair (sym :db_strategy) (sym {:reset :reset_all})) ...> ))
|
||||
PATTERN
|
||||
|
||||
MSG = 'Add a `db_strategy: :reset` to your context/decribe when you are creating object manager attributes!'.freeze
|
||||
|
|
17
.rubocop/cop/zammad/prevent_underscore_backport.rb
Normal file
17
.rubocop/cop/zammad/prevent_underscore_backport.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Zammad
|
||||
class PreventUnderscroreBackport < Base
|
||||
MSG = <<~ERROR_MESSAGE.freeze
|
||||
The method __(...) is not available in current stable.
|
||||
ERROR_MESSAGE
|
||||
|
||||
def on_send(node)
|
||||
add_offense(node) if node.method_name.eql? :__
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,7 +13,8 @@ module RuboCop
|
|||
|
||||
def on_new_investigation
|
||||
if processed_source.raw_source.include? '# Copyright (C) 2012-'
|
||||
update_copyright
|
||||
# Disabled for stable branches.
|
||||
# update_copyright
|
||||
else
|
||||
insert_copyright
|
||||
end
|
||||
|
|
|
@ -10,3 +10,4 @@ require_relative 'cop/zammad/no_to_sym_on_string'
|
|||
require_relative 'cop/zammad/prefer_negated_if_over_unless'
|
||||
require_relative 'cop/zammad/update_copyright'
|
||||
require_relative 'cop/zammad/forbid_rand'
|
||||
require_relative 'cop/zammad/prevent_underscore_backport'
|
||||
|
|
81
CHANGELOG.md
81
CHANGELOG.md
|
@ -1,22 +1,88 @@
|
|||
# Change Log
|
||||
|
||||
## [5.0.3](https://github.com/zammad/zammad/tree/5.0.3) (2021-12-07)
|
||||
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/5.0.2...5.0.3)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Possibility to specify the order of objects [294](https://github.com/zammad/zammad/issues/294) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)]
|
||||
- Display callback urls for third-party applications [3622](https://github.com/zammad/zammad/issues/3622) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Add clear selection action or has changed condition [3821](https://github.com/zammad/zammad/issues/3821) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[specification required](https://github.com/zammad/zammad/labels/specification required)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Missing ticket updates on high load in MariaDB/MySQL environments [3877](https://github.com/zammad/zammad/issues/3877) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Wrong SLA is used (alphabetical order is ignored) [3871](https://github.com/zammad/zammad/issues/3871) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Agent with CREATE only permission acts "on behalf" ticket customer on shared organizations [3872](https://github.com/zammad/zammad/issues/3872) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Provide meaningful modal if report profile tries to use dates out side the filtered date range [3616](https://github.com/zammad/zammad/issues/3616) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[reporting](https://github.com/zammad/zammad/labels/reporting)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Allow `position` to determine an attributes position entirely [3594](https://github.com/zammad/zammad/issues/3594) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)] [[specification required](https://github.com/zammad/zammad/labels/specification required)]
|
||||
- Till label not assigned to corresponding input fields in calendar edit view [3793](https://github.com/zammad/zammad/issues/3793) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
|
||||
- Simple quote characters (`'`) not properly displayed [3846](https://github.com/zammad/zammad/issues/3846) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
|
||||
- Remove api user and password for sipgate integration [3848](https://github.com/zammad/zammad/issues/3848) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin area)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- No signature on new ticket if email is default message type [3844](https://github.com/zammad/zammad/issues/3844) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Forwarding no longer possible for email and web articles [3855](https://github.com/zammad/zammad/issues/3855) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Default values of new group dialog is different in german vs. english [3851](https://github.com/zammad/zammad/issues/3851) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- If no date is set the UI show it's shown as NaN.NaN.NaN. [3850](https://github.com/zammad/zammad/issues/3850) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Number of to show caller log entries is inconsistent setting wise [3852](https://github.com/zammad/zammad/issues/3852) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[admin area](https://github.com/zammad/zammad/labels/admin area)] [[integration](https://github.com/zammad/zammad/labels/integration)]
|
||||
- Reply all: Duplicate email on changing recipient and cc in a certain way [3825](https://github.com/zammad/zammad/issues/3825) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Removing calendars via UI and API does not check for references [3845](https://github.com/zammad/zammad/issues/3845) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[escalation logic](https://github.com/zammad/zammad/labels/escalation logic)]
|
||||
- Email address not shown inside forwarded email [3824](https://github.com/zammad/zammad/issues/3824) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Wrong escalation update time in admin interface is shown [3837](https://github.com/zammad/zammad/issues/3837) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Zammad returns stack error when one tries to remove groups via API [3841](https://github.com/zammad/zammad/issues/3841) [[bug](https://github.com/zammad/zammad/labels/bug)] [[API](https://github.com/zammad/zammad/labels/API)]
|
||||
- Zammad ignores relative GitLab URLs [3830](https://github.com/zammad/zammad/issues/3830) [[bug](https://github.com/zammad/zammad/labels/bug)] [[integration](https://github.com/zammad/zammad/labels/integration)]
|
||||
- If selected value is not part of the restriction of set_fixed_to it should recalculate it with the new value [3822](https://github.com/zammad/zammad/issues/3822) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Invalid group and owner list for tickets created via customer profile [3835](https://github.com/zammad/zammad/issues/3835) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Ticket zoom will loose attachments on rerender [3831](https://github.com/zammad/zammad/issues/3831) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Package installation creates database.yml as `root` and thus breaks the installation until next update [3834](https://github.com/zammad/zammad/issues/3834) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Ticket create screen will loose attachments by time [3827](https://github.com/zammad/zammad/issues/3827) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Simple quote characters (`'`) not properly displayed [3733](https://github.com/zammad/zammad/issues/3733) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
|
||||
- When quoting, no breakout from div container possible [3094](https://github.com/zammad/zammad/issues/3094) [[bug](https://github.com/zammad/zammad/labels/bug)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
|
||||
- Quoting not working cleanly, if content gets too much [2334](https://github.com/zammad/zammad/issues/2334) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Zammad database credentials are world-readable [3828](https://github.com/zammad/zammad/issues/3828) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Update time SLAs escalates tickets with agent response [3140](https://github.com/zammad/zammad/issues/3140) [[bug](https://github.com/zammad/zammad/labels/bug)] [[escalation logic](https://github.com/zammad/zammad/labels/escalation logic)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
|
||||
## [5.0.2](https://github.com/zammad/zammad/tree/5.0.2) (2021-10-28)
|
||||
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/5.0.1...5.0.2)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- When looking for customers, it is no longer possible to change into organizations [3815](https://github.com/zammad/zammad/issues/3815) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
|
||||
- Owner should get cleared if not listed in changed group [3818](https://github.com/zammad/zammad/issues/3818) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Custom date and datetime attributes are filled with dates on creation of tickets/users after update from 4.1 to 5.x [3810](https://github.com/zammad/zammad/issues/3810) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- ActionController::UnknownHttpMethod FRAGE [3807](https://github.com/zammad/zammad/issues/3807) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[macros](https://github.com/zammad/zammad/labels/macros)]
|
||||
- Remote change of the group id does show it falsly as user change and not render the new value to the ticket [3801](https://github.com/zammad/zammad/issues/3801) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Adding private keys allows adding certificates [3727](https://github.com/zammad/zammad/issues/3727) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Able to create custom fields for existing relation (e. g. ticket.state) - will lead to non bootable Zammad [3811](https://github.com/zammad/zammad/issues/3811) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Sort order group_by broken (alphabetical) [3800](https://github.com/zammad/zammad/issues/3800) [[bug](https://github.com/zammad/zammad/labels/bug)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Ticket owner selection is not updated if owner selection should be empty [3809](https://github.com/zammad/zammad/issues/3809) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Chat can't be closed after timeout [2471](https://github.com/zammad/zammad/issues/2471) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)]
|
||||
- Support workflow mechanism to do pending reminder state hide pending time use case [3790](https://github.com/zammad/zammad/issues/3790) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Cache.clear in postinstall.sh throws ugly errors on fresh installations [3808](https://github.com/zammad/zammad/issues/3808) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Example payload in webhook view leads to 500 error [3794](https://github.com/zammad/zammad/issues/3794) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- OS package upgrade fails (activity_stream_object_id) [3797](https://github.com/zammad/zammad/issues/3797) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
## [5.0.1](https://github.com/zammad/zammad/tree/5.0.1) (2021-10-08)
|
||||
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/5.0.0...5.0.1)
|
||||
|
||||
**Fixed bugs:**
|
||||
- Bug Report 4.1.x Overview Sort - Grouped by user [3737](https://github.com/zammad/zammad/issues/3737) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
|
||||
- Bug Report 4.1.x Overview Sort - Grouped by user [3737](https://github.com/zammad/zammad/issues/3737) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Article box opening on tickets with no changes [3789](https://github.com/zammad/zammad/issues/3789) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
|
||||
- UploadCacheCleanupJob does not execute [3787](https://github.com/zammad/zammad/issues/3787) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- lib/fill_db.rb fails to work in production environments [3788](https://github.com/zammad/zammad/issues/3788) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
|
||||
## [5.0.0](https://github.com/zammad/zammad/tree/5.0.0) (2021-10-05)
|
||||
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/4.1.0...5.0.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Core Workflow: Add organization condition attributes for object User [3779](https://github.com/zammad/zammad/issues/3779) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- No script content (e. g. javascript) in emails [3365](https://github.com/zammad/zammad/issues/3365) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
|
||||
- Read-only custom objects [2102](https://github.com/zammad/zammad/issues/2102) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Read-only custom objects [2102](https://github.com/zammad/zammad/issues/2102) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Granular admin permission for google channel is missing [3194](https://github.com/zammad/zammad/issues/3194) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Use country codes (e.g. `DE` or `ES`) for knowledgebase answer selection [3574](https://github.com/zammad/zammad/issues/3574) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
|
||||
- New email account expert view cannot be opened without filling in all fields [3137](https://github.com/zammad/zammad/issues/3137) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
|
||||
|
@ -34,7 +100,7 @@
|
|||
- Visualise locked users in UI and make them unlock-able for admin [2565](https://github.com/zammad/zammad/issues/2565) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Representation of inactive customers and orgnizations [3302](https://github.com/zammad/zammad/issues/3302) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- No possibility to enforce auto response if one of the blocking auto response mail header exists [3667](https://github.com/zammad/zammad/issues/3667) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
|
||||
- REST doc of Online Notification controler is outdated/wrong and expand param is missing. [3635](https://github.com/zammad/zammad/issues/3635) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- REST doc of Online Notification controler is outdated/wrong and expand param is missing. [3635](https://github.com/zammad/zammad/issues/3635) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Scroll background instead of foreground [978](https://github.com/zammad/zammad/issues/978) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
|
||||
- Log if a active user (in UI) has been logged out due to SessionTimeout [3614](https://github.com/zammad/zammad/issues/3614) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- The rake task `zammad:package:migrate` does not execute migrations for linked packages. [3606](https://github.com/zammad/zammad/issues/3606) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[developer experience](https://github.com/zammad/zammad/labels/developer experience)]
|
||||
|
@ -44,7 +110,8 @@
|
|||
- Display minutes for session timeouts instead of seconds [3575](https://github.com/zammad/zammad/issues/3575) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[admin area](https://github.com/zammad/zammad/labels/admin area)]
|
||||
|
||||
**Fixed bugs:**
|
||||
- Inconstant alignment in the listing of attachments/submit button in new article area [3773](https://github.com/zammad/zammad/issues/3773) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
- Inconstant alignment in the listing of attachments/submit button in new article area [3773](https://github.com/zammad/zammad/issues/3773) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Improve contrasts in answer search for articles [3783](https://github.com/zammad/zammad/issues/3783) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
|
||||
- escaped 'Set fixed' workflows don't refresh set values on active ticket sessions [3757](https://github.com/zammad/zammad/issues/3757) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- ObjectManager Attribute without screen attribute causes CoreWorkflows migration to fail [3781](https://github.com/zammad/zammad/issues/3781) [[bug](https://github.com/zammad/zammad/labels/bug)] [[migration / update](https://github.com/zammad/zammad/labels/migration / update)]
|
||||
|
@ -59,7 +126,7 @@
|
|||
- Possible race condition causing OTRS import to fail [3765](https://github.com/zammad/zammad/issues/3765) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
|
||||
- Incorrect alignment in the listing of attachments when creating a ticket [3746](https://github.com/zammad/zammad/issues/3746) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Saved conditions break on selections without reloading [3758](https://github.com/zammad/zammad/issues/3758) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Misleading view of user icons which are on vacation and disabled [3075](https://github.com/zammad/zammad/issues/3075) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
|
||||
- Misleading view of user icons which are on vacation and disabled [3075](https://github.com/zammad/zammad/issues/3075) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
|
||||
- User with user_id 1 is show in admin interface (which should not) [3755](https://github.com/zammad/zammad/issues/3755) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)]
|
||||
- Unable to close tickets in certran cases if core workflow is used [3710](https://github.com/zammad/zammad/issues/3710) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Login failed after upgrade to zammad 5.0 [3759](https://github.com/zammad/zammad/issues/3759) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[migration / update](https://github.com/zammad/zammad/labels/migration / update)]
|
||||
|
@ -126,7 +193,7 @@
|
|||
- Allow out of office for one day without setting two days [3590](https://github.com/zammad/zammad/issues/3590) [[bug](https://github.com/zammad/zammad/labels/bug)] [[personal settings/menu](https://github.com/zammad/zammad/labels/personal settings/menu)]
|
||||
- FreshDesk Import doesn't pull in auto-assign domain(s) for organizations [3687](https://github.com/zammad/zammad/issues/3687) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
|
||||
- FreshDesk Import brings in all users as inactive [3689](https://github.com/zammad/zammad/issues/3689) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
|
||||
- KB Public UI icons are misspaced [3680](https://github.com/zammad/zammad/issues/3680) [[bug](https://github.com/zammad/zammad/labels/bug)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
|
||||
- KB Public UI icons are misspaced [3680](https://github.com/zammad/zammad/issues/3680) [[bug](https://github.com/zammad/zammad/labels/bug)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
|
||||
- FreshDesk Import Error - undefined method `body' for 10:Integer [3661](https://github.com/zammad/zammad/issues/3661) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
|
||||
- Cannot select multiple tickets in ticket overview with shift+click in Firefox [3449](https://github.com/zammad/zammad/issues/3449) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[overviews](https://github.com/zammad/zammad/labels/overviews)]
|
||||
- "Drop files here" drag area not always hiding [3460](https://github.com/zammad/zammad/issues/3460) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[help appreciated](https://github.com/zammad/zammad/labels/help appreciated)]
|
||||
|
|
112
Gemfile.lock
112
Gemfile.lock
|
@ -21,50 +21,50 @@ GEM
|
|||
specs:
|
||||
aasm (5.2.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
actioncable (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
actioncable (6.0.4.4)
|
||||
actionpack (= 6.0.4.4)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
activejob (= 6.0.4.1)
|
||||
activerecord (= 6.0.4.1)
|
||||
activestorage (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
actionmailbox (6.0.4.4)
|
||||
actionpack (= 6.0.4.4)
|
||||
activejob (= 6.0.4.4)
|
||||
activerecord (= 6.0.4.4)
|
||||
activestorage (= 6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
actionview (= 6.0.4.1)
|
||||
activejob (= 6.0.4.1)
|
||||
actionmailer (6.0.4.4)
|
||||
actionpack (= 6.0.4.4)
|
||||
actionview (= 6.0.4.4)
|
||||
activejob (= 6.0.4.4)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.4.1)
|
||||
actionview (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
actionpack (6.0.4.4)
|
||||
actionview (= 6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
activerecord (= 6.0.4.1)
|
||||
activestorage (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
actiontext (6.0.4.4)
|
||||
actionpack (= 6.0.4.4)
|
||||
activerecord (= 6.0.4.4)
|
||||
activestorage (= 6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
actionview (6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
activejob (6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
activerecord (6.0.4.1)
|
||||
activemodel (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
activemodel (6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
activerecord (6.0.4.4)
|
||||
activemodel (= 6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
activerecord-import (1.2.0)
|
||||
activerecord (>= 3.2)
|
||||
activerecord-nulldb-adapter (0.7.0)
|
||||
|
@ -75,12 +75,12 @@ GEM
|
|||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 2.0.8, < 3)
|
||||
railties (>= 5.2.4.1)
|
||||
activestorage (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
activejob (= 6.0.4.1)
|
||||
activerecord (= 6.0.4.1)
|
||||
activestorage (6.0.4.4)
|
||||
actionpack (= 6.0.4.4)
|
||||
activejob (= 6.0.4.4)
|
||||
activerecord (= 6.0.4.4)
|
||||
marcel (~> 1.0.0)
|
||||
activesupport (6.0.4.1)
|
||||
activesupport (6.0.4.4)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
|
@ -250,7 +250,7 @@ GEM
|
|||
rainbow (>= 2.2.1)
|
||||
rake (>= 10.0)
|
||||
gli (2.20.1)
|
||||
globalid (0.5.2)
|
||||
globalid (1.0.0)
|
||||
activesupport (>= 5.0)
|
||||
gmail_xoauth (0.4.2)
|
||||
oauth (>= 0.3.6)
|
||||
|
@ -319,7 +319,7 @@ GEM
|
|||
logging (2.3.0)
|
||||
little-plugger (~> 1.1)
|
||||
multi_json (~> 1.14)
|
||||
loofah (2.12.0)
|
||||
loofah (2.13.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
lumberjack (1.2.8)
|
||||
|
@ -335,7 +335,7 @@ GEM
|
|||
mini_portile2 (2.6.1)
|
||||
mini_racer (0.2.9)
|
||||
libv8 (>= 6.9.411)
|
||||
minitest (5.14.4)
|
||||
minitest (5.15.0)
|
||||
msgpack (1.4.2)
|
||||
msgpack (1.4.2-x86_64-linux-musl)
|
||||
multi_json (1.15.0)
|
||||
|
@ -458,20 +458,20 @@ GEM
|
|||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.4.1)
|
||||
actioncable (= 6.0.4.1)
|
||||
actionmailbox (= 6.0.4.1)
|
||||
actionmailer (= 6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
actiontext (= 6.0.4.1)
|
||||
actionview (= 6.0.4.1)
|
||||
activejob (= 6.0.4.1)
|
||||
activemodel (= 6.0.4.1)
|
||||
activerecord (= 6.0.4.1)
|
||||
activestorage (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
rails (6.0.4.4)
|
||||
actioncable (= 6.0.4.4)
|
||||
actionmailbox (= 6.0.4.4)
|
||||
actionmailer (= 6.0.4.4)
|
||||
actionpack (= 6.0.4.4)
|
||||
actiontext (= 6.0.4.4)
|
||||
actionview (= 6.0.4.4)
|
||||
activejob (= 6.0.4.4)
|
||||
activemodel (= 6.0.4.4)
|
||||
activerecord (= 6.0.4.4)
|
||||
activestorage (= 6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.4.1)
|
||||
railties (= 6.0.4.4)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -482,9 +482,9 @@ GEM
|
|||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.4.1)
|
||||
loofah (~> 2.3)
|
||||
railties (6.0.4.1)
|
||||
actionpack (= 6.0.4.1)
|
||||
activesupport (= 6.0.4.1)
|
||||
railties (6.0.4.4)
|
||||
actionpack (= 6.0.4.4)
|
||||
activesupport (= 6.0.4.4)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
|
@ -597,9 +597,9 @@ GEM
|
|||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.2)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sync (0.5.0)
|
||||
tcr (0.2.2)
|
||||
|
|
|
@ -300,9 +300,10 @@ class App.Controller extends Spine.Controller
|
|||
|
||||
frontendTimeUpdateItem: (item, currentVal) =>
|
||||
timestamp = item.attr('datetime')
|
||||
time = @humanTime(timestamp, item.hasClass('escalation'))
|
||||
return if timestamp is 'null'
|
||||
|
||||
# only do dom updates on changes
|
||||
time = @humanTime(timestamp, item.hasClass('escalation'))
|
||||
return if time is currentVal
|
||||
|
||||
newTitle = App.i18n.translateTimestamp(timestamp)
|
||||
|
|
|
@ -418,11 +418,11 @@ class App.ControllerForm extends App.Controller
|
|||
|
||||
if !@constructor.fieldIsMandatory(field_by_name)
|
||||
field_by_name.attr('required', true)
|
||||
field_by_name.parents('.form-group').find('label span').html('*')
|
||||
field_by_name.closest('.form-group').find('label span').html('*')
|
||||
field_by_name.closest('.form-group').addClass('is-required')
|
||||
if !@constructor.fieldIsMandatory(field_by_data)
|
||||
field_by_data.attr('required', true)
|
||||
field_by_data.parents('.form-group').find('label span').html('*')
|
||||
field_by_data.closest('.form-group').find('label span').html('*')
|
||||
field_by_data.closest('.form-group').addClass('is-required')
|
||||
|
||||
optional: (name, el = @form) ->
|
||||
|
@ -434,11 +434,11 @@ class App.ControllerForm extends App.Controller
|
|||
|
||||
if @constructor.fieldIsMandatory(field_by_name)
|
||||
field_by_name.attr('required', false)
|
||||
field_by_name.parents('.form-group').find('label span').html('')
|
||||
field_by_name.closest('.form-group').find('label span').html('')
|
||||
field_by_name.closest('.form-group').removeClass('is-required')
|
||||
if @constructor.fieldIsMandatory(field_by_data)
|
||||
field_by_data.attr('required', false)
|
||||
field_by_data.parents('.form-group').find('label span').html('')
|
||||
field_by_data.closest('.form-group').find('label span').html('')
|
||||
field_by_data.closest('.form-group').removeClass('is-required')
|
||||
|
||||
readonly: (name, el = @form) ->
|
||||
|
|
|
@ -493,14 +493,6 @@ class App.ControllerTable extends App.Controller
|
|||
sortable: @dndCallback
|
||||
))
|
||||
|
||||
getGroupByKeyName: (object, groupBy) ->
|
||||
reference_key = groupBy + '_id'
|
||||
|
||||
if reference_key of object
|
||||
return reference_key
|
||||
|
||||
groupBy
|
||||
|
||||
sortObjectKeys: (objects, direction) ->
|
||||
sorted = Object.keys(objects).sort()
|
||||
|
||||
|
@ -523,7 +515,7 @@ class App.ControllerTable extends App.Controller
|
|||
objectsToShow = @objectsOfPage(@pagerShownPage)
|
||||
if @groupBy
|
||||
# group by raw (and not printable) value so dates work also
|
||||
objectsGrouped = _.groupBy(objectsToShow, (object) => object[@getGroupByKeyName(object, @groupBy)])
|
||||
objectsGrouped = _.groupBy(objectsToShow, (object) => @groupObjectName(object, @groupBy, excludeTags: ['date', 'datetime']))
|
||||
else
|
||||
objectsGrouped = { '': objectsToShow }
|
||||
|
||||
|
@ -863,11 +855,15 @@ class App.ControllerTable extends App.Controller
|
|||
@objects = localObjects
|
||||
@lastSortedobjects = localObjects
|
||||
|
||||
groupObjectName: (object, key = undefined) ->
|
||||
groupObjectName: (object, key = undefined, options = {}) ->
|
||||
group = object
|
||||
if key
|
||||
if key not of object
|
||||
key += '_id'
|
||||
|
||||
# return internal value if needed
|
||||
return object[key] if options.excludeTags && _.find(@attributesList, (attr) -> attr.name == key && _.contains(options.excludeTags, attr.tag))
|
||||
|
||||
group = App.viewPrint(object, key, @attributesList)
|
||||
if _.isEmpty(group)
|
||||
group = ''
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
class App.ControllerTechnicalErrorModal extends App.ControllerModal
|
||||
head: "StatusCode: #{status}"
|
||||
contentCode: ''
|
||||
buttonClose: false
|
||||
buttonSubmit: 'Ok'
|
||||
onSubmit: (e) -> @close(e)
|
||||
|
||||
content: ->
|
||||
"<pre><code>#{@contentCode}</code></pre>"
|
|
@ -428,20 +428,7 @@ class ChannelEmailAccountWizard extends App.ControllerWizardModal
|
|||
ui.hide('options::folder')
|
||||
ui.hide('options::keep_on_server')
|
||||
|
||||
handlePort = (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !params
|
||||
return if !params.options
|
||||
currentPort = @$('[name="options::port"]').val()
|
||||
if params.options.ssl is true
|
||||
if !currentPort || currentPort is '143'
|
||||
@$('[name="options::port"]').val('993')
|
||||
return
|
||||
if params.options.ssl is false
|
||||
if !currentPort || currentPort is '993'
|
||||
@$('[name="options::port"]').val('143')
|
||||
return
|
||||
|
||||
new App.ControllerForm(
|
||||
form = new App.ControllerForm(
|
||||
el: @$('.base-inbound-settings'),
|
||||
model:
|
||||
configure_attributes: configureAttributesInbound
|
||||
|
@ -449,10 +436,16 @@ class ChannelEmailAccountWizard extends App.ControllerWizardModal
|
|||
params: @account.inbound
|
||||
handlers: [
|
||||
showHideFolder,
|
||||
handlePort,
|
||||
]
|
||||
)
|
||||
|
||||
form.el.find("select[name='options::ssl']").off('change').on('change', (e) ->
|
||||
if $(e.target).val() is 'true'
|
||||
form.el.find("[name='options::port']").val('993')
|
||||
else
|
||||
form.el.find("[name='options::port']").val('143')
|
||||
)
|
||||
|
||||
toggleOutboundAdapter: =>
|
||||
|
||||
# fill user / password based on intro info
|
||||
|
|
|
@ -60,6 +60,30 @@ class Form extends App.Controller
|
|||
placetel_token: App.Setting.get('placetel_token')
|
||||
)
|
||||
|
||||
configure_attributes = [
|
||||
{
|
||||
name: 'view_limit',
|
||||
display: '',
|
||||
tag: 'select',
|
||||
null: false,
|
||||
options: [
|
||||
{ name: 60, value: 60 }
|
||||
{ name: 120, value: 120 }
|
||||
{ name: 180, value: 180 }
|
||||
{ name: 240, value: 240 }
|
||||
{ name: 300, value: 300 }
|
||||
]
|
||||
},
|
||||
]
|
||||
new App.ControllerForm(
|
||||
el: @$('.js-viewLimit')
|
||||
model:
|
||||
configure_attributes: configure_attributes,
|
||||
params:
|
||||
view_limit: @config['view_limit']
|
||||
autofocus: false
|
||||
)
|
||||
|
||||
updateCurrentConfig: =>
|
||||
config = @config
|
||||
cleanupInput = @cleanupInput
|
||||
|
@ -70,6 +94,10 @@ class Form extends App.Controller
|
|||
default_caller_id = @$('input[name=default_caller_id]').val()
|
||||
config.outbound.default_caller_id = cleanupInput(default_caller_id)
|
||||
|
||||
# default view limit
|
||||
view_limit = @$('select[name=view_limit]').val()
|
||||
config.view_limit = parseInt(view_limit)
|
||||
|
||||
# routing table
|
||||
config.outbound.routing_table = []
|
||||
@$('.js-outboundRouting .js-row').each(->
|
||||
|
|
|
@ -59,17 +59,42 @@ class Form extends App.Controller
|
|||
config: @config
|
||||
)
|
||||
|
||||
configure_attributes = [
|
||||
{
|
||||
name: 'view_limit',
|
||||
display: '',
|
||||
tag: 'select',
|
||||
null: false,
|
||||
options: [
|
||||
{ name: 60, value: 60 }
|
||||
{ name: 120, value: 120 }
|
||||
{ name: 180, value: 180 }
|
||||
{ name: 240, value: 240 }
|
||||
{ name: 300, value: 300 }
|
||||
]
|
||||
},
|
||||
]
|
||||
new App.ControllerForm(
|
||||
el: @$('.js-viewLimit')
|
||||
model:
|
||||
configure_attributes: configure_attributes,
|
||||
params:
|
||||
view_limit: @config['view_limit']
|
||||
autofocus: false
|
||||
)
|
||||
|
||||
updateCurrentConfig: =>
|
||||
config = @config
|
||||
cleanupInput = @cleanupInput
|
||||
|
||||
config.api_user = cleanupInput(@$('input[name=api_user]').val())
|
||||
config.api_password = cleanupInput(@$('input[name=api_password]').val())
|
||||
|
||||
# default caller_id
|
||||
default_caller_id = @$('input[name=default_caller_id]').val()
|
||||
config.outbound.default_caller_id = cleanupInput(default_caller_id)
|
||||
|
||||
# default view limit
|
||||
view_limit = @$('select[name=view_limit]').val()
|
||||
config.view_limit = parseInt(view_limit)
|
||||
|
||||
# routing table
|
||||
config.outbound.routing_table = []
|
||||
@$('.js-outboundRouting .js-row').each(->
|
||||
|
|
|
@ -96,6 +96,8 @@ class App.UiElement.ApplicationSelector
|
|||
# ignore passwords and relations
|
||||
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids' && row.searchable isnt false
|
||||
config = _.clone(row)
|
||||
if config.tag is 'textarea'
|
||||
config.expanding = false
|
||||
if config.type is 'email' || config.type is 'tel'
|
||||
config.type = 'text'
|
||||
for operatorRegEx, operator of operators_type
|
||||
|
|
|
@ -46,7 +46,7 @@ class App.UiElement.ApplicationUiElement
|
|||
result = []
|
||||
for row in selection
|
||||
if attribute.translate
|
||||
row.name = App.i18n.translateInline(row.name)
|
||||
row.name = App.i18n.translatePlain(row.name)
|
||||
if !_.isEmpty(row.children)
|
||||
row.children = @getConfigOptionListArray(attribute, row.children)
|
||||
result.push row
|
||||
|
@ -65,7 +65,7 @@ class App.UiElement.ApplicationUiElement
|
|||
for key in order
|
||||
name_new = selection[key]
|
||||
if attribute.translate
|
||||
name_new = App.i18n.translateInline(name_new)
|
||||
name_new = App.i18n.translatePlain(name_new)
|
||||
attribute.options.push {
|
||||
name: name_new
|
||||
value: key
|
||||
|
@ -162,7 +162,7 @@ class App.UiElement.ApplicationUiElement
|
|||
nameNew = item.name
|
||||
|
||||
if attribute.translate
|
||||
nameNew = App.i18n.translateInline(nameNew)
|
||||
nameNew = App.i18n.translatePlain(nameNew)
|
||||
|
||||
row =
|
||||
value: item.id,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# coffeelint: disable=camel_case_classes
|
||||
class App.UiElement.auth_provider
|
||||
@render: (attribute) ->
|
||||
for key, value of App.Config.get('auth_provider_all')
|
||||
continue if value.config isnt attribute.provider
|
||||
attribute.value = "#{App.Config.get('http_type')}://#{App.Config.get('fqdn')}#{value.url}/callback"
|
||||
break
|
||||
|
||||
$( App.view('generic/auth_provider')( attribute: attribute ) )
|
|
@ -155,4 +155,4 @@ class App.UiElement.basedate
|
|||
clear: 'clear'
|
||||
}
|
||||
|
||||
App.i18n.translateDeep(data)
|
||||
App.i18n.translateDeepPlain(data)
|
||||
|
|
|
@ -50,15 +50,15 @@ class App.UiElement.core_workflow_condition extends App.UiElement.ApplicationSel
|
|||
|
||||
operatorsType =
|
||||
'active$': ['is']
|
||||
'boolean$': ['is', 'is not', 'is set', 'not set']
|
||||
'integer$': ['is', 'is not', 'is set', 'not set']
|
||||
'^select$': ['is', 'is not', 'is set', 'not set']
|
||||
'^tree_select$': ['is', 'is not', 'is set', 'not set']
|
||||
'^(input|textarea|richtext)$': ['is', 'is not', 'is set', 'not set', 'regex match', 'regex mismatch']
|
||||
'boolean$': ['is', 'is not', 'is set', 'not set', 'has changed', 'changed to']
|
||||
'integer$': ['is', 'is not', 'is set', 'not set', 'has changed', 'changed to']
|
||||
'^select$': ['is', 'is not', 'is set', 'not set', 'has changed', 'changed to']
|
||||
'^tree_select$': ['is', 'is not', 'is set', 'not set', 'has changed', 'changed to']
|
||||
'^(input|textarea|richtext)$': ['is', 'is not', 'is set', 'not set', 'has changed', 'changed to', 'regex match', 'regex mismatch']
|
||||
|
||||
operatorsName =
|
||||
'_id$': ['is', 'is not', 'is set', 'not set']
|
||||
'_ids$': ['is', 'is not', 'is set', 'not set']
|
||||
'_id$': ['is', 'is not', 'is set', 'not set', 'has changed', 'changed to']
|
||||
'_ids$': ['is', 'is not', 'is set', 'not set', 'has changed', 'changed to']
|
||||
|
||||
# merge config
|
||||
elements = {}
|
||||
|
@ -153,6 +153,8 @@ class App.UiElement.core_workflow_condition extends App.UiElement.ApplicationSel
|
|||
# ignore passwords and relations
|
||||
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids' && row.searchable isnt false
|
||||
config = _.clone(row)
|
||||
if config.tag is 'textarea'
|
||||
config.expanding = false
|
||||
if config.tag is 'select'
|
||||
config.multiple = true
|
||||
config.default = undefined
|
||||
|
@ -175,7 +177,7 @@ class App.UiElement.core_workflow_condition extends App.UiElement.ApplicationSel
|
|||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
name = @buildValueName(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
if _.contains(['is set', 'not set'], currentOperator)
|
||||
if _.contains(['is set', 'not set', 'has changed'], currentOperator)
|
||||
elementRow.find('.js-value').addClass('hide').html('<input type="hidden" name="' + name + '" value="true" />')
|
||||
return
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec
|
|||
super(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildValueConfigMultiple: (config, meta) ->
|
||||
if _.contains(['add_option', 'remove_option', 'set_fixed_to'], meta.operator)
|
||||
if _.contains(['add_option', 'remove_option', 'set_fixed_to', 'select'], meta.operator)
|
||||
config.multiple = true
|
||||
config.nulloption = true
|
||||
else
|
||||
|
|
|
@ -244,7 +244,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
|||
params: params
|
||||
)
|
||||
configureAttributes = [
|
||||
{ name: 'data_option::diff', display: 'Default time Diff (minutes)', tag: 'integer', null: false, default: 24 },
|
||||
{ name: 'data_option::diff', display: 'Default time Diff (minutes)', tag: 'integer', null: true },
|
||||
]
|
||||
datetimeDiff = new App.ControllerForm(
|
||||
model:
|
||||
|
@ -258,7 +258,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
|||
|
||||
@date: (item, localParams, params) ->
|
||||
configureAttributes = [
|
||||
{ name: 'data_option::diff', display: 'Default time Diff (hours)', tag: 'integer', null: false, default: 24 },
|
||||
{ name: 'data_option::diff', display: 'Default time Diff (hours)', tag: 'integer', null: true },
|
||||
]
|
||||
dateDiff = new App.ControllerForm(
|
||||
model:
|
||||
|
|
|
@ -27,6 +27,8 @@ class App.UiElement.richtext
|
|||
renderFile = (file) ->
|
||||
item.find('.attachments').append(App.view('generic/attachment_item')(file))
|
||||
attachments.push file
|
||||
if form.richTextUploadRenderCallback
|
||||
form.richTextUploadRenderCallback(attribute, attachments)
|
||||
|
||||
if params && params.attachments
|
||||
for file in params.attachments
|
||||
|
@ -54,6 +56,8 @@ class App.UiElement.richtext
|
|||
return if item.id.toString() is id.toString()
|
||||
item
|
||||
)
|
||||
if form.richTextUploadDeleteCallback
|
||||
form.richTextUploadDeleteCallback(attribute, attachments)
|
||||
|
||||
form_id = item.closest('form').find('[name=form_id]').val()
|
||||
|
||||
|
|
|
@ -8,9 +8,11 @@ class App.UiElement.sla_times
|
|||
item = $( App.view('generic/sla_times')(
|
||||
attribute: attribute
|
||||
first_response_time: params.first_response_time
|
||||
response_time: params.response_time
|
||||
update_time: params.update_time
|
||||
solution_time: params.solution_time
|
||||
first_response_time_in_text: @toText(params.first_response_time)
|
||||
response_time_in_text: @toText(params.response_time)
|
||||
update_time_in_text: @toText(params.update_time)
|
||||
solution_time_in_text: @toText(params.solution_time)
|
||||
) )
|
||||
|
@ -26,12 +28,16 @@ class App.UiElement.sla_times
|
|||
row = element.closest('tr')
|
||||
if element.prop('checked')
|
||||
row.addClass('is-active')
|
||||
|
||||
if row.has('.js-updateTypeSelector').length > 0 && row.has('.js-updateTypeSelector:checked').length == 0
|
||||
row.find('.js-updateTypeSelector:first').prop('checked', true)
|
||||
else
|
||||
row.removeClass('is-active')
|
||||
|
||||
# reset data item
|
||||
row.find('.js-timeConvertFrom').val('')
|
||||
row.find('.js-timeConvertTo').val('')
|
||||
row.find('.js-updateTypeSelector').attr('checked', false)
|
||||
row.find('.help-inline').empty()
|
||||
row.removeClass('has-error')
|
||||
)
|
||||
|
@ -42,12 +48,16 @@ class App.UiElement.sla_times
|
|||
inText = element.val()
|
||||
|
||||
row = element.closest('tr')
|
||||
row.find('.js-activateRow').prop('checked', true)
|
||||
|
||||
row
|
||||
.find('.js-activateRow')
|
||||
.prop('checked', true)
|
||||
.trigger('change')
|
||||
|
||||
row.addClass('is-active')
|
||||
|
||||
element
|
||||
.closest('td')
|
||||
.find('.js-timeConvertTo')
|
||||
row
|
||||
.find("[name='#{element.data('name')}']")
|
||||
.val(@toMinutes(inText) || '')
|
||||
)
|
||||
|
||||
|
@ -56,9 +66,19 @@ class App.UiElement.sla_times
|
|||
$(e.currentTarget).closest('tr').find('.checkbox-replacement').click()
|
||||
)
|
||||
|
||||
# toggle update type on clicking around the element
|
||||
item.find('.js-forward-radio').bind('click', (e) ->
|
||||
elem = $(e.currentTarget).closest('p').find('.js-updateTypeSelector')
|
||||
|
||||
elem.prop('checked', true)
|
||||
elem.trigger('change')
|
||||
)
|
||||
|
||||
# focus time input on clicking surrounding cell
|
||||
item.find('.js-focus-input').bind('click', (e) ->
|
||||
$(e.currentTarget).find('.form-control').focus()
|
||||
$(e.currentTarget)
|
||||
.find('.form-control:visible')
|
||||
.focus()
|
||||
)
|
||||
|
||||
# show placeholder instead of 00:00
|
||||
|
@ -67,15 +87,36 @@ class App.UiElement.sla_times
|
|||
$(e.currentTarget).val('')
|
||||
)
|
||||
|
||||
# switch update/response times when type is selected accordingly
|
||||
item.find('.js-updateTypeSelector').bind('change', (e) ->
|
||||
element = $(e.target)
|
||||
row = element.closest('tr')
|
||||
row.find('.js-activateRow').prop('checked', true)
|
||||
row.addClass('is-active')
|
||||
|
||||
row
|
||||
.find('.js-timeConvertFrom')
|
||||
.addClass('hidden')
|
||||
.val('')
|
||||
|
||||
row
|
||||
.find('.js-timeConvertTo')
|
||||
.val('')
|
||||
|
||||
row
|
||||
.find("[data-name='#{element.val()}_time']")
|
||||
.removeClass('hidden')
|
||||
)
|
||||
|
||||
# set initial active/inactive rows
|
||||
item.find('.js-timeConvertFrom').each(->
|
||||
row = $(@).closest('tr')
|
||||
checkbox = row.find('.js-activateRow')
|
||||
if $(@).val()
|
||||
checkbox.prop('checked', true)
|
||||
row.addClass('is-active')
|
||||
else
|
||||
checkbox.prop('checked', false)
|
||||
|
||||
return if !$(@).val()
|
||||
|
||||
checkbox.prop('checked', true)
|
||||
row.addClass('is-active')
|
||||
)
|
||||
|
||||
item
|
||||
|
|
|
@ -4,16 +4,17 @@ class App.UiElement.textarea
|
|||
fileUploaderId = 'file-uploader-' + new Date().getTime() + '-' + Math.floor( Math.random() * 99999 )
|
||||
item = $( App.view('generic/textarea')( attribute: attribute ) + '<div class="file-uploader ' + attribute.class + '" id="' + fileUploaderId + '"></div>' )
|
||||
|
||||
a = ->
|
||||
visible = $( item[0] ).is(':visible')
|
||||
if visible && !$( item[0] ).expanding('active')
|
||||
$( item[0] ).expanding()
|
||||
$( item[0] ).on('focus', ->
|
||||
if attribute.expanding isnt false
|
||||
a = ->
|
||||
visible = $( item[0] ).is(':visible')
|
||||
if visible && !$( item[0] ).expanding('active')
|
||||
$( item[0] ).expanding().focus()
|
||||
)
|
||||
App.Delay.set(a, 80)
|
||||
$( item[0] ).expanding()
|
||||
$( item[0] ).on('focus', ->
|
||||
visible = $( item[0] ).is(':visible')
|
||||
if visible && !$( item[0] ).expanding('active')
|
||||
$( item[0] ).expanding().focus()
|
||||
)
|
||||
App.Delay.set(a, 80)
|
||||
|
||||
if attribute.upload
|
||||
|
||||
|
@ -41,4 +42,4 @@ class App.UiElement.textarea
|
|||
debug: false
|
||||
)
|
||||
App.Delay.set(u, 100, undefined, 'form_upload')
|
||||
item
|
||||
item
|
||||
|
|
|
@ -48,12 +48,15 @@ class App.TicketCreate extends App.Controller
|
|||
if @ticket_id && @article_id
|
||||
@split = "/#{@ticket_id}/#{@article_id}"
|
||||
|
||||
load = (data) =>
|
||||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@buildScreen(params)
|
||||
@bindId = App.TicketCreateCollection.bind(load, false)
|
||||
App.TicketCreateCollection.fetch()
|
||||
@ajax(
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/ticket_create"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@buildScreen(params)
|
||||
)
|
||||
|
||||
# rerender view, e. g. on langauge change
|
||||
@controllerBind('ui:rerender', =>
|
||||
|
@ -69,9 +72,6 @@ class App.TicketCreate extends App.Controller
|
|||
@sidebarWidget.render(@params())
|
||||
)
|
||||
|
||||
release: =>
|
||||
App.TicketCreateCollection.unbindById(@bindId)
|
||||
|
||||
currentChannel: =>
|
||||
if !type
|
||||
type = @$('.type-tabs .tab.active').data('type')
|
||||
|
@ -282,8 +282,15 @@ class App.TicketCreate extends App.Controller
|
|||
return if !@formMeta
|
||||
App.QueueManager.run(@queueKey)
|
||||
|
||||
updateTaskManagerAttachments: (attribute, attachments) =>
|
||||
taskData = App.TaskManager.get(@taskKey)
|
||||
return if _.isEmpty(taskData)
|
||||
|
||||
taskData.attachments = attachments
|
||||
App.TaskManager.update(@taskKey, taskData)
|
||||
|
||||
render: (template = {}) ->
|
||||
return if !@formMeta
|
||||
|
||||
# get params
|
||||
params = @prefilledParams || {}
|
||||
if template && !_.isEmpty(template.options)
|
||||
|
@ -325,17 +332,16 @@ class App.TicketCreate extends App.Controller
|
|||
handlers = @Config.get('TicketCreateFormHandler')
|
||||
|
||||
@controllerFormCreateMiddle = new App.ControllerForm(
|
||||
el: @$('.ticket-form-middle')
|
||||
form_id: @formId
|
||||
model: App.Ticket
|
||||
screen: 'create_middle'
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: params
|
||||
noFieldset: true
|
||||
taskKey: @taskKey
|
||||
rejectNonExistentValues: true
|
||||
el: @$('.ticket-form-middle')
|
||||
form_id: @formId
|
||||
model: App.Ticket
|
||||
screen: 'create_middle'
|
||||
handlersConfig: handlers
|
||||
formMeta: @formMeta
|
||||
params: params
|
||||
noFieldset: true
|
||||
taskKey: @taskKey
|
||||
rejectNonExistentValues: true
|
||||
)
|
||||
|
||||
# tunnel events to make sure core workflow does know
|
||||
|
@ -359,8 +365,6 @@ class App.TicketCreate extends App.Controller
|
|||
events:
|
||||
'change [name=customer_id]': @localUserInfo
|
||||
handlersConfig: handlersTunnel
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
autofocus: true
|
||||
params: params
|
||||
taskKey: @taskKey
|
||||
|
@ -376,6 +380,8 @@ class App.TicketCreate extends App.Controller
|
|||
handlersConfig: handlersTunnel
|
||||
params: params
|
||||
taskKey: @taskKey
|
||||
richTextUploadRenderCallback: @updateTaskManagerAttachments
|
||||
richTextUploadDeleteCallback: @updateTaskManagerAttachments
|
||||
)
|
||||
@controllerFormCreateBottom = new App.ControllerForm(
|
||||
el: @$('.ticket-form-bottom')
|
||||
|
@ -383,8 +389,6 @@ class App.TicketCreate extends App.Controller
|
|||
model: App.Ticket
|
||||
screen: 'create_bottom'
|
||||
handlersConfig: handlersTunnel
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: params
|
||||
taskKey: @taskKey
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class TicketCreateFormHanderSignature
|
||||
class TicketCreateFormHandlerSignature
|
||||
|
||||
@run: (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !attribute
|
||||
|
@ -19,10 +19,7 @@ class TicketCreateFormHanderSignature
|
|||
if App.Utils.signatureCheck(currentBody.html() || '', signatureFinished)
|
||||
|
||||
# if signature has changed, in case remove old signature
|
||||
signature_id = ui.el.closest('.content').find('[data-signature=true]').data('signature-id')
|
||||
if signature_id && signature_id.toString() isnt signature.id.toString()
|
||||
|
||||
ui.el.closest('.content').find('[data-signature="true"]').remove()
|
||||
ui.el.closest('.content').find('[data-signature="true"]').remove()
|
||||
|
||||
if !App.Utils.htmlLastLineEmpty(currentBody)
|
||||
currentBody.append('<br><br>')
|
||||
|
@ -35,4 +32,4 @@ class TicketCreateFormHanderSignature
|
|||
else
|
||||
ui.el.closest('.content').find('[data-name="body"]').find('[data-signature=true]').remove()
|
||||
|
||||
App.Config.set('200-ticketFormSignature', TicketCreateFormHanderSignature, 'TicketCreateFormHandler')
|
||||
App.Config.set('200-ticketFormSignature', TicketCreateFormHandlerSignature, 'TicketCreateFormHandler')
|
|
@ -13,13 +13,7 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
@form_id = App.ControllerForm.formId()
|
||||
|
||||
@navupdate '#customer_ticket_new'
|
||||
|
||||
load = (data) =>
|
||||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@render()
|
||||
@bindId = App.TicketCreateCollection.bind(load, false)
|
||||
App.TicketCreateCollection.fetch()
|
||||
@render()
|
||||
|
||||
render: (template = {}) ->
|
||||
if !@Config.get('customer_ticket_create')
|
||||
|
@ -43,8 +37,6 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
form_id: @form_id
|
||||
model: App.Ticket
|
||||
screen: 'create_middle'
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
noFieldset: true
|
||||
handlersConfig: handlers
|
||||
|
@ -70,8 +62,6 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
model: App.Ticket
|
||||
screen: 'create_top'
|
||||
handlersConfig: handlersTunnel
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
autofocus: true
|
||||
params: defaults
|
||||
)
|
||||
|
@ -83,8 +73,6 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
events:
|
||||
'fileUploadStart .richtext': => @submitDisable()
|
||||
'fileUploadStop .richtext': => @submitEnable()
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
handlersConfig: handlersTunnel
|
||||
)
|
||||
|
@ -95,8 +83,6 @@ class CustomerTicketCreate extends App.ControllerAppContent
|
|||
model: App.Ticket
|
||||
screen: 'create_bottom'
|
||||
handlersConfig: handlersTunnel
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
)
|
||||
|
||||
|
|
|
@ -91,20 +91,7 @@ class GettingStartedChannelEmail extends App.ControllerWizardFullScreen
|
|||
ui.hide('options::folder')
|
||||
ui.hide('options::keep_on_server')
|
||||
|
||||
handlePort = (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !params
|
||||
return if !params.options
|
||||
currentPort = @$('.base-inbound-settings [name="options::port"]').val()
|
||||
if params.options.ssl is true
|
||||
if !currentPort
|
||||
@$('.base-inbound-settings [name="options::port"]').val('993')
|
||||
return
|
||||
if params.options.ssl is false
|
||||
if !currentPort || currentPort is '993'
|
||||
@$('.base-inbound-settings [name="options::port"]').val('143')
|
||||
return
|
||||
|
||||
new App.ControllerForm(
|
||||
form = new App.ControllerForm(
|
||||
el: @$('.base-inbound-settings')
|
||||
model:
|
||||
configure_attributes: configureAttributesInbound
|
||||
|
@ -112,10 +99,16 @@ class GettingStartedChannelEmail extends App.ControllerWizardFullScreen
|
|||
params: @account.inbound
|
||||
handlers: [
|
||||
showHideFolder,
|
||||
handlePort,
|
||||
]
|
||||
)
|
||||
|
||||
form.el.find("select[name='options::ssl']").off('change').on('change', (e) ->
|
||||
if $(e.target).val() is 'true'
|
||||
form.el.find("[name='options::port']").val('993')
|
||||
else
|
||||
form.el.find("[name='options::port']").val('143')
|
||||
)
|
||||
|
||||
toggleOutboundAdapter: =>
|
||||
|
||||
# fill user / password based on intro info
|
||||
|
|
|
@ -165,7 +165,7 @@ class ImportFreshdesk extends App.ControllerWizardFullScreen
|
|||
@$('.js-error').addClass('hide')
|
||||
|
||||
if !_.isEmpty(data.finished_at) && _.isEmpty(data.result['error'])
|
||||
window.location.reload()
|
||||
@redirectToLogin()
|
||||
return
|
||||
|
||||
if !_.isEmpty(data.result)
|
||||
|
|
|
@ -163,7 +163,7 @@ class ImportZendesk extends App.ControllerWizardFullScreen
|
|||
@$('.js-error').addClass('hide')
|
||||
|
||||
if !_.isEmpty(data.finished_at) && _.isEmpty(data.result['error'])
|
||||
window.location.reload()
|
||||
@redirectToLogin()
|
||||
return
|
||||
|
||||
if !_.isEmpty(data.result)
|
||||
|
|
|
@ -247,8 +247,6 @@ class Edit extends App.ControllerGenericEdit
|
|||
#if attribute.name is 'data_type'
|
||||
# attribute.disabled = true
|
||||
|
||||
console.log('configure_attributes', configure_attributes)
|
||||
|
||||
@controller = new App.ControllerForm(
|
||||
model:
|
||||
configure_attributes: configure_attributes
|
||||
|
|
|
@ -153,7 +153,7 @@ class Object extends App.ControllerObserver
|
|||
elLocal.find('.js-userList').html(members)
|
||||
)
|
||||
|
||||
if @organization.member_ids.length < @memberLimit
|
||||
if @organization.member_ids.length <= @memberLimit
|
||||
@el.find('.js-showMoreMembers').parent().addClass('hidden')
|
||||
else
|
||||
@el.find('.js-showMoreMembers').parent().removeClass('hidden')
|
||||
|
|
|
@ -171,6 +171,13 @@ class Graph extends App.Controller
|
|||
backends: @params.backendSelected
|
||||
)
|
||||
processData: true
|
||||
error: (xhr) =>
|
||||
return if !_.include([401, 403, 404, 422, 502], xhr.status)
|
||||
|
||||
@bodyModal = new App.ControllerTechnicalErrorModal(
|
||||
head: 'Cannot generate report'
|
||||
contentCode: xhr.responseJSON.error
|
||||
)
|
||||
success: (data) =>
|
||||
@update(data)
|
||||
@delay(@render, interval, 'report-update', 'page')
|
||||
|
|
|
@ -56,13 +56,9 @@ class App.Search extends App.Controller
|
|||
@navupdate(url: '#search', type: 'menu')
|
||||
return if _.isEmpty(params.query)
|
||||
|
||||
@$('.js-search').val(params.query).trigger('change')
|
||||
return if @shown
|
||||
|
||||
@search(1000, true)
|
||||
@$('.js-search').val(params.query).trigger('keyup')
|
||||
|
||||
hide: ->
|
||||
@shown = false
|
||||
if @table
|
||||
@table.hide()
|
||||
|
||||
|
@ -108,6 +104,7 @@ class App.Search extends App.Controller
|
|||
return
|
||||
|
||||
# on other keys, show result
|
||||
@navigate "#search/#{encodeURIComponent(@searchInput.val())}"
|
||||
@search(0)
|
||||
|
||||
empty: =>
|
||||
|
|
|
@ -26,12 +26,6 @@ class Sla extends App.ControllerSubContent
|
|||
sortBy: 'name'
|
||||
)
|
||||
for sla in slas
|
||||
if sla.first_response_time
|
||||
sla.first_response_time_in_text = @toText(sla.first_response_time)
|
||||
if sla.update_time
|
||||
sla.update_time_in_text = @toText(sla.update_time)
|
||||
if sla.solution_time
|
||||
sla.solution_time_in_text = @toText(sla.solution_time)
|
||||
sla.rules = App.UiElement.ticket_selector.humanText(sla.condition)
|
||||
sla.calendar = App.Calendar.find(sla.calendar_id)
|
||||
|
||||
|
@ -95,21 +89,8 @@ class Sla extends App.ControllerSubContent
|
|||
|
||||
description: (e) =>
|
||||
new App.ControllerGenericDescription(
|
||||
description: App.Calendar.description
|
||||
description: App.Sla.description
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
toText: (m) ->
|
||||
m = parseInt(m)
|
||||
return if !m
|
||||
minutes = m % 60
|
||||
hours = Math.floor(m / 60)
|
||||
|
||||
if minutes < 10
|
||||
minutes = "0#{minutes}"
|
||||
if hours < 10
|
||||
hours = "0#{hours}"
|
||||
|
||||
"#{hours}:#{minutes}"
|
||||
|
||||
App.Config.set('Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Sla, permission: ['admin.sla'] }, 'NavBarAdmin')
|
||||
|
|
|
@ -206,22 +206,13 @@ class App.TicketOverview extends App.Controller
|
|||
article: article
|
||||
)
|
||||
ticket.article = article
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
console.log('record, settings, details', record, settings, details)
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -234,21 +225,13 @@ class App.TicketOverview extends App.Controller
|
|||
ticket.owner_id = id
|
||||
if !_.isEmpty(groupId)
|
||||
ticket.group_id = groupId
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', settings.error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -259,21 +242,13 @@ class App.TicketOverview extends App.Controller
|
|||
#console.log "perform action #{action} with id #{id} on ", $(item).val()
|
||||
ticket = App.Ticket.find($(item).val())
|
||||
ticket.group_id = id
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
|
|
|
@ -493,16 +493,22 @@ class App.TicketZoom extends App.Controller
|
|||
@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: @
|
||||
callbackFileUploadStart: @submitDisable
|
||||
callbackFileUploadStop: @submitEnable
|
||||
ticket: @ticket
|
||||
ticket_id: @ticket_id
|
||||
el: elLocal.find('.article-new')
|
||||
formMeta: @formMeta
|
||||
form_id: @form_id
|
||||
defaults: @taskGet('article')
|
||||
taskKey: @taskKey
|
||||
ui: @
|
||||
richTextUploadStartCallback: @submitDisable
|
||||
richTextUploadRenderCallback: (attachments) =>
|
||||
@submitEnable()
|
||||
@taskUpdateAttachments('article', attachments)
|
||||
@delay(@markForm, 250, 'ticket-zoom-form-update')
|
||||
richTextUploadDeleteCallback: (attachments) =>
|
||||
@taskUpdateAttachments('article', attachments)
|
||||
@delay(@markForm, 250, 'ticket-zoom-form-update')
|
||||
)
|
||||
|
||||
@highligher = new App.TicketZoomHighlighter(
|
||||
|
@ -641,6 +647,7 @@ class App.TicketZoom extends App.Controller
|
|||
# update changes in ui
|
||||
currentStore = @currentStore()
|
||||
modelDiff = @formDiff(currentParams, currentStore)
|
||||
return if _.isEmpty(modelDiff)
|
||||
|
||||
# set followup state if needed
|
||||
@setDefaultFollowUpState(modelDiff, currentStore)
|
||||
|
@ -720,7 +727,7 @@ class App.TicketZoom extends App.Controller
|
|||
# add attachments if exist
|
||||
attachmentCount = @$('.article-add .textBubble .attachments .attachment').length
|
||||
if attachmentCount > 0
|
||||
currentParams.article.attachments = true
|
||||
currentParams.article.attachments = attachmentCount
|
||||
else
|
||||
delete currentParams.article.attachments
|
||||
|
||||
|
@ -735,6 +742,14 @@ class App.TicketZoom extends App.Controller
|
|||
|
||||
# do not compare null or undefined value
|
||||
if currentStore.ticket
|
||||
|
||||
# make sure that the compared state is same in local storage and
|
||||
# rendered html. Else we could have race conditions of data
|
||||
# which is not rendered yet
|
||||
renderedUpdatedAt = @el.find('.edit').attr('data-ticket-updated-at')
|
||||
return if !renderedUpdatedAt
|
||||
return if currentStore.ticket.updated_at.toString() isnt renderedUpdatedAt
|
||||
|
||||
for key, value of currentStore.ticket
|
||||
if value is null || value is undefined
|
||||
currentStore.ticket[key] = ''
|
||||
|
@ -969,15 +984,14 @@ class App.TicketZoom extends App.Controller
|
|||
processData: true
|
||||
success: (data) =>
|
||||
|
||||
#App.SessionStorage.set(@key, data)
|
||||
@load(data, true, true)
|
||||
|
||||
# reset article - should not be resubmitted on next ticket update
|
||||
ticket.article = undefined
|
||||
|
||||
# reset form after save
|
||||
@reset()
|
||||
|
||||
@load(data, false, true)
|
||||
|
||||
if @sidebarWidget
|
||||
@sidebarWidget.commit()
|
||||
|
||||
|
@ -1070,6 +1084,13 @@ class App.TicketZoom extends App.Controller
|
|||
|
||||
App.TaskManager.update(@taskKey, taskData)
|
||||
|
||||
taskUpdateAttachments: (area, attachments) =>
|
||||
taskData = App.TaskManager.get(@taskKey)
|
||||
return if !taskData
|
||||
|
||||
taskData.attachments = attachments
|
||||
App.TaskManager.update(@taskKey, taskData)
|
||||
|
||||
taskUpdateAll: (data) =>
|
||||
@localTaskData = data
|
||||
@localTaskData.article['form_id'] = @form_id
|
||||
|
@ -1092,7 +1113,7 @@ class App.TicketZoom extends App.Controller
|
|||
@localTaskData =
|
||||
ticket: {}
|
||||
article: {}
|
||||
App.TaskManager.update(@taskKey, { 'state': @localTaskData })
|
||||
App.TaskManager.update(@taskKey, { 'state': @localTaskData, attachments: [] })
|
||||
|
||||
renderOverviewNavigator: (parentEl) ->
|
||||
new App.TicketZoomOverviewNavigator(
|
||||
|
|
|
@ -198,17 +198,17 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
inputField: @$('.article-attachment input')
|
||||
|
||||
onFileStartCallback: =>
|
||||
@callbackFileUploadStart?()
|
||||
@richTextUploadStartCallback?()
|
||||
|
||||
onFileCompletedCallback: (response) =>
|
||||
@attachments.push response.data
|
||||
@renderAttachment(response.data)
|
||||
@$('.article-attachment input').val('')
|
||||
|
||||
@callbackFileUploadStop?()
|
||||
@richTextUploadRenderCallback?(@attachments)
|
||||
|
||||
onFileAbortedCallback: =>
|
||||
@callbackFileUploadStop?()
|
||||
@richTextUploadRenderCallback?(@attachments)
|
||||
|
||||
attachmentPlaceholder: @attachmentPlaceholder
|
||||
attachmentUpload: @attachmentUpload
|
||||
|
@ -287,7 +287,6 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
params.preferences ||= {}
|
||||
params.preferences.security = @paramsSecurity()
|
||||
|
||||
params.attachments = @attachments
|
||||
params
|
||||
|
||||
validate: =>
|
||||
|
@ -624,6 +623,8 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
$(e.currentTarget).closest('.attachment').remove()
|
||||
if element.find('.attachment').length == 0
|
||||
element.empty()
|
||||
|
||||
@richTextUploadDeleteCallback?(@attachments)
|
||||
)
|
||||
|
||||
actions: ->
|
||||
|
|
|
@ -79,6 +79,12 @@ class App.FormHandlerCoreWorkflow
|
|||
return if !App.WebSocket.channel()
|
||||
return !App.Config.get('core_workflow_ajax_mode')
|
||||
|
||||
@restrictValuesAttributeCache: (attribute, values) ->
|
||||
result = { values: values }
|
||||
return result if !attribute.relation
|
||||
result.lastUpdatedAt = App[attribute.relation].lastUpdatedAt()
|
||||
return result
|
||||
|
||||
# restricts the dropdown and tree select values of a form
|
||||
@restrictValues: (classname, form, ui, attributes, params, data) ->
|
||||
return if _.isEmpty(data.restrict_values)
|
||||
|
@ -111,17 +117,17 @@ class App.FormHandlerCoreWorkflow
|
|||
# cache state for performance and only run
|
||||
# if values or param differ
|
||||
if coreWorkflowRestrictions?[classname]?[item.name]
|
||||
compare = values
|
||||
compare = App.FormHandlerCoreWorkflow.restrictValuesAttributeCache(attribute, values)
|
||||
continue if _.isEqual(coreWorkflowRestrictions[classname][item.name], compare)
|
||||
|
||||
coreWorkflowRestrictions[classname] ||= {}
|
||||
coreWorkflowRestrictions[classname][item.name] = values
|
||||
coreWorkflowRestrictions[classname][item.name] = App.FormHandlerCoreWorkflow.restrictValuesAttributeCache(attribute, values)
|
||||
|
||||
valueFound = false
|
||||
for value in values
|
||||
|
||||
# false values are valid values e.g. for boolean fields (be careful)
|
||||
if value isnt undefined && paramValue isnt undefined
|
||||
if value isnt undefined && paramValue isnt undefined && value isnt null && paramValue isnt null
|
||||
if value.toString() == paramValue.toString()
|
||||
valueFound = true
|
||||
break
|
||||
|
@ -133,6 +139,11 @@ class App.FormHandlerCoreWorkflow
|
|||
if valueFound
|
||||
item.default = paramValue
|
||||
item.newValue = paramValue
|
||||
else if params.id
|
||||
obj = App[ui.model.className].find(params.id)
|
||||
if obj && obj[item.name]
|
||||
item.default = obj[item.name]
|
||||
item.newValue = obj[item.name]
|
||||
else
|
||||
item.default = ''
|
||||
item.newValue = ''
|
||||
|
@ -299,6 +310,11 @@ class App.FormHandlerCoreWorkflow
|
|||
screen: ui.screen
|
||||
}
|
||||
|
||||
# send last changed attribute only once for has changed condition
|
||||
if ui.lastChangedAttribute
|
||||
requestData.last_changed_attribute = ui.lastChangedAttribute
|
||||
ui.lastChangedAttribute = '-'
|
||||
|
||||
if App.FormHandlerCoreWorkflow.useWebSockets()
|
||||
App.WebSocket.send(requestData)
|
||||
else
|
||||
|
@ -324,8 +340,12 @@ class App.FormHandlerCoreWorkflow
|
|||
|
||||
# get params and add id from ui if needed
|
||||
params = App.FormHandlerCoreWorkflow.cleanParams(params_ref)
|
||||
if ui?.params?.id
|
||||
|
||||
# add object id for edit screens
|
||||
if ui?.params?.id && ui.screen.match(/edit/)
|
||||
params.id = ui.params.id
|
||||
else
|
||||
delete params.id
|
||||
|
||||
# skip double checks
|
||||
return if _.isEqual(coreWorkflowParams[classname], params)
|
||||
|
|
|
@ -29,41 +29,33 @@ class Edit extends App.Controller
|
|||
# for the new ticket + eventually changed task state
|
||||
@formMeta.core_workflow = undefined
|
||||
|
||||
if followUpPossible == 'new_ticket' && ticketState != 'closed' ||
|
||||
followUpPossible != 'new_ticket' ||
|
||||
@permissionCheck('admin') || @ticket.currentView() is 'agent'
|
||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||
elReplace: @el
|
||||
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||
screen: 'edit'
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
isDisabled: !@ticket.editable()
|
||||
taskKey: @taskKey
|
||||
core_workflow: {
|
||||
callbacks: [@markForm]
|
||||
}
|
||||
#bookmarkable: true
|
||||
)
|
||||
else
|
||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||
elReplace: @el
|
||||
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||
screen: 'edit'
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
isDisabled: @ticket.editable()
|
||||
taskKey: @taskKey
|
||||
core_workflow: {
|
||||
callbacks: [@markForm]
|
||||
}
|
||||
#bookmarkable: true
|
||||
)
|
||||
editable = @ticket.editable()
|
||||
if followUpPossible == 'new_ticket' && ticketState != 'closed' || followUpPossible != 'new_ticket' || @permissionCheck('admin') || @ticket.currentView() is 'agent'
|
||||
editable = !editable
|
||||
|
||||
# reset updated_at for the sidbar because we render a new state
|
||||
# it is used to compare the ticket with the rendered data later
|
||||
# and needed to prevent race conditions
|
||||
@el.removeAttr('data-ticket-updated-at')
|
||||
|
||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||
elReplace: @el
|
||||
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||
screen: 'edit'
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
isDisabled: editable
|
||||
taskKey: @taskKey
|
||||
core_workflow: {
|
||||
callbacks: [@markForm]
|
||||
}
|
||||
#bookmarkable: true
|
||||
)
|
||||
|
||||
# set updated_at for the sidbar because we render a new state
|
||||
@el.attr('data-ticket-updated-at', defaults.updated_at)
|
||||
@markForm(true)
|
||||
|
||||
return if @resetBind
|
||||
|
|
|
@ -33,7 +33,7 @@ class App.WidgetOrganization extends App.Controller
|
|||
elLocal.find('.js-userList').html(members)
|
||||
)
|
||||
|
||||
if @organization.member_ids.length < @memberLimit
|
||||
if @organization.member_ids.length <= @memberLimit
|
||||
@el.find('.js-showMoreMembers').parent().addClass('hidden')
|
||||
else
|
||||
@el.find('.js-showMoreMembers').parent().removeClass('hidden')
|
||||
|
|
|
@ -14,6 +14,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
'click': 'stopPropagation'
|
||||
'change .js-objectId': 'executeCallback'
|
||||
'click .js-remove': 'removeThisToken'
|
||||
'click .js-showMoreMembers': 'showMoreMembers'
|
||||
|
||||
elements:
|
||||
'.recipientList': 'recipientList'
|
||||
|
@ -251,14 +252,42 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
objectCount: objectCount
|
||||
)
|
||||
|
||||
showMoreMembers: (e) ->
|
||||
@preventDefaultAndStopPropagation(e)
|
||||
|
||||
memberElement = $(e.target).closest('.js-showMoreMembers')
|
||||
oldMemberLimit = memberElement.attr('organization-member-limit')
|
||||
newMemberLimit = (parseInt(oldMemberLimit / 25) + 1) * 25
|
||||
memberElement.attr('organization-member-limit', newMemberLimit)
|
||||
|
||||
@renderMembers(memberElement, oldMemberLimit, newMemberLimit)
|
||||
|
||||
renderMembers: (element, fromMemberLimit, toMemberLimit) ->
|
||||
id = element.closest('.recipientList-organizationMembers').attr('organization-id')
|
||||
organization = App.Organization.find(id)
|
||||
|
||||
# only first 10 members else we would need more ajax requests
|
||||
organization.members(fromMemberLimit, toMemberLimit, (users) =>
|
||||
for user in users
|
||||
element.before(@buildObjectItem(user))
|
||||
|
||||
if element.closest('ul').hasClass('is-shown')
|
||||
@showOrganizationMembers(undefined, element.closest('ul'))
|
||||
)
|
||||
|
||||
if organization.member_ids.length <= toMemberLimit
|
||||
element.addClass('hidden')
|
||||
else
|
||||
element.removeClass('hidden')
|
||||
|
||||
buildOrganizationMembers: (organization) =>
|
||||
organizationMemebers = $( App.view(@templateOrganizationItemMembers)(
|
||||
organizationMembers = $( App.view(@templateOrganizationItemMembers)(
|
||||
organization: organization
|
||||
) )
|
||||
if organization[@referenceAttribute]
|
||||
for objectId in organization[@referenceAttribute]
|
||||
object = App[@objectSingle].fullLocal(objectId)
|
||||
organizationMemebers.append(@buildObjectItem(object))
|
||||
|
||||
@renderMembers(organizationMembers.find('.js-showMoreMembers'), 0, 10)
|
||||
|
||||
organizationMembers
|
||||
|
||||
buildObjectItem: (object) =>
|
||||
icon = @objectIcon
|
||||
|
@ -404,8 +433,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
e.stopPropagation()
|
||||
listEntry = $(e.currentTarget)
|
||||
|
||||
organizationId = listEntry.data('organization-id')
|
||||
|
||||
organizationId = listEntry.data('organization-id') || listEntry.attr('organization-id')
|
||||
@organizationList = @$("[organization-id=#{ organizationId }]")
|
||||
|
||||
return if !@organizationList.get(0)
|
||||
|
|
|
@ -102,12 +102,18 @@ class _ajaxSingleton
|
|||
# do not show any error message with code 502
|
||||
return if status is 502
|
||||
|
||||
try
|
||||
json = JSON.parse(detail)
|
||||
text = json.error_human || json.error
|
||||
|
||||
text = detail if !text
|
||||
|
||||
escaped = App.Utils.htmlEscape(text)
|
||||
|
||||
# show error message
|
||||
new App.ControllerModal(
|
||||
head: "StatusCode: #{status}"
|
||||
contentInline: "<pre>#{App.Utils.htmlEscape(detail)}</pre>"
|
||||
buttonClose: true
|
||||
buttonSubmit: false
|
||||
new App.ControllerTechnicalErrorModal(
|
||||
contentCode: escaped
|
||||
head: "StatusCode: #{status}"
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class App.FullQuoteHeader
|
|||
|
||||
@fullQuoteHeaderForwardTo: (article) ->
|
||||
if article.type.name is 'email' || article.type.name is 'web'
|
||||
@fullQuoteHeaderEnsurePrivacy(article.to) || article.to
|
||||
@fullQuoteHeaderEnsureMultiPrivacy(article.to)
|
||||
else if article.sender.name is 'Customer' && article.type.name is 'phone'
|
||||
if email_address_id = App.Group.findByAttribute('name', article.to)?.email_address_id
|
||||
App.EmailAddress.find(email_address_id).displayName()
|
||||
|
@ -36,15 +36,17 @@ class App.FullQuoteHeader
|
|||
article.to
|
||||
else if article.sender.name is 'Agent' && article.type.name is 'phone'
|
||||
ticket = App.Ticket.find article.ticket_id
|
||||
@fullQuoteHeaderEnsurePrivacy(ticket.customer_id) || @fullQuoteHeaderEnsurePrivacy(article.to) || article.to
|
||||
@fullQuoteHeaderEnsurePrivacy(ticket.customer_id) || @fullQuoteHeaderEnsureMultiPrivacy(article.to)
|
||||
else
|
||||
article.to
|
||||
|
||||
@fullQuoteHeaderForwardCC: (article) ->
|
||||
return if !article.cc
|
||||
@fullQuoteHeaderEnsureMultiPrivacy(article.cc)
|
||||
|
||||
article
|
||||
.cc
|
||||
@fullQuoteHeaderEnsureMultiPrivacy: (input) ->
|
||||
return if !input
|
||||
|
||||
input
|
||||
.split(',')
|
||||
.map (elem) ->
|
||||
elem.trim()
|
||||
|
|
|
@ -8,7 +8,12 @@ class App.i18n
|
|||
@translateDeep: (input, args...) ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _i18nSingleton()
|
||||
_instance.translateDeep(input, args)
|
||||
_instance.translateDeep(input, args, false)
|
||||
|
||||
@translateDeepPlain: (input, args...) ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _i18nSingleton()
|
||||
_instance.translateDeep(input, args, true)
|
||||
|
||||
@translateContent: (string, args...) ->
|
||||
if _instance == undefined
|
||||
|
@ -230,17 +235,20 @@ class _i18nSingleton extends Spine.Module
|
|||
return string if !string
|
||||
@translate(string, args, true)
|
||||
|
||||
translateDeep: (input, args) =>
|
||||
translateDeep: (input, args, plain) =>
|
||||
if _.isArray(input)
|
||||
_.map input, (item) =>
|
||||
@translateDeep(item, args)
|
||||
@translateDeep(item, args, plain)
|
||||
else if _.isObject(input)
|
||||
_.reduce _.keys(input), (memo, item) =>
|
||||
memo[item] = @translateDeep(input[item])
|
||||
memo[item] = @translateDeep(input[item], args, plain)
|
||||
memo
|
||||
, {}
|
||||
else
|
||||
@translateInline(input, args)
|
||||
if plain
|
||||
@translatePlain(input, args)
|
||||
else
|
||||
@translateInline(input, args)
|
||||
|
||||
|
||||
translateContent: (string, args) =>
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
class _Singleton extends App._CollectionSingletonBase
|
||||
event: 'ticket_create_attributes'
|
||||
restEndpoint: '/ticket_create'
|
||||
|
||||
class App.TicketCreateCollection
|
||||
_instance = new _Singleton
|
||||
|
||||
@get: ->
|
||||
_instance.get()
|
||||
|
||||
@one: (callback, init = true) ->
|
||||
_instance.bind(callback, init, true)
|
||||
|
||||
@bind: (callback, init = true) ->
|
||||
_instance.bind(callback, init, false)
|
||||
|
||||
@unbind: (callback) ->
|
||||
_instance.unbind(callback)
|
||||
|
||||
@unbindById: (id) ->
|
||||
_instance.unbindById(id)
|
||||
|
||||
@trigger: ->
|
||||
_instance.trigger()
|
||||
|
||||
@fetch: ->
|
||||
_instance.fetch()
|
|
@ -1327,7 +1327,7 @@ class App.Utils
|
|||
if type is 'email' && !e.attrs.value.match(/@/) || e.attrs.value.match(/\s/)
|
||||
e.preventDefault()
|
||||
return false
|
||||
e.attrs.label = e.attrs.value
|
||||
e.attrs.label ||= e.attrs.value
|
||||
true
|
||||
)
|
||||
App.Delay.set(a, 500, undefined, 'tags')
|
||||
|
|
|
@ -654,6 +654,11 @@
|
|||
var tokensBefore = this.getTokensList()
|
||||
this.setTokens( this.$input.val(), true )
|
||||
|
||||
// remove token text was cleared while editing
|
||||
if (this.$input.data( 'edit' ) && !this.$input.val()) {
|
||||
this.$element.val( this.getTokensList() )
|
||||
}
|
||||
|
||||
if (tokensBefore == this.getTokensList() && this.$input.val().length)
|
||||
return false // No tokens were added, do nothing (prevent form submit)
|
||||
|
||||
|
|
|
@ -114,11 +114,22 @@
|
|||
sel = window.getSelection()
|
||||
if (sel) {
|
||||
node = $(sel.anchorNode)
|
||||
if (node && node.parent() && node.parent().is('blockquote')) {
|
||||
e.preventDefault()
|
||||
document.execCommand('Insertparagraph')
|
||||
document.execCommand('Outdent')
|
||||
return
|
||||
|
||||
if (node.closest('blockquote').length > 0) {
|
||||
|
||||
// Special handling when the line is not wrapped inside of a html element.
|
||||
if (!node.is('div') && node.parent().is('blockquote') && node.text()) {
|
||||
e.preventDefault()
|
||||
document.execCommand('formatBlock', false, 'div')
|
||||
document.execCommand('insertParagraph')
|
||||
return
|
||||
}
|
||||
if (!e.shiftKey && node && (node.is('blockquote') || (node.parent() && node.parent().is('blockquote')) || !node.text())) {
|
||||
e.preventDefault()
|
||||
document.execCommand('insertParagraph')
|
||||
document.execCommand('outdent')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,17 +41,33 @@ App.ViewHelpers =
|
|||
return '' if isNaN(parseInt(time))
|
||||
|
||||
# Hours, minutes and seconds
|
||||
hrs = ~~parseInt((time / 3600))
|
||||
hrs = ~~parseInt((time / 3600))
|
||||
mins = ~~parseInt(((time % 3600) / 60))
|
||||
secs = parseInt(time % 60)
|
||||
|
||||
# Output like "1:01" or "4:03:59" or "123:03:59"
|
||||
mins = "0#{mins}" if mins < 10
|
||||
secs = "0#{secs}" if secs < 10
|
||||
|
||||
if hrs > 0
|
||||
return "#{hrs}:#{mins}:#{secs}"
|
||||
|
||||
"#{mins}:#{secs}"
|
||||
|
||||
# define time_duration / hh:mm
|
||||
time_duration_hh_mm: (time_in_minutes) ->
|
||||
return '' if !time_in_minutes
|
||||
return '' if isNaN(parseInt(time_in_minutes))
|
||||
|
||||
# Hours, minutes and seconds
|
||||
hrs = ~~parseInt((time_in_minutes / 60))
|
||||
mins = ~~parseInt((time_in_minutes % 60))
|
||||
|
||||
hrs = "0#{hrs}" if hrs < 10
|
||||
mins = "0#{mins}" if mins < 10
|
||||
|
||||
"#{hrs}:#{mins}"
|
||||
|
||||
# define mask helper
|
||||
# mask an value like 'a***********yz'
|
||||
M: (item, start = 1, end = 2) ->
|
||||
|
|
|
@ -234,12 +234,11 @@ class Singleton extends Base
|
|||
|
||||
failResponse: (options) =>
|
||||
(xhr, statusText, error, settings) =>
|
||||
if options.failResponseNoTrigger isnt true
|
||||
switch settings.type
|
||||
when 'POST' then @createFailed()
|
||||
when 'DELETE' then @destroyFailed()
|
||||
# add errors to calllback
|
||||
@record.trigger('ajaxError', @record, xhr, statusText, error, settings)
|
||||
switch settings.type
|
||||
when 'POST' then @createFailed()
|
||||
when 'DELETE' then @destroyFailed()
|
||||
# add errors to calllback
|
||||
@record.trigger('ajaxError', @record, xhr, statusText, error, settings)
|
||||
|
||||
#options.fail?.call(@record, settings)
|
||||
detailsRaw = xhr.responseText
|
||||
|
@ -247,8 +246,7 @@ class Singleton extends Base
|
|||
details = JSON.parse(detailsRaw)
|
||||
options.fail?.call(@record, settings, details)
|
||||
|
||||
if options.failResponseNoTrigger isnt true
|
||||
@record.trigger('destroy', @record)
|
||||
@record.trigger('destroy', @record)
|
||||
# /add errors to calllback
|
||||
|
||||
createFailed: ->
|
||||
|
|
|
@ -916,20 +916,25 @@ set new attributes of model (remove already available attributes)
|
|||
|
||||
# use jquery instead of ._clone() because we need a deep copy of the obj
|
||||
@org_configure_attributes = $.extend(true, [], @configure_attributes)
|
||||
configure_attributes = $.extend(true, [], @configure_attributes)
|
||||
allAttributes = []
|
||||
for attribute in attributes
|
||||
@attributes.push attribute.name
|
||||
|
||||
found = false
|
||||
for attribute_model, index in @configure_attributes
|
||||
for attribute_model, index in configure_attributes
|
||||
continue if attribute_model.name != attribute.name
|
||||
|
||||
@configure_attributes[index] = _.extend(attribute_model, attribute)
|
||||
allAttributes.push $.extend(true, attribute_model, attribute)
|
||||
configure_attributes.splice(index, 1) # remove found attribute
|
||||
|
||||
found = true
|
||||
break
|
||||
|
||||
if !found
|
||||
@configure_attributes.push attribute
|
||||
allAttributes.push $.extend(true, {}, attribute)
|
||||
|
||||
@configure_attributes = $.extend(true, [], allAttributes.concat(configure_attributes))
|
||||
|
||||
@resetAttributes: ->
|
||||
return if _.isEmpty(@org_configure_attributes)
|
||||
|
|
|
@ -6,8 +6,8 @@ class App.ObjectManagerAttribute extends App.Model
|
|||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'display', display: 'Display', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'object', display: 'Object', tag: 'input', readonly: 1 },
|
||||
{ name: 'position', display: 'Position', tag: 'input', readonly: 1 },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'data_type', display: 'Format', tag: 'object_manager_attribute', null: false },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'position', display: 'Position', tag: 'integer', type: 'number', limit: 100, null: true },
|
||||
]
|
||||
|
|
|
@ -36,7 +36,7 @@ Using **Organisations** you can **group** customers. This has among others two i
|
|||
userResult = ->
|
||||
users = []
|
||||
for user_id in member_ids
|
||||
user = App.User.find(user_id)
|
||||
user = App.User.fullLocal(user_id)
|
||||
continue if !user
|
||||
users.push(user)
|
||||
return users
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class App.Sla extends App.Model
|
||||
@configure 'Sla', 'name', 'first_response_time', 'update_time', 'solution_time', 'condition', 'calendar_id'
|
||||
@configure 'Sla', 'name', 'first_response_time', 'response_time', 'update_time', 'solution_time', 'condition', 'calendar_id'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/slas'
|
||||
@configure_attributes = [
|
||||
|
@ -12,6 +12,7 @@ class App.Sla extends App.Model
|
|||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'first_response_time',skipRendering: true },
|
||||
{ name: 'response_time', skipRendering: true },
|
||||
{ name: 'update_time', skipRendering: true },
|
||||
{ name: 'solution_time', skipRendering: true },
|
||||
]
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
</div>
|
||||
<div class="form-group js-sure">
|
||||
<h3 class="danger-color"><%- @T('Warning') %></h3>
|
||||
<p class="danger-color"><%- @T('There is no rollback of this deletion possible. If you are absolutely sure to do this, then type in "%s" into the input.', App.i18n.translateInline('delete').toUpperCase()) %></p>
|
||||
<p class="danger-color"><%- @T('There is no rollback of this deletion possible. If you are absolutely sure to do this, then type in "%s" into the input.', App.i18n.translatePlain('delete').toUpperCase()) %></p>
|
||||
<%- @sure_html %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<input type="text" disabled="disabled" readonly="readonly" value="<%= @attribute.value %>" class="form-control" />
|
|
@ -20,8 +20,8 @@
|
|||
<td data-day="<%- id %>" data-slot="<%- slot %>" class="form-group day-time<%- ' is-active' if @hours[id].active %>">
|
||||
<label for="<%- id %>_start_time">from</label>
|
||||
<input type="text" id="<%- id %>_start_time" name="<%= @attribute.name %>::<%- id %>::start" value="<%- @hours[id].timeframes[slot][0] %>" data-day="<%- id %>" data-slot="<%- slot %>" data-i="0" class="form-control form-control--small time js-time">
|
||||
<label for="monday_end_time">till</label>
|
||||
<input type="text" id="monday_end_time" name="<%= @attribute.name %>::<%- id %>::end" value="<%- @hours[id].timeframes[slot][1] %>" data-day="<%- id %>" data-slot="<%- slot %>" data-i="1" class="form-control form-control--small time js-time">
|
||||
<label for="<%- id %>_end_time">till</label>
|
||||
<input type="text" id="<%- id %>_end_time" name="<%= @attribute.name %>::<%- id %>::end" value="<%- @hours[id].timeframes[slot][1] %>" data-day="<%- id %>" data-slot="<%- slot %>" data-i="1" class="form-control form-control--small time js-time">
|
||||
<% else: %>
|
||||
<td class="empty-cell">
|
||||
<% end %>
|
||||
|
@ -45,4 +45,4 @@
|
|||
<td data-day="<%- id %>" class="settings-list-action-cell js-add-time<%- ' is-active' if @hours[id].active %>">
|
||||
<%- @Icon('plus-small') %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</tbody>
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
<% if @attribute.multiple: %>
|
||||
<%- @tokens %>
|
||||
<% end %>
|
||||
<input name="<%- @attribute.name %>_completion" class="user-select token-input js-objectSelect" autocapitalize="off" placeholder="<%- @Ti(@attribute.placeholder) %>" autocomplete="off" <%= @attribute.autofocus %> role="textbox" aria-autocomplete="list" value="<%= @name %>" aria-haspopup="true">
|
||||
<input name="<%= @attribute.name %>_completion" class="user-select token-input js-objectSelect form-control" autocapitalize="off" placeholder="<%- @Ti(@attribute.placeholder) %>" autocomplete="off" <%= @attribute.autofocus %> role="textbox" aria-autocomplete="list" value="<%= @name %>" aria-haspopup="true">
|
||||
<% if @attribute.disableCreateObject isnt true: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-menu" aria-labelledby="customer_id">
|
||||
<ul class="recipientList" role="menu"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,4 +4,9 @@
|
|||
<%- @Icon('arrow-left') %>
|
||||
<span class="btn-label"><%- @T('Back') %></span>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<li class="recipientList-controls js-showMoreMembers" organization-member-limit="10">
|
||||
<div class="btn btn--action btn--onDark">
|
||||
<span class="btn-label"><%- @T('show more') %></span>
|
||||
</div>
|
||||
</ul>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<th><%- @T('Time') %> <span class="text-muted"><%- @T('in hours') %></span>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="form-group">
|
||||
<tr>
|
||||
<td class="u-positionOrigin">
|
||||
<label class="checkbox-replacement checkbox-replacement--fullscreen dont-grey-out">
|
||||
<input type="checkbox" class="js-activateRow" id="first_response_time" name="first_response_time_enabled">
|
||||
|
@ -16,25 +16,51 @@
|
|||
<td class="u-clickable js-forward-click">
|
||||
<div><%- @T('First Response Time') %></div>
|
||||
<p class="subtle"><%- @T('Timeframe for the first response.') %></p>
|
||||
<td class="u-clickable js-focus-input">
|
||||
<td class="u-clickable js-focus-input form-group">
|
||||
<input type="hidden" name="first_response_time" value="<%= @first_response_time %>" class="js-timeConvertTo">
|
||||
<input type="text" value="<%= @first_response_time_in_text %>" class="form-control form-control--small timeframe js-timeConvertFrom" placeholder="hh:mm" name="first_response_time_in_text" data-name="first_response_time">
|
||||
|
||||
<tr class="form-group">
|
||||
<tr>
|
||||
<td class="u-positionOrigin">
|
||||
<label class="checkbox-replacement checkbox-replacement--fullscreen dont-grey-out">
|
||||
<input type="checkbox" class="js-activateRow" id="update_time" name="update_time_enabled">
|
||||
<%- @Icon('checkbox', 'icon-unchecked') %>
|
||||
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
||||
</label>
|
||||
<td class="u-clickable js-forward-click">
|
||||
<div><%- @T('Update Time') %></div>
|
||||
<p class="subtle"><%- @T('Timeframe for every following response.') %></p>
|
||||
<td>
|
||||
<input type="hidden" name="update_time" value="<%= @update_time %>" class="js-timeConvertTo">
|
||||
<input type="text" value="<%= @update_time_in_text %>" class="form-control form-control--small timeframe js-timeConvertFrom" placeholder="hh:mm" name="update_time_in_text" data-name="update_time">
|
||||
<div class="u-clickable js-forward-click">
|
||||
<div><%- @T('Update Time') %></div>
|
||||
<p class="subtle"><%- @T('Timeframe between agent updates or for an agent to respond.') %></p>
|
||||
</div>
|
||||
|
||||
<tr class="form-group">
|
||||
<p class="sla_radio_container js-forward-radio">
|
||||
<label class="inline-label radio-replacement">
|
||||
<input class="js-updateTypeSelector" type="radio" name="update_type" value="update" <% if @update_time: %>checked<% end %>>
|
||||
<%- @Icon('radio', 'icon-unchecked') %>
|
||||
<%- @Icon('radio-checked', 'icon-checked') %>
|
||||
<span class="label-text"><%- @T('between agent updates') %></span>
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<p class="sla_radio_container js-forward-radio u-clickable">
|
||||
<label class="inline-label radio-replacement">
|
||||
<input class="js-updateTypeSelector" type="radio" name="update_type" value="response" <% if @response_time: %>checked<% end %>>
|
||||
<%- @Icon('radio', 'icon-unchecked') %>
|
||||
<%- @Icon('radio-checked', 'icon-checked') %>
|
||||
<span class="label-text"><%- @T('for an agent to respond') %></span>
|
||||
</label>
|
||||
</p>
|
||||
<td class="form-group u-clickable js-focus-input u-clickable">
|
||||
<span class="form-group">
|
||||
<input type="hidden" name="update_time" value="<%= @update_time %>" class="js-timeConvertTo">
|
||||
<input type="text" value="<%= @update_time_in_text %>" class="form-control form-control--small timeframe js-timeConvertFrom <% if @response_time: %>hidden<% end %>" placeholder="hh:mm" name="update_time_in_text" data-name="update_time">
|
||||
</span>
|
||||
<span class="form-group">
|
||||
<input type="hidden" name="response_time" value="<%= @response_time %>" class="js-timeConvertTo">
|
||||
<input type="text" value="<%= @response_time_in_text %>" class="form-control form-control--small timeframe js-timeConvertFrom <% if !@response_time: %>hidden<% end %>" placeholder="hh:mm" name="response_time_in_text" data-name="response_time">
|
||||
</span>
|
||||
|
||||
<tr>
|
||||
<td class="u-positionOrigin">
|
||||
<label class="checkbox-replacement checkbox-replacement--fullscreen dont-grey-out">
|
||||
<input type="checkbox" id="solution_time" class="js-activateRow" name="solution_time_enabled">
|
||||
|
@ -44,7 +70,7 @@
|
|||
<td class="u-clickable js-forward-click">
|
||||
<div><%- @T('Solution Time') %></div>
|
||||
<p class="subtle"><%- @T('Timeframe for solving the problem.') %></p>
|
||||
<td>
|
||||
<td class="form-group u-clickable js-focus-input">
|
||||
<input type="hidden" name="solution_time" value="<%= @solution_time %>" class="js-timeConvertTo">
|
||||
<input type="text" value="<%= @solution_time_in_text %>" class="form-control form-control--small timeframe js-timeConvertFrom" placeholder="hh:mm" name="solution_time_in_text" data-name="solution_time">
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50%"><%- @T('Name') %>
|
||||
<th width="50%"><%- @T('Value') %>
|
||||
<th width="50%"><%- @T('Description') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
@ -91,24 +91,30 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<p><%- @T('Default caller id.') %>
|
||||
<p><%- @T('Settings') %>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50%"><%- @T('Default caller id') %>
|
||||
<th width="50%"><%- @T('Note') %>
|
||||
<th width="50%"><%- @T('Value') %>
|
||||
<th width="50%"><%- @T('Description') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!--
|
||||
<tr>
|
||||
<td class="settings-list-control-cell"><input name="default_caller_id" value="<%= @config.outbound.default_caller_id %>" placeholder="4930609854189" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><%- @T('Default caller id for outbound calls.') %>
|
||||
-->
|
||||
<tr>
|
||||
<td class="settings-list-control-cell js-viewLimit">
|
||||
<td class="settings-list-row-control"><%- @T('Shown records in caller log.') %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<h2><%- @T('User assignment to telephones') %></h2>
|
||||
|
||||
<p><%- @T('User assignment to telephones to be able to offer extended like open new ticket screen on answering a call.') %>
|
||||
|
@ -137,4 +143,4 @@
|
|||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary js-submit"><%- @T('Save') %></button>
|
||||
</form>
|
||||
</form>
|
||||
|
|
|
@ -79,38 +79,22 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<p><%- @T('Default caller id.') %>
|
||||
<p><%- @T('Settings') %>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50%"><%- @T('Default caller id') %>
|
||||
<th width="50%"><%- @T('Note') %>
|
||||
<th width="50%"><%- @T('Value') %>
|
||||
<th width="50%"><%- @T('Description') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-control-cell"><input name="default_caller_id" value="<%= @config.outbound.default_caller_id %>" placeholder="4930609854189" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><%- @T('Default caller id for outbound calls.') %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p><%- @T('In order for Zammad to access %s, a %s API user and password must be stored here', 'Sipgate', 'Sipgate') %>:<p>
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="20%"><%- @T('Type') %>
|
||||
<th width="80%"><%- @T('Content') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('API User') %>
|
||||
<td class="settings-list-control-cell"><input type="input" class="form-control form-control--small js-select" value="<%= @config.api_user %>" name="api_user" placeholder="someuser@example.com">
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('API Password') %>
|
||||
<td class="settings-list-control-cell"><input type="password" class="form-control form-control--small js-select" value="<%= @config.api_password %>" name="api_password" placeholder="">
|
||||
<td class="settings-list-control-cell js-viewLimit">
|
||||
<td class="settings-list-row-control"><%- @T('Shown records in caller log.') %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -143,4 +127,4 @@
|
|||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary js-submit"><%- @T('Save') %></button>
|
||||
</form>
|
||||
</form>
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
<th class=""><%- @T('Display') %></th>
|
||||
<th class=""><%- @T('Name') %></th>
|
||||
<th class="" style="width: 200px;"><%- @T('Type') %></th>
|
||||
<th><%- @T('Position') %></th>
|
||||
<th class="" style="width: 140px;"><%- @T('Action') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -64,6 +65,7 @@
|
|||
<td><%= item.display %></td>
|
||||
<td><%= item.name %></td>
|
||||
<td><%= item.data_type %></td>
|
||||
<td><%= item.position %></td>
|
||||
<td>
|
||||
<% if item.to_create is true: %>
|
||||
<%- @T('will be created') %>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<% if @object.member_ids: %>
|
||||
<hr>
|
||||
<div class="popover-block">
|
||||
<label><%- @T('Members') %></label>
|
||||
<label><%- @T('Members') %> (<%= @object.member_ids.length %>)</label>
|
||||
<div class="userList js-userList"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -34,15 +34,19 @@
|
|||
<div class="action-block action-block--flex">
|
||||
<div class="label"><%- @T('Escalation Times') %></div>
|
||||
<% if sla.first_response_time: %>
|
||||
<%- sla.first_response_time_in_text %> <%- @T('hours') %> - <%- @T('First Response Time') %>
|
||||
<%- @time_duration_hh_mm(sla.first_response_time) %> <%- @T('hours') %> - <%- @T('First Response Time') %>
|
||||
<% end %>
|
||||
<% if sla.response_time: %>
|
||||
<br>
|
||||
<%- @time_duration_hh_mm(sla.response_time) %> <%- @T('hours') %> - <%- @T('Response Time') %>
|
||||
<% end %>
|
||||
<% if sla.update_time: %>
|
||||
<br>
|
||||
<%- sla.update_time_in_text %> <%- @T('hours') %> - <%- @T('Update Time') %>
|
||||
<%- @time_duration_hh_mm(sla.update_time) %> <%- @T('hours') %> - <%- @T('Update Time') %>
|
||||
<% end %>
|
||||
<% if sla.solution_time: %>
|
||||
<br>
|
||||
<%- sla.solution_time_in_text %> <%- @T('hours') %> - <%- @T('Solution Time') %>
|
||||
<%- @time_duration_hh_mm(sla.solution_time) %> <%- @T('hours') %> - <%- @T('Solution Time') %>
|
||||
<% end %>
|
||||
<br>
|
||||
</div>
|
||||
|
@ -57,4 +61,4 @@
|
|||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
@font-face {
|
||||
font-family: 'Fira Sans';
|
||||
src: url('fonts/FiraSans-Bold.eot');
|
||||
src: url('fonts/FiraSans-Bold.woff2') format('woff2'),
|
||||
url('fonts/FiraSans-Bold.woff') format('woff'),
|
||||
url('fonts/FiraSans-Bold.ttf') format('truetype');
|
||||
src: url('assets/fonts/FiraSans-Bold.eot');
|
||||
src: url('assets/fonts/FiraSans-Bold.woff2') format('woff2'),
|
||||
url('assets/fonts/FiraSans-Bold.woff') format('woff'),
|
||||
url('assets/fonts/FiraSans-Bold.ttf') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fira Sans';
|
||||
src: url('fonts/FiraSans-Regular.eot');
|
||||
src: url('fonts/FiraSans-Regular.woff2') format('woff2'),
|
||||
url('fonts/FiraSans-Regular.woff') format('woff'),
|
||||
url('fonts/FiraSans-Regular.ttf') format('truetype');
|
||||
src: url('assets/fonts/FiraSans-Regular.eot');
|
||||
src: url('assets/fonts/FiraSans-Regular.woff2') format('woff2'),
|
||||
url('assets/fonts/FiraSans-Regular.woff') format('woff'),
|
||||
url('assets/fonts/FiraSans-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fira Sans';
|
||||
src: url('fonts/FiraSans-Medium.eot');
|
||||
src: url('fonts/FiraSans-Medium.woff2') format('woff2'),
|
||||
url('fonts/FiraSans-Medium.woff') format('woff'),
|
||||
url('fonts/FiraSans-Medium.ttf') format('truetype');
|
||||
src: url('assets/fonts/FiraSans-Medium.eot');
|
||||
src: url('assets/fonts/FiraSans-Medium.woff2') format('woff2'),
|
||||
url('assets/fonts/FiraSans-Medium.woff') format('woff'),
|
||||
url('assets/fonts/FiraSans-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fira Sans';
|
||||
src: url('fonts/FiraSans-Light.eot');
|
||||
src: url('fonts/FiraSans-Light.woff2') format('woff2'),
|
||||
url('fonts/FiraSans-Light.woff') format('woff'),
|
||||
url('fonts/FiraSans-Light.ttf') format('truetype');
|
||||
src: url('assets/fonts/FiraSans-Light.eot');
|
||||
src: url('assets/fonts/FiraSans-Light.woff2') format('woff2'),
|
||||
url('assets/fonts/FiraSans-Light.woff') format('woff'),
|
||||
url('assets/fonts/FiraSans-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -358,6 +358,26 @@ ol, ul {
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
code {
|
||||
background: hsla(0, 0%, 0%, 0.2);
|
||||
border-radius: 3px;
|
||||
box-decoration-break: clone;
|
||||
}
|
||||
|
||||
code,
|
||||
.hljs {
|
||||
padding: 2px 4px;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
background: none;
|
||||
}
|
||||
|
||||
pre code.hljs {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
padding: 9.5px;
|
||||
|
@ -371,30 +391,26 @@ pre {
|
|||
border: 1px solid hsl(0,0%,90%);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.modal-content pre {
|
||||
background: hsl(0, 0%, 97%);
|
||||
border: 1px solid hsl(0, 0%, 87%);
|
||||
}
|
||||
|
||||
pre code {
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
white-space: pre-wrap;
|
||||
background-color: transparent;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.hljs,
|
||||
code {
|
||||
background: none;
|
||||
padding: 2px 4px;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
overflow-x: auto;
|
||||
|
||||
code:not(.hljs) {
|
||||
border: 1px solid rgba(0,0,0,.2);
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
pre code.hljs {
|
||||
font-size: 1em;
|
||||
&.hljs {
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.textarea::placeholder,
|
||||
|
@ -13157,3 +13173,10 @@ span.is-disabled {
|
|||
.text-modules-box {
|
||||
max-height: 40vh;
|
||||
}
|
||||
|
||||
.sla_times {
|
||||
.sla_radio_container {
|
||||
padding-top: 0.5em;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,10 @@ module ApplicationController::Authenticates
|
|||
end
|
||||
|
||||
def authentication_check_only(auth_param = {})
|
||||
if Rails.env.test? && ENV['FAKE_SELENIUM_LOGIN_USER_ID'].present? && session[:user_id].blank?
|
||||
session[:user_id] = ENV['FAKE_SELENIUM_LOGIN_USER_ID'].to_i
|
||||
end
|
||||
|
||||
# logger.debug 'authentication_check'
|
||||
# logger.debug params.inspect
|
||||
# logger.debug session.inspect
|
||||
|
|
|
@ -38,6 +38,7 @@ class CalendarsController < ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
model_references_check(Calendar, params)
|
||||
model_destroy_render(Calendar, params)
|
||||
end
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@ curl http://localhost/api/v1/groups/{id} -v -u #{login}:#{password} -H "Content-
|
|||
=end
|
||||
|
||||
def destroy
|
||||
model_references_check(Group, params)
|
||||
model_destroy_render(Group, params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,9 +50,9 @@ class ImportFreshdeskController < ApplicationController
|
|||
|
||||
Setting.set('import_freshdesk_endpoint_key', params[:token])
|
||||
|
||||
result = Sequencer.process('Import::Freshdesk::ConnectionTest')
|
||||
connection_result = Sequencer.process('Import::Freshdesk::ConnectionTest')
|
||||
|
||||
if !result[:connected]
|
||||
if !connection_result[:connected]
|
||||
|
||||
Setting.set('import_freshdesk_endpoint_key', nil)
|
||||
|
||||
|
@ -63,6 +63,19 @@ class ImportFreshdeskController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
permission_result = Sequencer.process('Import::Freshdesk::PermissionCheck')
|
||||
|
||||
if !permission_result[:permission_present]
|
||||
|
||||
Setting.set('import_freshdesk_endpoint_key', nil)
|
||||
|
||||
render json: {
|
||||
result: 'invalid',
|
||||
message_human: 'Missing administrator permission!',
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
}
|
||||
|
|
|
@ -42,9 +42,7 @@ class Integration::SMIMEController < ApplicationController
|
|||
string = params[:file].read.force_encoding('utf-8')
|
||||
end
|
||||
|
||||
items = string.scan(%r{.+?-+END(?: TRUSTED)? CERTIFICATE-+}mi).each_with_object([]) do |cert, result|
|
||||
result << SMIMECertificate.create!(public_key: cert)
|
||||
end
|
||||
items = SMIMECertificate.create_certificates(string)
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
|
@ -73,14 +71,8 @@ class Integration::SMIMEController < ApplicationController
|
|||
|
||||
raise "Parameter 'data' or 'file' required." if string.blank?
|
||||
|
||||
private_key = OpenSSL::PKey.read(string, params[:secret])
|
||||
modulus = private_key.public_key.n.to_s(16)
|
||||
|
||||
certificate = SMIMECertificate.find_by(modulus: modulus)
|
||||
|
||||
raise Exceptions::UnprocessableEntity, 'Unable for find certificate for this private key.' if !certificate
|
||||
|
||||
certificate.update!(private_key: string, private_key_secret: params[:secret])
|
||||
SMIMECertificate.create_certificates(string)
|
||||
SMIMECertificate.create_private_keys(string, params[:secret])
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
|
|
|
@ -29,7 +29,7 @@ class ObjectManagerAttributesController < ApplicationController
|
|||
)
|
||||
raise Exceptions::UnprocessableEntity, 'already exists' if exists
|
||||
|
||||
add_attribute_using_params(permitted_params.merge(position: 1550), status: :created)
|
||||
add_attribute_using_params(permitted_params, status: :created)
|
||||
end
|
||||
|
||||
# PUT /object_manager_attributes/1
|
||||
|
|
|
@ -22,26 +22,34 @@ class ReportsController < ApplicationController
|
|||
get_params = params_all
|
||||
return if !get_params
|
||||
|
||||
result = {}
|
||||
get_params[:metric][:backend].each do |backend|
|
||||
condition = get_params[:profile].condition
|
||||
if backend[:condition]
|
||||
backend[:condition].merge(condition)
|
||||
else
|
||||
backend[:condition] = condition
|
||||
end
|
||||
next if !backend[:adapter]
|
||||
begin
|
||||
result = {}
|
||||
get_params[:metric][:backend].each do |backend|
|
||||
condition = get_params[:profile].condition
|
||||
if backend[:condition]
|
||||
backend[:condition].merge(condition)
|
||||
else
|
||||
backend[:condition] = condition
|
||||
end
|
||||
next if !backend[:adapter]
|
||||
|
||||
result[backend[:name]] = backend[:adapter].aggs(
|
||||
range_start: get_params[:start],
|
||||
range_end: get_params[:stop],
|
||||
interval: get_params[:range],
|
||||
selector: backend[:condition],
|
||||
params: backend[:params],
|
||||
timezone: get_params[:timezone],
|
||||
timezone_offset: get_params[:timezone_offset],
|
||||
current_user: current_user
|
||||
)
|
||||
result[backend[:name]] = backend[:adapter].aggs(
|
||||
range_start: get_params[:start],
|
||||
range_end: get_params[:stop],
|
||||
interval: get_params[:range],
|
||||
selector: backend[:condition],
|
||||
params: backend[:params],
|
||||
timezone: get_params[:timezone],
|
||||
timezone_offset: get_params[:timezone_offset],
|
||||
current_user: current_user
|
||||
)
|
||||
end
|
||||
rescue => e
|
||||
if e.message.include? 'Conflicting date range'
|
||||
raise Exceptions::UnprocessableEntity, 'Conflicting date ranges. Please check your selected report profile.'
|
||||
end
|
||||
|
||||
raise e
|
||||
end
|
||||
|
||||
render json: {
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class TicketCreateScreenJob < ApplicationJob
|
||||
include HasActiveJobLock
|
||||
|
||||
def perform
|
||||
Sessions.list.each do |client_id, data|
|
||||
next if client_id.blank?
|
||||
|
||||
user_id = data&.dig(:user, 'id')
|
||||
next if user_id.blank?
|
||||
|
||||
user = User.lookup(id: user_id)
|
||||
next if !user&.permissions?('ticket.agent')
|
||||
|
||||
# get attributes to update
|
||||
ticket_create_attributes = Ticket::ScreenOptions.attributes_to_change(
|
||||
current_user: user,
|
||||
)
|
||||
|
||||
# no data exists
|
||||
next if ticket_create_attributes.blank?
|
||||
|
||||
Rails.logger.debug { "push ticket_create for user #{user.id}" }
|
||||
Sessions.send(client_id, {
|
||||
event: 'ticket_create_attributes',
|
||||
data: ticket_create_attributes,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
|
@ -373,18 +373,14 @@ returns
|
|||
end
|
||||
|
||||
# check if sla's are refer to an existing calendar
|
||||
default_calendar = Calendar.find_by(default: true)
|
||||
Sla.find_each do |sla|
|
||||
if !sla.calendar_id
|
||||
sla.calendar_id = default_calendar.id
|
||||
sla.save!
|
||||
next
|
||||
end
|
||||
if !Calendar.exists?(id: sla.calendar_id)
|
||||
if destroyed?
|
||||
default_calendar = Calendar.find_by(default: true)
|
||||
Sla.where(calendar_id: id).find_each do |sla|
|
||||
sla.calendar_id = default_calendar.id
|
||||
sla.save!
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class Channel::Driver::Sms::MessageBird < Channel::Driver::Sms::Base
|
|||
send_create(options, attr)
|
||||
true
|
||||
rescue => e
|
||||
Rails.logger.debug { "MessageBird error: #{e.inspect}" }
|
||||
Rails.logger.error { "MessageBird error: #{e.inspect}" }
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
|
|
@ -606,6 +606,20 @@ process unprocessable_mails (tmp/unprocessable_mail/*.eml) again
|
|||
if mail.html_part&.body.present?
|
||||
content_type = mail.html_part.mime_type || 'text/plain'
|
||||
body = body_text(mail.html_part, strict_html: true)
|
||||
elsif mail.text_part.present? && mail.all_parts.any? { |elem| elem.inline? && elem.content_type&.start_with?('image') }
|
||||
content_type = 'text/html'
|
||||
|
||||
body = mail
|
||||
.all_parts
|
||||
.reduce('') do |memo, part|
|
||||
if part.mime_type == 'text/plain' && !part.attachment?
|
||||
memo += body_text(part, strict_html: false).text2html
|
||||
elsif part.inline? && part.content_type&.start_with?('image')
|
||||
memo += "<img src=\'cid:#{part.cid}\'>"
|
||||
end
|
||||
|
||||
memo
|
||||
end
|
||||
elsif mail.text_part.present?
|
||||
content_type = 'text/plain'
|
||||
|
||||
|
@ -614,9 +628,6 @@ process unprocessable_mails (tmp/unprocessable_mail/*.eml) again
|
|||
.reduce('') do |memo, part|
|
||||
if part.mime_type == 'text/plain' && !part.attachment?
|
||||
memo += body_text(part, strict_html: false)
|
||||
elsif part.inline? && part.content_type&.start_with?('image')
|
||||
content_type = 'text/html'
|
||||
memo += "<img src=\'cid:#{part.cid}\'>"
|
||||
end
|
||||
|
||||
memo
|
||||
|
|
|
@ -14,8 +14,7 @@ module HasGroupRelationDefinition
|
|||
validates :access, presence: true
|
||||
validate :validate_access
|
||||
|
||||
after_save :touch_related
|
||||
after_destroy :touch_related
|
||||
after_commit :touch_related
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -251,7 +251,6 @@ module HasGroups
|
|||
yield
|
||||
self.group_access_buffer = nil
|
||||
cache_delete
|
||||
push_ticket_create_screen_background_job
|
||||
end
|
||||
|
||||
def process_group_access_buffer
|
||||
|
|
|
@ -6,9 +6,9 @@ module HasRoles
|
|||
included do
|
||||
has_and_belongs_to_many :roles,
|
||||
before_add: %i[validate_agent_limit_by_role validate_roles],
|
||||
after_add: %i[cache_update check_notifications push_ticket_create_screen_for_role_change],
|
||||
after_add: %i[cache_update check_notifications],
|
||||
before_remove: :last_admin_check_by_role,
|
||||
after_remove: %i[cache_update push_ticket_create_screen_for_role_change]
|
||||
after_remove: %i[cache_update]
|
||||
end
|
||||
|
||||
# Checks a given Group( ID) for given access(es) for the instance associated roles.
|
||||
|
@ -45,15 +45,6 @@ module HasRoles
|
|||
)
|
||||
end
|
||||
|
||||
def push_ticket_create_screen_for_role_change(role)
|
||||
return if Setting.get('import_mode')
|
||||
|
||||
permission = Permission.lookup(name: 'ticket.agent')
|
||||
return if !role.permissions.exists?(id: permission.id)
|
||||
|
||||
push_ticket_create_screen_background_job
|
||||
end
|
||||
|
||||
# methods defined here are going to extend the class, not the instance of it
|
||||
class_methods do
|
||||
|
||||
|
|
|
@ -216,23 +216,26 @@ reload search index with full data
|
|||
def search_index_reload
|
||||
tolerance = 10
|
||||
tolerance_count = 0
|
||||
batch_size = 100
|
||||
query = all.order(created_at: :desc)
|
||||
query = order(created_at: :desc)
|
||||
total = query.count
|
||||
query.find_in_batches(batch_size: batch_size).with_index do |group, batch|
|
||||
group.each do |item|
|
||||
next if item.ignore_search_indexing?(:destroy)
|
||||
|
||||
record_count = 0
|
||||
batch_size = 100
|
||||
query.as_batches(size: batch_size) do |record|
|
||||
if !record.ignore_search_indexing?(:destroy)
|
||||
begin
|
||||
item.search_index_update_backend
|
||||
record.search_index_update_backend
|
||||
rescue => e
|
||||
logger.error "Unable to send #{item.class}.find(#{item.id}).search_index_update_backend backend: #{e.inspect}"
|
||||
logger.error "Unable to send #{record.class}.find(#{record.id}).search_index_update_backend backend: #{e.inspect}"
|
||||
tolerance_count += 1
|
||||
sleep 15
|
||||
raise "Unable to send #{item.class}.find(#{item.id}).search_index_update_backend backend: #{e.inspect}" if tolerance_count == tolerance
|
||||
raise "Unable to send #{record.class}.find(#{record.id}).search_index_update_backend backend: #{e.inspect}" if tolerance_count == tolerance
|
||||
end
|
||||
end
|
||||
puts "\t#{[(batch + 1) * batch_size, total].min}/#{total}" # rubocop:disable Rails/Output
|
||||
|
||||
record_count += 1
|
||||
if (record_count % batch_size).zero? || record_count == total
|
||||
puts "\t#{record_count}/#{total}" # rubocop:disable Rails/Output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module HasTicketCreateScreenImpact
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_commit :push_ticket_create_screen
|
||||
end
|
||||
|
||||
def push_ticket_create_screen?
|
||||
return true if destroyed?
|
||||
|
||||
%w[id name active updated_at].any? do |attribute|
|
||||
saved_change_to_attribute?(attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def push_ticket_create_screen
|
||||
return if Setting.get('import_mode')
|
||||
return if !push_ticket_create_screen?
|
||||
|
||||
push_ticket_create_screen_background_job
|
||||
end
|
||||
|
||||
def push_ticket_create_screen_background_job
|
||||
TicketCreateScreenJob.set(wait: 10.seconds).perform_later
|
||||
end
|
||||
end
|
|
@ -56,22 +56,42 @@ class CoreWorkflow::Attributes
|
|||
result
|
||||
end
|
||||
|
||||
def selected
|
||||
if @payload['params']['id'] && payload_class.exists?(id: @payload['params']['id'])
|
||||
result = saved_only
|
||||
def exists?
|
||||
return if @payload['params']['id'].blank?
|
||||
|
||||
@exists ||= payload_class.exists?(id: @payload['params']['id'])
|
||||
end
|
||||
|
||||
def overwritten
|
||||
|
||||
# params loading and preparing is very expensive so cache it
|
||||
checksum = Digest::MD5.hexdigest(Marshal.dump(@payload['params']))
|
||||
return @overwritten[checksum] if @overwritten.present? && @overwritten[checksum]
|
||||
|
||||
@overwritten = {}
|
||||
@overwritten[checksum] = begin
|
||||
result = saved_only(dump: true)
|
||||
overwrite_selected(result)
|
||||
end
|
||||
end
|
||||
|
||||
def selected
|
||||
if exists?
|
||||
overwritten
|
||||
else
|
||||
selected_only
|
||||
end
|
||||
end
|
||||
|
||||
def saved_only
|
||||
return if @payload['params']['id'].blank?
|
||||
def saved_only(dump: false)
|
||||
return if !exists?
|
||||
|
||||
# dont use lookup here because the cache will not
|
||||
# know about new attributes and make crashes
|
||||
@saved_only ||= payload_class.find_by(id: @payload['params']['id'])
|
||||
|
||||
return @saved_only if !dump
|
||||
|
||||
# we use marshal here because clone still uses references and dup can't
|
||||
# detect changes for the rails object
|
||||
Marshal.load(Marshal.dump(@saved_only))
|
||||
|
@ -193,7 +213,7 @@ class CoreWorkflow::Attributes
|
|||
end
|
||||
|
||||
def attribute_options_relation?(attribute)
|
||||
attribute[:relation].present?
|
||||
attribute[:tag] == 'select' && attribute[:relation].present?
|
||||
end
|
||||
|
||||
def values(attribute)
|
||||
|
@ -238,13 +258,10 @@ class CoreWorkflow::Attributes
|
|||
end
|
||||
|
||||
def saved_attribute_value(attribute)
|
||||
saved_attribute_value = saved_only&.try(attribute[:name])
|
||||
|
||||
# special case for owner_id
|
||||
if saved_only&.class == Ticket && attribute[:name] == 'owner_id' && saved_attribute_value == 1
|
||||
saved_attribute_value = nil
|
||||
end
|
||||
return if saved_only&.class == Ticket && attribute[:name] == 'owner_id'
|
||||
|
||||
saved_attribute_value
|
||||
saved_only&.try(attribute[:name])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
class CoreWorkflow::Attributes::User < CoreWorkflow::Attributes::Base
|
||||
|
||||
def values
|
||||
return ticket_owner_id_bulk if @attributes.payload['screen'] == 'overview_bulk'
|
||||
return ticket_owner_id if @attributes.payload['class_name'] == 'Ticket' && @attribute[:name] == 'owner_id'
|
||||
if @attribute[:name] == 'owner_id' && @attributes.payload['class_name'] == 'Ticket'
|
||||
return ticket_owner_id_bulk if @attributes.payload['screen'] == 'overview_bulk'
|
||||
|
||||
return ticket_owner_id
|
||||
end
|
||||
|
||||
[]
|
||||
end
|
||||
|
@ -35,7 +38,10 @@ class CoreWorkflow::Attributes::User < CoreWorkflow::Attributes::Base
|
|||
def ticket_owner_id
|
||||
return [''] if @attributes.selected_only.group_id.blank?
|
||||
|
||||
group_owner_ids
|
||||
owner_ids = group_owner_ids
|
||||
return [''] if owner_ids.blank?
|
||||
|
||||
owner_ids
|
||||
end
|
||||
|
||||
def group_owner_ids
|
||||
|
|
|
@ -8,7 +8,7 @@ class CoreWorkflow::Condition
|
|||
def initialize(result_object:, workflow:)
|
||||
@user = result_object.user
|
||||
@payload = result_object.payload
|
||||
@workflow = workflow
|
||||
@workflow = workflow
|
||||
@attribute_object = result_object.attributes
|
||||
@result_object = result_object
|
||||
@check = nil
|
||||
|
|
|
@ -10,6 +10,10 @@ class CoreWorkflow::Condition::Backend
|
|||
|
||||
attr_reader :value
|
||||
|
||||
def field
|
||||
@key.sub(%r{.*\.}, '')
|
||||
end
|
||||
|
||||
def object?(object)
|
||||
@condition_object.attributes.instance_of?(object)
|
||||
end
|
||||
|
|
9
app/models/core_workflow/condition/changed_to.rb
Normal file
9
app/models/core_workflow/condition/changed_to.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::ChangedTo < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return if !CoreWorkflow::Condition::HasChanged.new(condition_object: @condition_object, key: @key, condition: @condition, value: @value).match
|
||||
|
||||
CoreWorkflow::Condition::Is.new(condition_object: @condition_object, key: @key, condition: @condition, value: @value).match
|
||||
end
|
||||
end
|
9
app/models/core_workflow/condition/has_changed.rb
Normal file
9
app/models/core_workflow/condition/has_changed.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class CoreWorkflow::Condition::HasChanged < CoreWorkflow::Condition::Backend
|
||||
def match
|
||||
return if @condition_object.payload['last_changed_attribute'] != field
|
||||
|
||||
true
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue