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
|
||||
variables:
|
||||
RAILS_ENV: "test"
|
||||
NO_RESET_BEFORE_SUITE: "true"
|
||||
BROWSER: "chrome"
|
||||
|
||||
.variables_capybara_ff_template: &variables_capybara_ff_definition
|
||||
<<: *test_capybara_definition
|
||||
variables:
|
||||
RAILS_ENV: "test"
|
||||
NO_RESET_BEFORE_SUITE: "true"
|
||||
BROWSER: "firefox"
|
||||
|
||||
test:browser:core:capybara_chrome_postgresql:
|
||||
|
|
|
@ -175,6 +175,8 @@ Rails/SkipsModelValidations:
|
|||
Enabled: true
|
||||
Exclude:
|
||||
- test/**/*
|
||||
- "**/*_spec.rb"
|
||||
- "**/*_examples.rb"
|
||||
|
||||
Style/ClassAndModuleChildren:
|
||||
Description: 'Checks style of children classes and modules.'
|
||||
|
|
31
Gemfile.lock
31
Gemfile.lock
|
@ -174,7 +174,7 @@ GEM
|
|||
railties (>= 3.0.0)
|
||||
faker (1.9.1)
|
||||
i18n (>= 0.7)
|
||||
faraday (0.12.2)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-http-cache (2.0.0)
|
||||
faraday (~> 0.8)
|
||||
|
@ -212,7 +212,7 @@ GEM
|
|||
guard
|
||||
guard-compat (~> 1.1)
|
||||
hashdiff (0.3.7)
|
||||
hashie (3.5.6)
|
||||
hashie (3.6.0)
|
||||
htmlentities (4.3.4)
|
||||
http (3.3.0)
|
||||
addressable (~> 2.3)
|
||||
|
@ -235,7 +235,7 @@ GEM
|
|||
interception (0.5)
|
||||
jaro_winkler (1.5.1)
|
||||
json (2.1.0)
|
||||
jwt (1.5.6)
|
||||
jwt (2.1.0)
|
||||
kgio (2.11.0)
|
||||
koala (3.0.0)
|
||||
addressable
|
||||
|
@ -265,7 +265,7 @@ GEM
|
|||
mini_mime (1.0.1)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.11.3)
|
||||
multi_json (1.12.2)
|
||||
multi_json (1.13.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
mysql2 (0.4.10)
|
||||
|
@ -282,16 +282,16 @@ GEM
|
|||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
oauth (0.5.3)
|
||||
oauth2 (1.4.0)
|
||||
faraday (>= 0.8, < 0.13)
|
||||
jwt (~> 1.0)
|
||||
oauth2 (1.4.1)
|
||||
faraday (>= 0.8, < 0.16.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
octokit (4.7.0)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
omniauth (1.7.1)
|
||||
hashie (>= 3.4.6, < 3.6.0)
|
||||
omniauth (1.9.0)
|
||||
hashie (>= 3.4.6, < 3.7.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-facebook (4.0.0)
|
||||
omniauth-oauth2 (~> 1.2)
|
||||
|
@ -301,11 +301,10 @@ GEM
|
|||
omniauth-gitlab (1.0.2)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.0)
|
||||
omniauth-google-oauth2 (0.5.2)
|
||||
jwt (~> 1.5)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-google-oauth2 (0.6.0)
|
||||
jwt (>= 2.0)
|
||||
omniauth (>= 1.1.1)
|
||||
omniauth-oauth2 (>= 1.3.1)
|
||||
omniauth-oauth2 (>= 1.5)
|
||||
omniauth-linkedin-oauth2 (0.2.5)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2
|
||||
|
@ -315,9 +314,9 @@ GEM
|
|||
omniauth-oauth (1.1.0)
|
||||
oauth
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (1.4.0)
|
||||
oauth2 (~> 1.0)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-oauth2 (1.6.0)
|
||||
oauth2 (~> 1.1)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
|
|
|
@ -99,11 +99,26 @@ curl http://localhost/api/v1/monitoring/health_check?token=XXX
|
|||
issues.push "#{count_failed_jobs} failing background jobs"
|
||||
end
|
||||
|
||||
listed_failed_jobs = failed_jobs.select(:handler, :attempts).limit(10)
|
||||
sorted_failed_jobs = listed_failed_jobs.group_by(&:name).sort_by { |_handler, entries| entries.length }.reverse.to_h
|
||||
sorted_failed_jobs.each_with_index do |(name, jobs), index|
|
||||
attempts = jobs.map(&:attempts).sum
|
||||
issues.push "Failed to run background job ##{index += 1} '#{name}' #{jobs.count} time(s) with #{attempts} attempt(s)."
|
||||
handler_attempts_map = {}
|
||||
failed_jobs.order(:created_at).limit(10).each do |job|
|
||||
|
||||
job_name = if job.name == 'ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper'.freeze
|
||||
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
|
||||
|
||||
# job count check
|
||||
|
|
|
@ -11,6 +11,9 @@ class ApplicationJob < ActiveJob::Base
|
|||
# until we resolve this dependency.
|
||||
around_enqueue do |job, block|
|
||||
block.call.tap do |delayed_job|
|
||||
# skip test adapter
|
||||
break if delayed_job.is_a?(Array)
|
||||
|
||||
delayed_job.update!(attempts: job.executions)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
class BackgroundJobSearchIndex
|
||||
def initialize(object, o_id)
|
||||
class SearchIndexJob < ApplicationJob
|
||||
|
||||
retry_on StandardError, attempts: 20
|
||||
|
||||
def perform(object, o_id)
|
||||
@object = object
|
||||
@o_id = o_id
|
||||
end
|
||||
|
||||
def perform
|
||||
record = @object.constantize.lookup(id: @o_id)
|
||||
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"
|
||||
false
|
||||
end
|
||||
|
||||
def max_attempts
|
||||
20
|
||||
end
|
||||
|
||||
end
|
|
@ -24,7 +24,7 @@ update search index, if configured - will be executed automatically
|
|||
# start background job to transfer data to search index
|
||||
return true if !SearchIndexBackend.enabled?
|
||||
|
||||
Delayed::Job.enqueue(BackgroundJobSearchIndex.new(self.class.to_s, id))
|
||||
SearchIndexJob.perform_later(self.class.to_s, id)
|
||||
true
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
class HtmlSanitizer
|
||||
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
|
||||
|
||||
|
@ -9,198 +11,202 @@ satinize html string based on whiltelist
|
|||
|
||||
=end
|
||||
|
||||
def self.strict(string, external = false)
|
||||
@fqdn = Setting.get('fqdn')
|
||||
def self.strict(string, external = false, timeout: true)
|
||||
Timeout.timeout(timeout ? PROCESSING_TIMEOUT : nil) do
|
||||
@fqdn = Setting.get('fqdn')
|
||||
|
||||
# config
|
||||
tags_remove_content = Rails.configuration.html_sanitizer_tags_remove_content
|
||||
tags_quote_content = Rails.configuration.html_sanitizer_tags_quote_content
|
||||
tags_whitelist = Rails.configuration.html_sanitizer_tags_whitelist
|
||||
attributes_whitelist = Rails.configuration.html_sanitizer_attributes_whitelist
|
||||
css_properties_whitelist = Rails.configuration.html_sanitizer_css_properties_whitelist
|
||||
css_values_blacklist = Rails.application.config.html_sanitizer_css_values_backlist
|
||||
# config
|
||||
tags_remove_content = Rails.configuration.html_sanitizer_tags_remove_content
|
||||
tags_quote_content = Rails.configuration.html_sanitizer_tags_quote_content
|
||||
tags_whitelist = Rails.configuration.html_sanitizer_tags_whitelist
|
||||
attributes_whitelist = Rails.configuration.html_sanitizer_attributes_whitelist
|
||||
css_properties_whitelist = Rails.configuration.html_sanitizer_css_properties_whitelist
|
||||
css_values_blacklist = Rails.application.config.html_sanitizer_css_values_backlist
|
||||
|
||||
# We whitelist yahoo_quoted because Yahoo Mail marks quoted email content using
|
||||
# <div class='yahoo_quoted'> and we rely on this class to identify quoted messages
|
||||
classes_whitelist = ['js-signatureMarker', 'yahoo_quoted']
|
||||
attributes_2_css = %w[width height]
|
||||
# We whitelist yahoo_quoted because Yahoo Mail marks quoted email content using
|
||||
# <div class='yahoo_quoted'> and we rely on this class to identify quoted messages
|
||||
classes_whitelist = ['js-signatureMarker', 'yahoo_quoted']
|
||||
attributes_2_css = %w[width height]
|
||||
|
||||
# remove html comments
|
||||
string.gsub!(/<!--.+?-->/m, '')
|
||||
# remove html comments
|
||||
string.gsub!(/<!--.+?-->/m, '')
|
||||
|
||||
scrubber_link = Loofah::Scrubber.new do |node|
|
||||
scrubber_link = Loofah::Scrubber.new do |node|
|
||||
|
||||
# wrap plain-text URLs in <a> tags
|
||||
if node.is_a?(Nokogiri::XML::Text) && node.content.present? && node.content.include?(':') && node.ancestors.map(&:name).exclude?('a')
|
||||
urls = URI.extract(node.content, LINKABLE_URL_SCHEMES)
|
||||
.map { |u| u.sub(/[,.]$/, '') } # URI::extract captures trailing dots/commas
|
||||
.reject { |u| u.match?(/^[^:]+:$/) } # URI::extract will match, e.g., 'tel:'
|
||||
# wrap plain-text URLs in <a> tags
|
||||
if node.is_a?(Nokogiri::XML::Text) && node.content.present? && node.content.include?(':') && node.ancestors.map(&:name).exclude?('a')
|
||||
urls = URI.extract(node.content, LINKABLE_URL_SCHEMES)
|
||||
.map { |u| u.sub(/[,.]$/, '') } # URI::extract captures trailing dots/commas
|
||||
.reject { |u| u.match?(/^[^:]+:$/) } # URI::extract will match, e.g., 'tel:'
|
||||
|
||||
next if urls.blank?
|
||||
next if urls.blank?
|
||||
|
||||
add_link(node.content, urls, node)
|
||||
end
|
||||
add_link(node.content, urls, node)
|
||||
end
|
||||
|
||||
# prepare links
|
||||
if node['href']
|
||||
href = cleanup_target(node['href'], keep_spaces: true)
|
||||
href_without_spaces = href.gsub(/[[:space:]]/, '')
|
||||
if external && href_without_spaces.present? && !href_without_spaces.downcase.start_with?('//') && href_without_spaces.downcase !~ %r{^.{1,6}://.+?}
|
||||
node['href'] = "http://#{node['href']}"
|
||||
href = node['href']
|
||||
# prepare links
|
||||
if node['href']
|
||||
href = cleanup_target(node['href'], keep_spaces: true)
|
||||
href_without_spaces = href.gsub(/[[:space:]]/, '')
|
||||
if external && href_without_spaces.present? && !href_without_spaces.downcase.start_with?('//') && href_without_spaces.downcase !~ %r{^.{1,6}://.+?}
|
||||
node['href'] = "http://#{node['href']}"
|
||||
href = node['href']
|
||||
href_without_spaces = href.gsub(/[[:space:]]/, '')
|
||||
end
|
||||
|
||||
next if !href_without_spaces.downcase.start_with?('http', 'ftp', '//')
|
||||
|
||||
node.set_attribute('href', href)
|
||||
node.set_attribute('rel', 'nofollow noreferrer noopener')
|
||||
node.set_attribute('target', '_blank')
|
||||
end
|
||||
|
||||
next if !href_without_spaces.downcase.start_with?('http', 'ftp', '//')
|
||||
if node.name == 'a' && node['href'].blank?
|
||||
node.replace node.children.to_s
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
|
||||
node.set_attribute('href', href)
|
||||
node.set_attribute('rel', 'nofollow noreferrer noopener')
|
||||
node.set_attribute('target', '_blank')
|
||||
end
|
||||
|
||||
if node.name == 'a' && node['href'].blank?
|
||||
node.replace node.children.to_s
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
|
||||
# check if href is different to text
|
||||
if node.name == 'a' && !url_same?(node['href'], node.text)
|
||||
if node['title'].blank?
|
||||
node['title'] = node['href']
|
||||
# check if href is different to text
|
||||
if node.name == 'a' && !url_same?(node['href'], node.text)
|
||||
if node['title'].blank?
|
||||
node['title'] = node['href']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scrubber_wipe = Loofah::Scrubber.new do |node|
|
||||
scrubber_wipe = Loofah::Scrubber.new do |node|
|
||||
|
||||
# remove tags with subtree
|
||||
if tags_remove_content.include?(node.name)
|
||||
node.remove
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
|
||||
# remove tag, insert quoted content
|
||||
if tags_quote_content.include?(node.name)
|
||||
string = html_decode(node.content)
|
||||
text = Nokogiri::XML::Text.new(string, node.document)
|
||||
node.add_next_sibling(text)
|
||||
node.remove
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
|
||||
# replace tags, keep subtree
|
||||
if !tags_whitelist.include?(node.name)
|
||||
node.replace node.children.to_s
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
|
||||
# prepare src attribute
|
||||
if node['src']
|
||||
src = cleanup_target(node['src'])
|
||||
if src =~ /(javascript|livescript|vbscript):/i || src.downcase.start_with?('http', 'ftp', '//')
|
||||
# remove tags with subtree
|
||||
if tags_remove_content.include?(node.name)
|
||||
node.remove
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
end
|
||||
|
||||
# clean class / only use allowed classes
|
||||
if node['class']
|
||||
classes = node['class'].gsub(/\t|\n|\r/, '').split(' ')
|
||||
class_new = ''
|
||||
classes.each do |local_class|
|
||||
next if !classes_whitelist.include?(local_class.to_s.strip)
|
||||
|
||||
if class_new != ''
|
||||
class_new += ' '
|
||||
end
|
||||
class_new += local_class
|
||||
end
|
||||
if class_new != ''
|
||||
node['class'] = class_new
|
||||
else
|
||||
node.delete('class')
|
||||
end
|
||||
end
|
||||
|
||||
# move style attributes to css attributes
|
||||
attributes_2_css.each do |key|
|
||||
next if !node[key]
|
||||
|
||||
if node['style'].blank?
|
||||
node['style'] = ''
|
||||
else
|
||||
node['style'] += ';'
|
||||
end
|
||||
value = node[key]
|
||||
node.delete(key)
|
||||
next if value.blank?
|
||||
|
||||
value += 'px' if !value.match?(/%|px|em/i)
|
||||
node['style'] += "#{key}:#{value}"
|
||||
end
|
||||
|
||||
# clean style / only use allowed style properties
|
||||
if node['style']
|
||||
pears = node['style'].downcase.gsub(/\t|\n|\r/, '').split(';')
|
||||
style = ''
|
||||
pears.each do |local_pear|
|
||||
prop = local_pear.split(':')
|
||||
next if !prop[0]
|
||||
|
||||
key = prop[0].strip
|
||||
next if !css_properties_whitelist.include?(node.name)
|
||||
next if !css_properties_whitelist[node.name].include?(key)
|
||||
next if css_values_blacklist[node.name]&.include?(local_pear.gsub(/[[:space:]]/, '').strip)
|
||||
|
||||
style += "#{local_pear};"
|
||||
end
|
||||
node['style'] = style
|
||||
if style == ''
|
||||
node.delete('style')
|
||||
end
|
||||
end
|
||||
|
||||
# scan for invalid link content
|
||||
%w[href style].each do |attribute_name|
|
||||
next if !node[attribute_name]
|
||||
|
||||
href = cleanup_target(node[attribute_name])
|
||||
next if href !~ /(javascript|livescript|vbscript):/i
|
||||
|
||||
node.delete(attribute_name)
|
||||
end
|
||||
|
||||
# remove attributes if not whitelisted
|
||||
node.each do |attribute, _value|
|
||||
attribute_name = attribute.downcase
|
||||
next if attributes_whitelist[:all].include?(attribute_name) || (attributes_whitelist[node.name]&.include?(attribute_name))
|
||||
|
||||
node.delete(attribute)
|
||||
end
|
||||
|
||||
# remove mailto links
|
||||
if node['href']
|
||||
href = cleanup_target(node['href'])
|
||||
if href =~ /mailto:(.*)$/i
|
||||
text = Nokogiri::XML::Text.new($1, node.document)
|
||||
# remove tag, insert quoted content
|
||||
if tags_quote_content.include?(node.name)
|
||||
string = html_decode(node.content)
|
||||
text = Nokogiri::XML::Text.new(string, node.document)
|
||||
node.add_next_sibling(text)
|
||||
node.remove
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
new_string = ''
|
||||
done = true
|
||||
while done
|
||||
new_string = Loofah.fragment(string).scrub!(scrubber_wipe).to_s
|
||||
if string == new_string
|
||||
done = false
|
||||
end
|
||||
string = new_string
|
||||
end
|
||||
# replace tags, keep subtree
|
||||
if !tags_whitelist.include?(node.name)
|
||||
node.replace node.children.to_s
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
|
||||
Loofah.fragment(string).scrub!(scrubber_link).to_s
|
||||
# prepare src attribute
|
||||
if node['src']
|
||||
src = cleanup_target(node['src'])
|
||||
if src =~ /(javascript|livescript|vbscript):/i || src.downcase.start_with?('http', 'ftp', '//')
|
||||
node.remove
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
end
|
||||
|
||||
# clean class / only use allowed classes
|
||||
if node['class']
|
||||
classes = node['class'].gsub(/\t|\n|\r/, '').split(' ')
|
||||
class_new = ''
|
||||
classes.each do |local_class|
|
||||
next if !classes_whitelist.include?(local_class.to_s.strip)
|
||||
|
||||
if class_new != ''
|
||||
class_new += ' '
|
||||
end
|
||||
class_new += local_class
|
||||
end
|
||||
if class_new != ''
|
||||
node['class'] = class_new
|
||||
else
|
||||
node.delete('class')
|
||||
end
|
||||
end
|
||||
|
||||
# move style attributes to css attributes
|
||||
attributes_2_css.each do |key|
|
||||
next if !node[key]
|
||||
|
||||
if node['style'].blank?
|
||||
node['style'] = ''
|
||||
else
|
||||
node['style'] += ';'
|
||||
end
|
||||
value = node[key]
|
||||
node.delete(key)
|
||||
next if value.blank?
|
||||
|
||||
value += 'px' if !value.match?(/%|px|em/i)
|
||||
node['style'] += "#{key}:#{value}"
|
||||
end
|
||||
|
||||
# clean style / only use allowed style properties
|
||||
if node['style']
|
||||
pears = node['style'].downcase.gsub(/\t|\n|\r/, '').split(';')
|
||||
style = ''
|
||||
pears.each do |local_pear|
|
||||
prop = local_pear.split(':')
|
||||
next if !prop[0]
|
||||
|
||||
key = prop[0].strip
|
||||
next if !css_properties_whitelist.include?(node.name)
|
||||
next if !css_properties_whitelist[node.name].include?(key)
|
||||
next if css_values_blacklist[node.name]&.include?(local_pear.gsub(/[[:space:]]/, '').strip)
|
||||
|
||||
style += "#{local_pear};"
|
||||
end
|
||||
node['style'] = style
|
||||
if style == ''
|
||||
node.delete('style')
|
||||
end
|
||||
end
|
||||
|
||||
# scan for invalid link content
|
||||
%w[href style].each do |attribute_name|
|
||||
next if !node[attribute_name]
|
||||
|
||||
href = cleanup_target(node[attribute_name])
|
||||
next if href !~ /(javascript|livescript|vbscript):/i
|
||||
|
||||
node.delete(attribute_name)
|
||||
end
|
||||
|
||||
# remove attributes if not whitelisted
|
||||
node.each do |attribute, _value|
|
||||
attribute_name = attribute.downcase
|
||||
next if attributes_whitelist[:all].include?(attribute_name) || (attributes_whitelist[node.name]&.include?(attribute_name))
|
||||
|
||||
node.delete(attribute)
|
||||
end
|
||||
|
||||
# remove mailto links
|
||||
if node['href']
|
||||
href = cleanup_target(node['href'])
|
||||
if href =~ /mailto:(.*)$/i
|
||||
text = Nokogiri::XML::Text.new($1, node.document)
|
||||
node.add_next_sibling(text)
|
||||
node.remove
|
||||
Loofah::Scrubber::STOP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
new_string = ''
|
||||
done = true
|
||||
while done
|
||||
new_string = Loofah.fragment(string).scrub!(scrubber_wipe).to_s
|
||||
if string == new_string
|
||||
done = false
|
||||
end
|
||||
string = new_string
|
||||
end
|
||||
|
||||
Loofah.fragment(string).scrub!(scrubber_link).to_s
|
||||
end
|
||||
rescue Timeout::Error => e
|
||||
UNPROCESSABLE_HTML_MSG
|
||||
end
|
||||
|
||||
=begin
|
||||
|
@ -214,21 +220,25 @@ cleanup html string:
|
|||
|
||||
=end
|
||||
|
||||
def self.cleanup(string)
|
||||
string.gsub!(/<[A-z]:[A-z]>/, '')
|
||||
string.gsub!(%r{</[A-z]:[A-z]>}, '')
|
||||
string.delete!("\t")
|
||||
def self.cleanup(string, timeout: true)
|
||||
Timeout.timeout(timeout ? PROCESSING_TIMEOUT : nil) do
|
||||
string.gsub!(/<[A-z]:[A-z]>/, '')
|
||||
string.gsub!(%r{</[A-z]:[A-z]>}, '')
|
||||
string.delete!("\t")
|
||||
|
||||
# remove all new lines
|
||||
string.gsub!(/(\n\r|\r\r\n|\r\n|\n)/, "\n")
|
||||
# remove all new lines
|
||||
string.gsub!(/(\n\r|\r\r\n|\r\n|\n)/, "\n")
|
||||
|
||||
# remove double multiple empty lines
|
||||
string.gsub!(/\n\n\n+/, "\n\n")
|
||||
# remove double multiple empty lines
|
||||
string.gsub!(/\n\n\n+/, "\n\n")
|
||||
|
||||
string = cleanup_structure(string, 'pre')
|
||||
string = cleanup_replace_tags(string)
|
||||
string = cleanup_structure(string)
|
||||
string
|
||||
string = cleanup_structure(string, 'pre')
|
||||
string = cleanup_replace_tags(string)
|
||||
string = cleanup_structure(string)
|
||||
string
|
||||
end
|
||||
rescue Timeout::Error => e
|
||||
UNPROCESSABLE_HTML_MSG
|
||||
end
|
||||
|
||||
def self.cleanup_replace_tags(string)
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
do($ = window.jQuery, window) ->
|
||||
|
||||
scripts = document.getElementsByTagName('script')
|
||||
|
||||
# search for script to get protocol and hostname for ws connection
|
||||
myScript = scripts[scripts.length - 1]
|
||||
scriptHost = myScript.src.match('.*://([^:/]*).*')[1]
|
||||
scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1]
|
||||
scriptProtocol = window.location.protocol.replace(':', '') # set default protocol
|
||||
if myScript && myScript.src
|
||||
scriptHost = myScript.src.match('.*://([^:/]*).*')[1]
|
||||
scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1]
|
||||
|
||||
# Define the plugin class
|
||||
class Base
|
||||
|
@ -875,6 +879,7 @@ do($ = window.jQuery, window) ->
|
|||
|
||||
@isOpen = true
|
||||
@log.debug 'open widget'
|
||||
@show()
|
||||
|
||||
if !@sessionId
|
||||
@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); }; },
|
||||
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; },
|
||||
|
@ -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;
|
||||
scripts = document.getElementsByTagName('script');
|
||||
myScript = scripts[scripts.length - 1];
|
||||
scriptHost = myScript.src.match('.*://([^:/]*).*')[1];
|
||||
scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1];
|
||||
scriptProtocol = window.location.protocol.replace(':', '');
|
||||
if (myScript && myScript.src) {
|
||||
scriptHost = myScript.src.match('.*://([^:/]*).*')[1];
|
||||
scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1];
|
||||
}
|
||||
Base = (function() {
|
||||
Base.prototype.defaults = {
|
||||
debug: false
|
||||
|
@ -1167,6 +1109,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
}
|
||||
this.isOpen = true;
|
||||
this.log.debug('open widget');
|
||||
this.show();
|
||||
if (!this.sessionId) {
|
||||
this.showLoader();
|
||||
}
|
||||
|
@ -1899,6 +1842,67 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
return window.ZammadChat = ZammadChat;
|
||||
})(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) {
|
||||
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,
|
||||
background: '#494d52',
|
||||
flat: true,
|
||||
shown: false,
|
||||
show: true,
|
||||
idleTimeout: 1,
|
||||
idleTimeoutIntervallCheck: 0.5,
|
||||
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
|
||||
App.Ajax.request({
|
||||
type: 'GET',
|
||||
url: '/tests/wait/2',
|
||||
url: '/tests/wait/3',
|
||||
success: function (data) {
|
||||
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.
|
||||
maxlength: 255,
|
||||
}
|
||||
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
create(:object_manager_attribute_text)
|
||||
.update_columns(data_option: wrong)
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
|
||||
expect { migrate }.not_to raise_error
|
||||
end
|
||||
|
@ -117,7 +114,7 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
|||
context 'for interger attributes' do
|
||||
it 'missing :min and :max' do
|
||||
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
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class FailingTestJob < ApplicationJob
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.describe ApplicationJob, type: :job do
|
||||
RSpec.describe ApplicationJob do
|
||||
|
||||
it 'syncs ActiveJob#executions to Delayed::Job#attempts' do
|
||||
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
|
||||
|
||||
# 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
|
||||
|
|
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 'models/concerns/can_lookup_examples'
|
||||
require 'models/concerns/has_search_index_backend_examples'
|
||||
|
||||
RSpec.describe Organization do
|
||||
include_examples 'CanLookup'
|
||||
include_examples 'HasSearchIndexBackend', indexed_factory: :organization
|
||||
|
||||
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
|
||||
# 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.
|
||||
config.filter_rails_from_backtrace!
|
||||
|
|
|
@ -372,7 +372,6 @@ RSpec.describe 'Monitoring', type: :request do
|
|||
end
|
||||
|
||||
it 'does check health false' do
|
||||
|
||||
channel = Channel.find_by(active: true)
|
||||
channel.status_in = 'ok'
|
||||
channel.status_out = 'error'
|
||||
|
@ -423,7 +422,7 @@ RSpec.describe 'Monitoring', type: :request do
|
|||
# health_check - scheduler job count
|
||||
travel 2.seconds
|
||||
8001.times do
|
||||
Delayed::Job.enqueue( BackgroundJobSearchIndex.new('Ticket', 1))
|
||||
SearchIndexJob.perform_later('Ticket', 1)
|
||||
end
|
||||
Scheduler.where(active: true).each do |local_scheduler|
|
||||
local_scheduler.last_run = Time.zone.now
|
||||
|
@ -520,7 +519,6 @@ RSpec.describe 'Monitoring', type: :request do
|
|||
end
|
||||
|
||||
it 'does check failed delayed job', db_strategy: :reset do
|
||||
|
||||
# disable elasticsearch
|
||||
prev_es_config = Setting.get('es_url')
|
||||
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['issues']).to be_truthy
|
||||
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
|
||||
manual_added = Delayed::Job.enqueue( BackgroundJobSearchIndex.new('Ticket', 1))
|
||||
manual_added.update!(attempts: 10)
|
||||
manual_added = SearchIndexJob.perform_later('Ticket', 1)
|
||||
Delayed::Job.find(manual_added.provider_job_id).update!(attempts: 10)
|
||||
|
||||
# health_check
|
||||
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['issues']).to be_truthy
|
||||
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
|
||||
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['issues']).to be_truthy
|
||||
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
|
||||
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['issues']).to be_truthy
|
||||
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
|
||||
Delayed::Job.delete_all
|
||||
|
|
|
@ -2120,7 +2120,7 @@ RSpec.describe 'Ticket', type: :request do
|
|||
travel 2.minutes
|
||||
ticket3
|
||||
travel 2.minutes
|
||||
ticket2.touch # rubocop:disable Rails/SkipsModelValidations
|
||||
ticket2.touch
|
||||
end
|
||||
|
||||
# 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|
|
||||
config.before(:suite) do
|
||||
next if ENV['NO_RESET_BEFORE_SUITE']
|
||||
next if !ENV['RESET_BEFORE_SUITE']
|
||||
|
||||
Rake::Task['zammad:db:reset'].invoke
|
||||
end
|
||||
|
|
|
@ -72,6 +72,7 @@ class AdminChannelEmailTest < TestCase
|
|||
|
||||
click(css: '.content.active .js-channelDelete')
|
||||
sleep 2
|
||||
# flanky
|
||||
click(css: '.modal .js-submit')
|
||||
sleep 2
|
||||
end
|
||||
|
|
|
@ -113,6 +113,7 @@ class AgentTicketAutoAssignmentTest < TestCase
|
|||
|
||||
# define auto assignment exception
|
||||
click(css: 'a[href="#manage"]')
|
||||
# flanky
|
||||
click(css: '.content.active a[href="#settings/ticket"]')
|
||||
click(css: '.content.active a[href="#auto_assignment"]')
|
||||
click(css: '.content.active .js-select.js-option[title="master@example.com"]')
|
||||
|
|
|
@ -591,6 +591,78 @@ class ChatTest < TestCase
|
|||
)
|
||||
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
|
||||
chat_url = "#{browser_url}/assets/chat/znuny.html?port=#{ENV['WS_PORT']}"
|
||||
agent = browser_instance
|
||||
|
@ -762,4 +834,31 @@ class ChatTest < TestCase
|
|||
|
||||
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
|
||||
|
|
|
@ -50,6 +50,7 @@ class IntegrationCtiTest < TestCase
|
|||
Net::HTTP.post_form(url, params.merge(event: 'newCall'))
|
||||
Net::HTTP.post_form(url, params.merge(event: 'hangup'))
|
||||
|
||||
# flanky
|
||||
watch_for(
|
||||
css: '.js-phoneMenuItem .counter',
|
||||
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: 'hangup'))
|
||||
|
||||
# flanky
|
||||
watch_for(
|
||||
css: '.js-phoneMenuItem .counter',
|
||||
value: (call_counter + 1).to_s,
|
||||
|
|
|
@ -203,6 +203,7 @@ class KeyboardShortcutsTest < TestCase
|
|||
)
|
||||
sleep 5
|
||||
shortcut(key: 'a')
|
||||
# flanky
|
||||
watch_for(
|
||||
css: '.js-notificationsContainer',
|
||||
value: 'Test Ticket for Shortcuts II',
|
||||
|
|
Loading…
Reference in a new issue