Merge branch 'develop' into private-te-refactoring-constantize
This commit is contained in:
commit
ae5ccc11df
31 changed files with 750 additions and 298 deletions
|
@ -366,14 +366,12 @@ browser:build:
|
||||||
<<: *test_capybara_definition
|
<<: *test_capybara_definition
|
||||||
variables:
|
variables:
|
||||||
RAILS_ENV: "test"
|
RAILS_ENV: "test"
|
||||||
NO_RESET_BEFORE_SUITE: "true"
|
|
||||||
BROWSER: "chrome"
|
BROWSER: "chrome"
|
||||||
|
|
||||||
.variables_capybara_ff_template: &variables_capybara_ff_definition
|
.variables_capybara_ff_template: &variables_capybara_ff_definition
|
||||||
<<: *test_capybara_definition
|
<<: *test_capybara_definition
|
||||||
variables:
|
variables:
|
||||||
RAILS_ENV: "test"
|
RAILS_ENV: "test"
|
||||||
NO_RESET_BEFORE_SUITE: "true"
|
|
||||||
BROWSER: "firefox"
|
BROWSER: "firefox"
|
||||||
|
|
||||||
test:browser:core:capybara_chrome_postgresql:
|
test:browser:core:capybara_chrome_postgresql:
|
||||||
|
|
|
@ -175,6 +175,8 @@ Rails/SkipsModelValidations:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Exclude:
|
Exclude:
|
||||||
- test/**/*
|
- test/**/*
|
||||||
|
- "**/*_spec.rb"
|
||||||
|
- "**/*_examples.rb"
|
||||||
|
|
||||||
Style/ClassAndModuleChildren:
|
Style/ClassAndModuleChildren:
|
||||||
Description: 'Checks style of children classes and modules.'
|
Description: 'Checks style of children classes and modules.'
|
||||||
|
|
31
Gemfile.lock
31
Gemfile.lock
|
@ -174,7 +174,7 @@ GEM
|
||||||
railties (>= 3.0.0)
|
railties (>= 3.0.0)
|
||||||
faker (1.9.1)
|
faker (1.9.1)
|
||||||
i18n (>= 0.7)
|
i18n (>= 0.7)
|
||||||
faraday (0.12.2)
|
faraday (0.15.4)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-http-cache (2.0.0)
|
faraday-http-cache (2.0.0)
|
||||||
faraday (~> 0.8)
|
faraday (~> 0.8)
|
||||||
|
@ -212,7 +212,7 @@ GEM
|
||||||
guard
|
guard
|
||||||
guard-compat (~> 1.1)
|
guard-compat (~> 1.1)
|
||||||
hashdiff (0.3.7)
|
hashdiff (0.3.7)
|
||||||
hashie (3.5.6)
|
hashie (3.6.0)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
http (3.3.0)
|
http (3.3.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
|
@ -235,7 +235,7 @@ GEM
|
||||||
interception (0.5)
|
interception (0.5)
|
||||||
jaro_winkler (1.5.1)
|
jaro_winkler (1.5.1)
|
||||||
json (2.1.0)
|
json (2.1.0)
|
||||||
jwt (1.5.6)
|
jwt (2.1.0)
|
||||||
kgio (2.11.0)
|
kgio (2.11.0)
|
||||||
koala (3.0.0)
|
koala (3.0.0)
|
||||||
addressable
|
addressable
|
||||||
|
@ -265,7 +265,7 @@ GEM
|
||||||
mini_mime (1.0.1)
|
mini_mime (1.0.1)
|
||||||
mini_portile2 (2.3.0)
|
mini_portile2 (2.3.0)
|
||||||
minitest (5.11.3)
|
minitest (5.11.3)
|
||||||
multi_json (1.12.2)
|
multi_json (1.13.1)
|
||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
mysql2 (0.4.10)
|
mysql2 (0.4.10)
|
||||||
|
@ -282,16 +282,16 @@ GEM
|
||||||
nenv (~> 0.1)
|
nenv (~> 0.1)
|
||||||
shellany (~> 0.0)
|
shellany (~> 0.0)
|
||||||
oauth (0.5.3)
|
oauth (0.5.3)
|
||||||
oauth2 (1.4.0)
|
oauth2 (1.4.1)
|
||||||
faraday (>= 0.8, < 0.13)
|
faraday (>= 0.8, < 0.16.0)
|
||||||
jwt (~> 1.0)
|
jwt (>= 1.0, < 3.0)
|
||||||
multi_json (~> 1.3)
|
multi_json (~> 1.3)
|
||||||
multi_xml (~> 0.5)
|
multi_xml (~> 0.5)
|
||||||
rack (>= 1.2, < 3)
|
rack (>= 1.2, < 3)
|
||||||
octokit (4.7.0)
|
octokit (4.7.0)
|
||||||
sawyer (~> 0.8.0, >= 0.5.3)
|
sawyer (~> 0.8.0, >= 0.5.3)
|
||||||
omniauth (1.7.1)
|
omniauth (1.9.0)
|
||||||
hashie (>= 3.4.6, < 3.6.0)
|
hashie (>= 3.4.6, < 3.7.0)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
omniauth-facebook (4.0.0)
|
omniauth-facebook (4.0.0)
|
||||||
omniauth-oauth2 (~> 1.2)
|
omniauth-oauth2 (~> 1.2)
|
||||||
|
@ -301,11 +301,10 @@ GEM
|
||||||
omniauth-gitlab (1.0.2)
|
omniauth-gitlab (1.0.2)
|
||||||
omniauth (~> 1.0)
|
omniauth (~> 1.0)
|
||||||
omniauth-oauth2 (~> 1.0)
|
omniauth-oauth2 (~> 1.0)
|
||||||
omniauth-google-oauth2 (0.5.2)
|
omniauth-google-oauth2 (0.6.0)
|
||||||
jwt (~> 1.5)
|
jwt (>= 2.0)
|
||||||
multi_json (~> 1.3)
|
|
||||||
omniauth (>= 1.1.1)
|
omniauth (>= 1.1.1)
|
||||||
omniauth-oauth2 (>= 1.3.1)
|
omniauth-oauth2 (>= 1.5)
|
||||||
omniauth-linkedin-oauth2 (0.2.5)
|
omniauth-linkedin-oauth2 (0.2.5)
|
||||||
omniauth (~> 1.0)
|
omniauth (~> 1.0)
|
||||||
omniauth-oauth2
|
omniauth-oauth2
|
||||||
|
@ -315,9 +314,9 @@ GEM
|
||||||
omniauth-oauth (1.1.0)
|
omniauth-oauth (1.1.0)
|
||||||
oauth
|
oauth
|
||||||
omniauth (~> 1.0)
|
omniauth (~> 1.0)
|
||||||
omniauth-oauth2 (1.4.0)
|
omniauth-oauth2 (1.6.0)
|
||||||
oauth2 (~> 1.0)
|
oauth2 (~> 1.1)
|
||||||
omniauth (~> 1.2)
|
omniauth (~> 1.9)
|
||||||
omniauth-twitter (1.4.0)
|
omniauth-twitter (1.4.0)
|
||||||
omniauth-oauth (~> 1.1)
|
omniauth-oauth (~> 1.1)
|
||||||
rack
|
rack
|
||||||
|
|
|
@ -99,11 +99,26 @@ curl http://localhost/api/v1/monitoring/health_check?token=XXX
|
||||||
issues.push "#{count_failed_jobs} failing background jobs"
|
issues.push "#{count_failed_jobs} failing background jobs"
|
||||||
end
|
end
|
||||||
|
|
||||||
listed_failed_jobs = failed_jobs.select(:handler, :attempts).limit(10)
|
handler_attempts_map = {}
|
||||||
sorted_failed_jobs = listed_failed_jobs.group_by(&:name).sort_by { |_handler, entries| entries.length }.reverse.to_h
|
failed_jobs.order(:created_at).limit(10).each do |job|
|
||||||
sorted_failed_jobs.each_with_index do |(name, jobs), index|
|
|
||||||
attempts = jobs.map(&:attempts).sum
|
job_name = if job.name == 'ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper'.freeze
|
||||||
issues.push "Failed to run background job ##{index += 1} '#{name}' #{jobs.count} time(s) with #{attempts} attempt(s)."
|
job.payload_object.job_data['job_class']
|
||||||
|
else
|
||||||
|
job.name
|
||||||
|
end
|
||||||
|
|
||||||
|
handler_attempts_map[job_name] ||= {
|
||||||
|
count: 0,
|
||||||
|
attempts: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler_attempts_map[job_name][:count] += 1
|
||||||
|
handler_attempts_map[job_name][:attempts] += job.attempts
|
||||||
|
end
|
||||||
|
|
||||||
|
Hash[handler_attempts_map.sort].each_with_index do |(job_name, job_data), index|
|
||||||
|
issues.push "Failed to run background job ##{index + 1} '#{job_name}' #{job_data[:count]} time(s) with #{job_data[:attempts]} attempt(s)."
|
||||||
end
|
end
|
||||||
|
|
||||||
# job count check
|
# job count check
|
||||||
|
|
|
@ -11,6 +11,9 @@ class ApplicationJob < ActiveJob::Base
|
||||||
# until we resolve this dependency.
|
# until we resolve this dependency.
|
||||||
around_enqueue do |job, block|
|
around_enqueue do |job, block|
|
||||||
block.call.tap do |delayed_job|
|
block.call.tap do |delayed_job|
|
||||||
|
# skip test adapter
|
||||||
|
break if delayed_job.is_a?(Array)
|
||||||
|
|
||||||
delayed_job.update!(attempts: job.executions)
|
delayed_job.update!(attempts: job.executions)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
class SearchIndexJob < ApplicationJob
|
||||||
class BackgroundJobSearchIndex
|
|
||||||
def initialize(object, o_id)
|
retry_on StandardError, attempts: 20
|
||||||
|
|
||||||
|
def perform(object, o_id)
|
||||||
@object = object
|
@object = object
|
||||||
@o_id = o_id
|
@o_id = o_id
|
||||||
end
|
|
||||||
|
|
||||||
def perform
|
|
||||||
record = @object.constantize.lookup(id: @o_id)
|
record = @object.constantize.lookup(id: @o_id)
|
||||||
return if !exists?(record)
|
return if !exists?(record)
|
||||||
|
|
||||||
|
@ -20,9 +20,4 @@ class BackgroundJobSearchIndex
|
||||||
Rails.logger.info "Can't index #{@object}.lookup(id: #{@o_id}), no such record found"
|
Rails.logger.info "Can't index #{@object}.lookup(id: #{@o_id}), no such record found"
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def max_attempts
|
|
||||||
20
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
|
@ -24,7 +24,7 @@ update search index, if configured - will be executed automatically
|
||||||
# start background job to transfer data to search index
|
# start background job to transfer data to search index
|
||||||
return true if !SearchIndexBackend.enabled?
|
return true if !SearchIndexBackend.enabled?
|
||||||
|
|
||||||
Delayed::Job.enqueue(BackgroundJobSearchIndex.new(self.class.to_s, id))
|
SearchIndexJob.perform_later(self.class.to_s, id)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
class HtmlSanitizer
|
class HtmlSanitizer
|
||||||
LINKABLE_URL_SCHEMES = URI.scheme_list.keys.map(&:downcase) - ['mailto'] + ['tel']
|
LINKABLE_URL_SCHEMES = URI.scheme_list.keys.map(&:downcase) - ['mailto'] + ['tel']
|
||||||
|
PROCESSING_TIMEOUT = 10
|
||||||
|
UNPROCESSABLE_HTML_MSG = 'This message cannot be displayed due to HTML processing issues. Download the raw message below and open it via an Email client if you still wish to view it.'.freeze
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
||||||
|
@ -9,7 +11,8 @@ satinize html string based on whiltelist
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def self.strict(string, external = false)
|
def self.strict(string, external = false, timeout: true)
|
||||||
|
Timeout.timeout(timeout ? PROCESSING_TIMEOUT : nil) do
|
||||||
@fqdn = Setting.get('fqdn')
|
@fqdn = Setting.get('fqdn')
|
||||||
|
|
||||||
# config
|
# config
|
||||||
|
@ -202,6 +205,9 @@ satinize html string based on whiltelist
|
||||||
|
|
||||||
Loofah.fragment(string).scrub!(scrubber_link).to_s
|
Loofah.fragment(string).scrub!(scrubber_link).to_s
|
||||||
end
|
end
|
||||||
|
rescue Timeout::Error => e
|
||||||
|
UNPROCESSABLE_HTML_MSG
|
||||||
|
end
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
||||||
|
@ -214,7 +220,8 @@ cleanup html string:
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def self.cleanup(string)
|
def self.cleanup(string, timeout: true)
|
||||||
|
Timeout.timeout(timeout ? PROCESSING_TIMEOUT : nil) do
|
||||||
string.gsub!(/<[A-z]:[A-z]>/, '')
|
string.gsub!(/<[A-z]:[A-z]>/, '')
|
||||||
string.gsub!(%r{</[A-z]:[A-z]>}, '')
|
string.gsub!(%r{</[A-z]:[A-z]>}, '')
|
||||||
string.delete!("\t")
|
string.delete!("\t")
|
||||||
|
@ -230,6 +237,9 @@ cleanup html string:
|
||||||
string = cleanup_structure(string)
|
string = cleanup_structure(string)
|
||||||
string
|
string
|
||||||
end
|
end
|
||||||
|
rescue Timeout::Error => e
|
||||||
|
UNPROCESSABLE_HTML_MSG
|
||||||
|
end
|
||||||
|
|
||||||
def self.cleanup_replace_tags(string)
|
def self.cleanup_replace_tags(string)
|
||||||
#return string
|
#return string
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
do($ = window.jQuery, window) ->
|
do($ = window.jQuery, window) ->
|
||||||
|
|
||||||
scripts = document.getElementsByTagName('script')
|
scripts = document.getElementsByTagName('script')
|
||||||
|
|
||||||
|
# search for script to get protocol and hostname for ws connection
|
||||||
myScript = scripts[scripts.length - 1]
|
myScript = scripts[scripts.length - 1]
|
||||||
|
scriptProtocol = window.location.protocol.replace(':', '') # set default protocol
|
||||||
|
if myScript && myScript.src
|
||||||
scriptHost = myScript.src.match('.*://([^:/]*).*')[1]
|
scriptHost = myScript.src.match('.*://([^:/]*).*')[1]
|
||||||
scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1]
|
scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1]
|
||||||
|
|
||||||
|
@ -875,6 +879,7 @@ do($ = window.jQuery, window) ->
|
||||||
|
|
||||||
@isOpen = true
|
@isOpen = true
|
||||||
@log.debug 'open widget'
|
@log.debug 'open widget'
|
||||||
|
@show()
|
||||||
|
|
||||||
if !@sessionId
|
if !@sessionId
|
||||||
@showLoader()
|
@showLoader()
|
||||||
|
|
|
@ -1,64 +1,3 @@
|
||||||
if (!window.zammadChatTemplates) {
|
|
||||||
window.zammadChatTemplates = {};
|
|
||||||
}
|
|
||||||
window.zammadChatTemplates["agent"] = function (__obj) {
|
|
||||||
if (!__obj) __obj = {};
|
|
||||||
var __out = [], __capture = function(callback) {
|
|
||||||
var out = __out, result;
|
|
||||||
__out = [];
|
|
||||||
callback.call(this);
|
|
||||||
result = __out.join('');
|
|
||||||
__out = out;
|
|
||||||
return __safe(result);
|
|
||||||
}, __sanitize = function(value) {
|
|
||||||
if (value && value.ecoSafe) {
|
|
||||||
return value;
|
|
||||||
} else if (typeof value !== 'undefined' && value != null) {
|
|
||||||
return __escape(value);
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}, __safe, __objSafe = __obj.safe, __escape = __obj.escape;
|
|
||||||
__safe = __obj.safe = function(value) {
|
|
||||||
if (value && value.ecoSafe) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
if (!(typeof value !== 'undefined' && value != null)) value = '';
|
|
||||||
var result = new String(value);
|
|
||||||
result.ecoSafe = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (!__escape) {
|
|
||||||
__escape = __obj.escape = function(value) {
|
|
||||||
return ('' + value)
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
(function() {
|
|
||||||
(function() {
|
|
||||||
if (this.agent.avatar) {
|
|
||||||
__out.push('\n<img class="zammad-chat-agent-avatar" src="');
|
|
||||||
__out.push(__sanitize(this.agent.avatar));
|
|
||||||
__out.push('">\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
__out.push('\n<span class="zammad-chat-agent-sentence">\n <span class="zammad-chat-agent-name">');
|
|
||||||
|
|
||||||
__out.push(__sanitize(this.agent.name));
|
|
||||||
|
|
||||||
__out.push('</span>\n</span>');
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
}).call(__obj);
|
|
||||||
__obj.safe = __objSafe, __obj.escape = __escape;
|
|
||||||
return __out.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||||
slice = [].slice,
|
slice = [].slice,
|
||||||
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||||
|
@ -68,8 +7,11 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
var Base, Io, Log, Timeout, ZammadChat, myScript, scriptHost, scriptProtocol, scripts;
|
var Base, Io, Log, Timeout, ZammadChat, myScript, scriptHost, scriptProtocol, scripts;
|
||||||
scripts = document.getElementsByTagName('script');
|
scripts = document.getElementsByTagName('script');
|
||||||
myScript = scripts[scripts.length - 1];
|
myScript = scripts[scripts.length - 1];
|
||||||
|
scriptProtocol = window.location.protocol.replace(':', '');
|
||||||
|
if (myScript && myScript.src) {
|
||||||
scriptHost = myScript.src.match('.*://([^:/]*).*')[1];
|
scriptHost = myScript.src.match('.*://([^:/]*).*')[1];
|
||||||
scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1];
|
scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1];
|
||||||
|
}
|
||||||
Base = (function() {
|
Base = (function() {
|
||||||
Base.prototype.defaults = {
|
Base.prototype.defaults = {
|
||||||
debug: false
|
debug: false
|
||||||
|
@ -1167,6 +1109,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
}
|
}
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
this.log.debug('open widget');
|
this.log.debug('open widget');
|
||||||
|
this.show();
|
||||||
if (!this.sessionId) {
|
if (!this.sessionId) {
|
||||||
this.showLoader();
|
this.showLoader();
|
||||||
}
|
}
|
||||||
|
@ -1899,6 +1842,67 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
return window.ZammadChat = ZammadChat;
|
return window.ZammadChat = ZammadChat;
|
||||||
})(window.jQuery, window);
|
})(window.jQuery, window);
|
||||||
|
|
||||||
|
if (!window.zammadChatTemplates) {
|
||||||
|
window.zammadChatTemplates = {};
|
||||||
|
}
|
||||||
|
window.zammadChatTemplates["agent"] = function (__obj) {
|
||||||
|
if (!__obj) __obj = {};
|
||||||
|
var __out = [], __capture = function(callback) {
|
||||||
|
var out = __out, result;
|
||||||
|
__out = [];
|
||||||
|
callback.call(this);
|
||||||
|
result = __out.join('');
|
||||||
|
__out = out;
|
||||||
|
return __safe(result);
|
||||||
|
}, __sanitize = function(value) {
|
||||||
|
if (value && value.ecoSafe) {
|
||||||
|
return value;
|
||||||
|
} else if (typeof value !== 'undefined' && value != null) {
|
||||||
|
return __escape(value);
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, __safe, __objSafe = __obj.safe, __escape = __obj.escape;
|
||||||
|
__safe = __obj.safe = function(value) {
|
||||||
|
if (value && value.ecoSafe) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
if (!(typeof value !== 'undefined' && value != null)) value = '';
|
||||||
|
var result = new String(value);
|
||||||
|
result.ecoSafe = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!__escape) {
|
||||||
|
__escape = __obj.escape = function(value) {
|
||||||
|
return ('' + value)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(function() {
|
||||||
|
(function() {
|
||||||
|
if (this.agent.avatar) {
|
||||||
|
__out.push('\n<img class="zammad-chat-agent-avatar" src="');
|
||||||
|
__out.push(__sanitize(this.agent.avatar));
|
||||||
|
__out.push('">\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
__out.push('\n<span class="zammad-chat-agent-sentence">\n <span class="zammad-chat-agent-name">');
|
||||||
|
|
||||||
|
__out.push(__sanitize(this.agent.name));
|
||||||
|
|
||||||
|
__out.push('</span>\n</span>');
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
|
|
||||||
|
}).call(__obj);
|
||||||
|
__obj.safe = __objSafe, __obj.escape = __escape;
|
||||||
|
return __out.join('');
|
||||||
|
};
|
||||||
|
|
||||||
if (!window.zammadChatTemplates) {
|
if (!window.zammadChatTemplates) {
|
||||||
window.zammadChatTemplates = {};
|
window.zammadChatTemplates = {};
|
||||||
}
|
}
|
||||||
|
|
4
public/assets/chat/chat.min.js
vendored
4
public/assets/chat/chat.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -157,7 +157,7 @@
|
||||||
debug: true,
|
debug: true,
|
||||||
background: '#494d52',
|
background: '#494d52',
|
||||||
flat: true,
|
flat: true,
|
||||||
shown: false,
|
show: true,
|
||||||
idleTimeout: 1,
|
idleTimeout: 1,
|
||||||
idleTimeoutIntervallCheck: 0.5,
|
idleTimeoutIntervallCheck: 0.5,
|
||||||
inactiveTimeout: 2,
|
inactiveTimeout: 2,
|
||||||
|
|
202
public/assets/chat/znuny_open_by_button.html
Normal file
202
public/assets/chat/znuny_open_by_button.html
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="de-de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Zammad Chat</title>
|
||||||
|
<link rel="stylesheet" href="znuny.css">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mockup {
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
position: fixed;
|
||||||
|
left: 20px;
|
||||||
|
top: 20px;
|
||||||
|
background: white;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 3px 10px rgba(0,0,0,.3);
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings input {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.settings input + input {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td:first-child {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td.log {
|
||||||
|
text-align: left;
|
||||||
|
padding-right: 0;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.settings {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Box {
|
||||||
|
background: hsl(0,0%,91%);
|
||||||
|
width: 26px;
|
||||||
|
height: 24px;
|
||||||
|
color: hsl(0,0%,47%);
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.Box.Active {
|
||||||
|
background: hsl(0,0%,36%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
<img class="mockup" width="100%" src="znuny.png">
|
||||||
|
|
||||||
|
<div class="settings">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><h2>Settings</h2>
|
||||||
|
<td>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input id="flat" type="checkbox" data-option="flat">
|
||||||
|
<td>
|
||||||
|
<label for="flat">Flat Design</label>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="color" id="color" value="#AE99D6" data-option="color">
|
||||||
|
<td>
|
||||||
|
<label for="color">Color</label>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="range" id="borderRadius" value="5" min="0" max="20" data-option="borderRadius">
|
||||||
|
<input type="number" value="5" min="5" max="20" data-option="borderRadius">px
|
||||||
|
<td>
|
||||||
|
<label for="borderRadius">Border Radius</label>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="range" id="fontSize" value="12" min="11" max="18" data-option="fontSize">
|
||||||
|
<input type="number" value="12" min="11" max="18" data-option="fontSize">px
|
||||||
|
<td>
|
||||||
|
<label for="fontSize">Font Size</label>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<td><button class="open-zammad-chat">Open Chat</button>
|
||||||
|
<tr>
|
||||||
|
<td class="log"><h2>Log</h2>
|
||||||
|
<td>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="log js-chatLogDisplay">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="jquery-2.1.4.min.js"></script>
|
||||||
|
<script src="chat.js"></script>
|
||||||
|
<script>
|
||||||
|
function getSearchParameters() {
|
||||||
|
var prmstr = window.location.search.substr(1);
|
||||||
|
return prmstr != null && prmstr != '' ? transformToAssocArray(prmstr) : {};
|
||||||
|
}
|
||||||
|
function transformToAssocArray( prmstr ) {
|
||||||
|
var params = {};
|
||||||
|
var prmarr = prmstr.split('&');
|
||||||
|
for ( var i = 0; i < prmarr.length; i++) {
|
||||||
|
var tmparr = prmarr[i].split('=');
|
||||||
|
params[tmparr[0]] = tmparr[1];
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
var hostname = window.location.hostname;
|
||||||
|
var port = window.location.port;
|
||||||
|
var params = getSearchParameters();
|
||||||
|
var host = 'ws://'+ (location.host || 'localhost').split(':')[0] +':6042'
|
||||||
|
if (params['port']) {
|
||||||
|
host = 'ws://' + hostname + ':' + params['port']
|
||||||
|
}
|
||||||
|
cssUrl = 'http://' + hostname + ':' + port + '/assets/chat/chat.css'
|
||||||
|
|
||||||
|
var chat = new ZammadChat({
|
||||||
|
chatId: 1,
|
||||||
|
host: host,
|
||||||
|
cssUrl: cssUrl,
|
||||||
|
debug: true,
|
||||||
|
background: '#494d52',
|
||||||
|
flat: true,
|
||||||
|
show: false,
|
||||||
|
idleTimeout: 1,
|
||||||
|
idleTimeoutIntervallCheck: 0.5,
|
||||||
|
inactiveTimeout: 2,
|
||||||
|
inactiveTimeoutIntervallCheck: 0.5,
|
||||||
|
waitingListTimeout: 1.2,
|
||||||
|
waitingListTimeoutIntervallCheck: 0.5,
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.settings :input').on({
|
||||||
|
change: function(){
|
||||||
|
switch($(this).attr('data-option')){
|
||||||
|
case "flat":
|
||||||
|
$('.zammad-chat').toggleClass('zammad-chat--flat', this.checked);
|
||||||
|
break;
|
||||||
|
case "color":
|
||||||
|
setScssVariable('themeColor', this.value);
|
||||||
|
updateStyle();
|
||||||
|
break;
|
||||||
|
case "borderRadius":
|
||||||
|
setScssVariable('borderRadius', this.value + "px");
|
||||||
|
updateStyle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input: function(){
|
||||||
|
switch($(this).attr('data-option')){
|
||||||
|
case "borderRadius":
|
||||||
|
$('[data-option="borderRadius"]').val(this.value);
|
||||||
|
setScssVariable('borderRadius', this.value + "px");
|
||||||
|
updateStyle();
|
||||||
|
break;
|
||||||
|
case "fontSize":
|
||||||
|
$('[data-option="fontSize"]').val(this.value);
|
||||||
|
setScssVariable('fontSize', this.value + "px");
|
||||||
|
updateStyle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -63,7 +63,7 @@ App.Ajax.request({
|
||||||
// ajax parallel
|
// ajax parallel
|
||||||
App.Ajax.request({
|
App.Ajax.request({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: '/tests/wait/2',
|
url: '/tests/wait/3',
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
test( "ajax - parallel - ajax get 200 1/2", function() {
|
test( "ajax - parallel - ajax get 200 1/2", function() {
|
||||||
|
|
||||||
|
|
|
@ -102,11 +102,8 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||||
type: 'text', # to trigger a #save in the migration.
|
type: 'text', # to trigger a #save in the migration.
|
||||||
maxlength: 255,
|
maxlength: 255,
|
||||||
}
|
}
|
||||||
|
|
||||||
# rubocop:disable Rails/SkipsModelValidations
|
|
||||||
create(:object_manager_attribute_text)
|
create(:object_manager_attribute_text)
|
||||||
.update_columns(data_option: wrong)
|
.update_columns(data_option: wrong)
|
||||||
# rubocop:enable Rails/SkipsModelValidations
|
|
||||||
|
|
||||||
expect { migrate }.not_to raise_error
|
expect { migrate }.not_to raise_error
|
||||||
end
|
end
|
||||||
|
@ -117,7 +114,7 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||||
context 'for interger attributes' do
|
context 'for interger attributes' do
|
||||||
it 'missing :min and :max' do
|
it 'missing :min and :max' do
|
||||||
attribute = create(:object_manager_attribute_integer)
|
attribute = create(:object_manager_attribute_integer)
|
||||||
attribute.update_columns(data_option: {}) # rubocop:disable Rails/SkipsModelValidations
|
attribute.update_columns(data_option: {})
|
||||||
|
|
||||||
expect { migrate }.not_to raise_error
|
expect { migrate }.not_to raise_error
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ class FailingTestJob < ApplicationJob
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
RSpec.describe ApplicationJob, type: :job do
|
RSpec.describe ApplicationJob do
|
||||||
|
|
||||||
it 'syncs ActiveJob#executions to Delayed::Job#attempts' do
|
it 'syncs ActiveJob#executions to Delayed::Job#attempts' do
|
||||||
FailingTestJob.perform_later
|
FailingTestJob.perform_later
|
||||||
|
|
24
spec/jobs/search_index_job_spec.rb
Normal file
24
spec/jobs/search_index_job_spec.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SearchIndexJob, type: :job do
|
||||||
|
|
||||||
|
it 'calls search_index_update_backend on matching record' do
|
||||||
|
user = create(:user)
|
||||||
|
expect(::User).to receive(:lookup).with(id: user.id).and_return(user)
|
||||||
|
expect(user).to receive(:search_index_update_backend)
|
||||||
|
|
||||||
|
described_class.perform_now('User', user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't perform for non existing records" do
|
||||||
|
id = 9999
|
||||||
|
expect(::User).to receive(:lookup).with(id: id).and_return(nil)
|
||||||
|
described_class.perform_now('User', id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'retries on exception' do
|
||||||
|
expect(::User).to receive(:lookup).and_raise(RuntimeError)
|
||||||
|
described_class.perform_now('User', 1)
|
||||||
|
expect(SearchIndexJob).to have_been_enqueued
|
||||||
|
end
|
||||||
|
end
|
|
@ -183,4 +183,27 @@ RSpec.describe HtmlSanitizer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Issue #2416 - html_sanitizer goes into loop for specific content
|
||||||
|
describe '.strict' do
|
||||||
|
context 'with strings that take a long time (>10s) to parse' do
|
||||||
|
before { allow(Timeout).to receive(:timeout).and_raise(Timeout::Error) }
|
||||||
|
|
||||||
|
it 'returns a timeout error message for the user' do
|
||||||
|
expect(HtmlSanitizer.strict(+'<img src="/some_one.png">', true))
|
||||||
|
.to match(HtmlSanitizer::UNPROCESSABLE_HTML_MSG)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.cleanup' do
|
||||||
|
context 'with strings that take a long time (>10s) to parse' do
|
||||||
|
before { allow(Timeout).to receive(:timeout).and_raise(Timeout::Error) }
|
||||||
|
|
||||||
|
it 'returns a timeout error message for the user' do
|
||||||
|
expect(HtmlSanitizer.cleanup(+'<img src="/some_one.png">'))
|
||||||
|
.to match(HtmlSanitizer::UNPROCESSABLE_HTML_MSG)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
33
spec/models/concerns/has_search_index_backend_examples.rb
Normal file
33
spec/models/concerns/has_search_index_backend_examples.rb
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
RSpec.shared_examples 'HasSearchIndexBackend' do |indexed_factory:|
|
||||||
|
|
||||||
|
context '#search_index_update', performs_jobs: true do
|
||||||
|
subject { create(indexed_factory) }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(SearchIndexBackend).to receive(:enabled?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'record indexing' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
expect(subject).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'indexes on create' do
|
||||||
|
expect(SearchIndexJob).to have_been_enqueued
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'indexes on update' do
|
||||||
|
clear_jobs
|
||||||
|
subject.update(note: 'Updated')
|
||||||
|
expect(SearchIndexJob).to have_been_enqueued
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'indexes on touch' do
|
||||||
|
clear_jobs
|
||||||
|
subject.touch
|
||||||
|
expect(SearchIndexJob).to have_been_enqueued
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,8 +1,10 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
require 'models/concerns/can_lookup_examples'
|
require 'models/concerns/can_lookup_examples'
|
||||||
|
require 'models/concerns/has_search_index_backend_examples'
|
||||||
|
|
||||||
RSpec.describe Organization do
|
RSpec.describe Organization do
|
||||||
include_examples 'CanLookup'
|
include_examples 'CanLookup'
|
||||||
|
include_examples 'HasSearchIndexBackend', indexed_factory: :organization
|
||||||
|
|
||||||
context '.where_or_cis' do
|
context '.where_or_cis' do
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ RSpec.configure do |config|
|
||||||
#
|
#
|
||||||
# The different available types are documented in the features, such as in
|
# The different available types are documented in the features, such as in
|
||||||
# https://relishapp.com/rspec/rspec-rails/docs
|
# https://relishapp.com/rspec/rspec-rails/docs
|
||||||
config.infer_spec_type_from_file_location!
|
# config.infer_spec_type_from_file_location!
|
||||||
|
|
||||||
# Filter lines from Rails gems in backtraces.
|
# Filter lines from Rails gems in backtraces.
|
||||||
config.filter_rails_from_backtrace!
|
config.filter_rails_from_backtrace!
|
||||||
|
|
|
@ -372,7 +372,6 @@ RSpec.describe 'Monitoring', type: :request do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does check health false' do
|
it 'does check health false' do
|
||||||
|
|
||||||
channel = Channel.find_by(active: true)
|
channel = Channel.find_by(active: true)
|
||||||
channel.status_in = 'ok'
|
channel.status_in = 'ok'
|
||||||
channel.status_out = 'error'
|
channel.status_out = 'error'
|
||||||
|
@ -423,7 +422,7 @@ RSpec.describe 'Monitoring', type: :request do
|
||||||
# health_check - scheduler job count
|
# health_check - scheduler job count
|
||||||
travel 2.seconds
|
travel 2.seconds
|
||||||
8001.times do
|
8001.times do
|
||||||
Delayed::Job.enqueue( BackgroundJobSearchIndex.new('Ticket', 1))
|
SearchIndexJob.perform_later('Ticket', 1)
|
||||||
end
|
end
|
||||||
Scheduler.where(active: true).each do |local_scheduler|
|
Scheduler.where(active: true).each do |local_scheduler|
|
||||||
local_scheduler.last_run = Time.zone.now
|
local_scheduler.last_run = Time.zone.now
|
||||||
|
@ -520,7 +519,6 @@ RSpec.describe 'Monitoring', type: :request do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does check failed delayed job', db_strategy: :reset do
|
it 'does check failed delayed job', db_strategy: :reset do
|
||||||
|
|
||||||
# disable elasticsearch
|
# disable elasticsearch
|
||||||
prev_es_config = Setting.get('es_url')
|
prev_es_config = Setting.get('es_url')
|
||||||
Setting.set('es_url', 'http://127.0.0.1:92001')
|
Setting.set('es_url', 'http://127.0.0.1:92001')
|
||||||
|
@ -598,11 +596,11 @@ RSpec.describe 'Monitoring', type: :request do
|
||||||
expect(json_response['message']).to be_truthy
|
expect(json_response['message']).to be_truthy
|
||||||
expect(json_response['issues']).to be_truthy
|
expect(json_response['issues']).to be_truthy
|
||||||
expect(json_response['healthy']).to eq(false)
|
expect(json_response['healthy']).to eq(false)
|
||||||
expect( json_response['message']).to eq("Failed to run background job #1 'BackgroundJobSearchIndex' 1 time(s) with 4 attempt(s).")
|
expect( json_response['message']).to eq("Failed to run background job #1 'SearchIndexJob' 4 time(s) with 4 attempt(s).")
|
||||||
|
|
||||||
# add another job
|
# add another job
|
||||||
manual_added = Delayed::Job.enqueue( BackgroundJobSearchIndex.new('Ticket', 1))
|
manual_added = SearchIndexJob.perform_later('Ticket', 1)
|
||||||
manual_added.update!(attempts: 10)
|
Delayed::Job.find(manual_added.provider_job_id).update!(attempts: 10)
|
||||||
|
|
||||||
# health_check
|
# health_check
|
||||||
get "/api/v1/monitoring/health_check?token=#{token}", params: {}, as: :json
|
get "/api/v1/monitoring/health_check?token=#{token}", params: {}, as: :json
|
||||||
|
@ -612,7 +610,7 @@ RSpec.describe 'Monitoring', type: :request do
|
||||||
expect(json_response['message']).to be_truthy
|
expect(json_response['message']).to be_truthy
|
||||||
expect(json_response['issues']).to be_truthy
|
expect(json_response['issues']).to be_truthy
|
||||||
expect(json_response['healthy']).to eq(false)
|
expect(json_response['healthy']).to eq(false)
|
||||||
expect( json_response['message']).to eq("Failed to run background job #1 'BackgroundJobSearchIndex' 2 time(s) with 14 attempt(s).")
|
expect( json_response['message']).to eq("Failed to run background job #1 'SearchIndexJob' 5 time(s) with 14 attempt(s).")
|
||||||
|
|
||||||
# add another job
|
# add another job
|
||||||
dummy_class = Class.new do
|
dummy_class = Class.new do
|
||||||
|
@ -633,7 +631,7 @@ RSpec.describe 'Monitoring', type: :request do
|
||||||
expect(json_response['message']).to be_truthy
|
expect(json_response['message']).to be_truthy
|
||||||
expect(json_response['issues']).to be_truthy
|
expect(json_response['issues']).to be_truthy
|
||||||
expect(json_response['healthy']).to eq(false)
|
expect(json_response['healthy']).to eq(false)
|
||||||
expect( json_response['message']).to eq("Failed to run background job #1 'BackgroundJobSearchIndex' 2 time(s) with 14 attempt(s).;Failed to run background job #2 'Object' 1 time(s) with 5 attempt(s).")
|
expect( json_response['message']).to eq("Failed to run background job #1 'Object' 1 time(s) with 5 attempt(s).;Failed to run background job #2 'SearchIndexJob' 5 time(s) with 14 attempt(s).")
|
||||||
|
|
||||||
# reset settings
|
# reset settings
|
||||||
Setting.set('es_url', prev_es_config)
|
Setting.set('es_url', prev_es_config)
|
||||||
|
@ -652,7 +650,7 @@ RSpec.describe 'Monitoring', type: :request do
|
||||||
expect(json_response['message']).to be_truthy
|
expect(json_response['message']).to be_truthy
|
||||||
expect(json_response['issues']).to be_truthy
|
expect(json_response['issues']).to be_truthy
|
||||||
expect(json_response['healthy']).to eq(false)
|
expect(json_response['healthy']).to eq(false)
|
||||||
expect( json_response['message']).to eq("13 failing background jobs;Failed to run background job #1 'Object' 8 time(s) with 40 attempt(s).;Failed to run background job #2 'BackgroundJobSearchIndex' 2 time(s) with 14 attempt(s).")
|
expect(json_response['message']).to eq("16 failing background jobs;Failed to run background job #1 'Object' 5 time(s) with 25 attempt(s).;Failed to run background job #2 'SearchIndexJob' 5 time(s) with 14 attempt(s).")
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
Delayed::Job.delete_all
|
Delayed::Job.delete_all
|
||||||
|
|
|
@ -2120,7 +2120,7 @@ RSpec.describe 'Ticket', type: :request do
|
||||||
travel 2.minutes
|
travel 2.minutes
|
||||||
ticket3
|
ticket3
|
||||||
travel 2.minutes
|
travel 2.minutes
|
||||||
ticket2.touch # rubocop:disable Rails/SkipsModelValidations
|
ticket2.touch
|
||||||
end
|
end
|
||||||
|
|
||||||
# https://github.com/zammad/zammad/issues/2296
|
# https://github.com/zammad/zammad/issues/2296
|
||||||
|
|
38
spec/support/active_job.rb
Normal file
38
spec/support/active_job.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
module ZammadActiveJobHelper
|
||||||
|
|
||||||
|
delegate :enqueued_jobs, :performed_jobs, to: :queue_adapter
|
||||||
|
|
||||||
|
def queue_adapter
|
||||||
|
::ActiveJob::Base.queue_adapter
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_jobs
|
||||||
|
enqueued_jobs.clear
|
||||||
|
performed_jobs.clear
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
|
||||||
|
activate_for = {
|
||||||
|
type: :job, # actual Job examples
|
||||||
|
performs_jobs: true, # examples performing Jobs
|
||||||
|
}
|
||||||
|
|
||||||
|
activate_for.each do |key, value|
|
||||||
|
config.include ZammadActiveJobHelper, key => value
|
||||||
|
config.include RSpec::Rails::JobExampleGroup, key => value
|
||||||
|
|
||||||
|
config.around(:each, key => value) do |example|
|
||||||
|
|
||||||
|
default_queue_adapter = ::ActiveJob::Base.queue_adapter
|
||||||
|
::ActiveJob::Base.queue_adapter = :test
|
||||||
|
|
||||||
|
clear_jobs
|
||||||
|
|
||||||
|
example.run
|
||||||
|
|
||||||
|
::ActiveJob::Base.queue_adapter = default_queue_adapter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.before(:suite) do
|
config.before(:suite) do
|
||||||
next if ENV['NO_RESET_BEFORE_SUITE']
|
next if !ENV['RESET_BEFORE_SUITE']
|
||||||
|
|
||||||
Rake::Task['zammad:db:reset'].invoke
|
Rake::Task['zammad:db:reset'].invoke
|
||||||
end
|
end
|
||||||
|
|
|
@ -72,6 +72,7 @@ class AdminChannelEmailTest < TestCase
|
||||||
|
|
||||||
click(css: '.content.active .js-channelDelete')
|
click(css: '.content.active .js-channelDelete')
|
||||||
sleep 2
|
sleep 2
|
||||||
|
# flanky
|
||||||
click(css: '.modal .js-submit')
|
click(css: '.modal .js-submit')
|
||||||
sleep 2
|
sleep 2
|
||||||
end
|
end
|
||||||
|
|
|
@ -113,6 +113,7 @@ class AgentTicketAutoAssignmentTest < TestCase
|
||||||
|
|
||||||
# define auto assignment exception
|
# define auto assignment exception
|
||||||
click(css: 'a[href="#manage"]')
|
click(css: 'a[href="#manage"]')
|
||||||
|
# flanky
|
||||||
click(css: '.content.active a[href="#settings/ticket"]')
|
click(css: '.content.active a[href="#settings/ticket"]')
|
||||||
click(css: '.content.active a[href="#auto_assignment"]')
|
click(css: '.content.active a[href="#auto_assignment"]')
|
||||||
click(css: '.content.active .js-select.js-option[title="master@example.com"]')
|
click(css: '.content.active .js-select.js-option[title="master@example.com"]')
|
||||||
|
|
|
@ -591,6 +591,78 @@ class ChatTest < TestCase
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_open_chat_by_button
|
||||||
|
chat_url = "#{browser_url}/assets/chat/znuny_open_by_button.html?port=#{ENV['WS_PORT']}"
|
||||||
|
agent = browser_instance
|
||||||
|
login(
|
||||||
|
browser: agent,
|
||||||
|
username: 'master@example.com',
|
||||||
|
password: 'test',
|
||||||
|
url: browser_url,
|
||||||
|
)
|
||||||
|
tasks_close_all(
|
||||||
|
browser: agent,
|
||||||
|
)
|
||||||
|
click(
|
||||||
|
browser: agent,
|
||||||
|
css: 'a[href="#customer_chat"]',
|
||||||
|
)
|
||||||
|
agent.find_elements(css: '.active .chat-window .js-disconnect:not(.is-hidden)').each(&:click)
|
||||||
|
agent.find_elements(css: '.active .chat-window .js-close').each(&:click)
|
||||||
|
|
||||||
|
customer = browser_instance
|
||||||
|
location(
|
||||||
|
browser: customer,
|
||||||
|
url: chat_url,
|
||||||
|
)
|
||||||
|
watch_for(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat',
|
||||||
|
timeout: 5,
|
||||||
|
)
|
||||||
|
exists_not(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat-is-shown',
|
||||||
|
)
|
||||||
|
exists_not(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat-is-open',
|
||||||
|
)
|
||||||
|
click(
|
||||||
|
browser: customer,
|
||||||
|
css: '.open-zammad-chat',
|
||||||
|
)
|
||||||
|
watch_for(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat-is-shown',
|
||||||
|
timeout: 4,
|
||||||
|
)
|
||||||
|
watch_for(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat-is-open',
|
||||||
|
timeout: 4,
|
||||||
|
)
|
||||||
|
watch_for(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat',
|
||||||
|
value: '(waiting|warte)',
|
||||||
|
)
|
||||||
|
click(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat-header-icon-close',
|
||||||
|
)
|
||||||
|
watch_for_disappear(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat-is-shown',
|
||||||
|
timeout: 4,
|
||||||
|
)
|
||||||
|
watch_for_disappear(
|
||||||
|
browser: customer,
|
||||||
|
css: '.zammad-chat-is-open',
|
||||||
|
timeout: 4,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def test_timeouts
|
def test_timeouts
|
||||||
chat_url = "#{browser_url}/assets/chat/znuny.html?port=#{ENV['WS_PORT']}"
|
chat_url = "#{browser_url}/assets/chat/znuny.html?port=#{ENV['WS_PORT']}"
|
||||||
agent = browser_instance
|
agent = browser_instance
|
||||||
|
@ -762,4 +834,31 @@ class ChatTest < TestCase
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disable_chat
|
||||||
|
login(
|
||||||
|
browser: agent,
|
||||||
|
username: 'master@example.com',
|
||||||
|
password: 'test',
|
||||||
|
url: browser_url,
|
||||||
|
)
|
||||||
|
tasks_close_all(
|
||||||
|
browser: agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
# disable chat
|
||||||
|
click(
|
||||||
|
browser: agent,
|
||||||
|
css: 'a[href="#manage"]',
|
||||||
|
)
|
||||||
|
click(
|
||||||
|
browser: agent,
|
||||||
|
css: '.content.active a[href="#channels/chat"]',
|
||||||
|
)
|
||||||
|
switch(
|
||||||
|
browser: agent,
|
||||||
|
css: '.content.active .js-chatSetting',
|
||||||
|
type: 'off',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,6 +50,7 @@ class IntegrationCtiTest < TestCase
|
||||||
Net::HTTP.post_form(url, params.merge(event: 'newCall'))
|
Net::HTTP.post_form(url, params.merge(event: 'newCall'))
|
||||||
Net::HTTP.post_form(url, params.merge(event: 'hangup'))
|
Net::HTTP.post_form(url, params.merge(event: 'hangup'))
|
||||||
|
|
||||||
|
# flanky
|
||||||
watch_for(
|
watch_for(
|
||||||
css: '.js-phoneMenuItem .counter',
|
css: '.js-phoneMenuItem .counter',
|
||||||
value: (call_counter + 1).to_s,
|
value: (call_counter + 1).to_s,
|
||||||
|
|
|
@ -43,6 +43,7 @@ class IntegrationSipgateTest < TestCase
|
||||||
Net::HTTP.post_form(url, params.merge(event: 'newCall'))
|
Net::HTTP.post_form(url, params.merge(event: 'newCall'))
|
||||||
Net::HTTP.post_form(url, params.merge(event: 'hangup'))
|
Net::HTTP.post_form(url, params.merge(event: 'hangup'))
|
||||||
|
|
||||||
|
# flanky
|
||||||
watch_for(
|
watch_for(
|
||||||
css: '.js-phoneMenuItem .counter',
|
css: '.js-phoneMenuItem .counter',
|
||||||
value: (call_counter + 1).to_s,
|
value: (call_counter + 1).to_s,
|
||||||
|
|
|
@ -203,6 +203,7 @@ class KeyboardShortcutsTest < TestCase
|
||||||
)
|
)
|
||||||
sleep 5
|
sleep 5
|
||||||
shortcut(key: 'a')
|
shortcut(key: 'a')
|
||||||
|
# flanky
|
||||||
watch_for(
|
watch_for(
|
||||||
css: '.js-notificationsContainer',
|
css: '.js-notificationsContainer',
|
||||||
value: 'Test Ticket for Shortcuts II',
|
value: 'Test Ticket for Shortcuts II',
|
||||||
|
|
Loading…
Reference in a new issue