blinkr 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ require 'blinkr/error'
1
2
  require 'ostruct'
2
3
  require 'slim'
3
4
 
@@ -38,18 +39,30 @@ module Blinkr
38
39
  lines << elm.line
39
40
  snippets << elm.to_s
40
41
  end
41
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'HTML Compatibility/Correctness', :type => '<title> tag declared more than once', :title => %Q{<title> declared more than once (lines #{lines.join(', ')})}, :message => %Q{<title> declared more than onc}, :snippet => snippets.join('\n'), :icon => 'fa-header' })
42
+ page.errors << Blinkr::Error.new({:severity => :info, :category => 'HTML Compatibility/Correctness',
43
+ :type => '<title> tag declared more than once',
44
+ :title => %Q{<title> declared more than once (lines #{lines.join(', ')})},
45
+ :message => %Q{<title> declared more than onc},
46
+ :snippet => snippets.join('\n'), :icon => 'fa-header'})
42
47
  elsif elms.empty?
43
- page.errors << OpenStruct.new({ :severity => 'warning', :category => 'SEO', :type => '<title> tag missing', :title => %Q{<title> tag missing}, :message => %Q{<title> tag missing}, :icon => 'fa-header' })
48
+ page.errors << Blinkr::Error.new({:severity => :warning, :category => 'SEO', :type => '<title> tag missing',
49
+ :title => %Q{<title> tag missing}, :message => %Q{<title> tag missing},
50
+ :icon => 'fa-header'})
44
51
  else
45
52
  title = elms.first.text
46
53
  @titles[title] ||= {}
47
54
  @titles[title][page.response.effective_url] = page
48
55
  if title.length < 20
49
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => 'page title too short', :title => %Q{<title> too short (line #{elms.first.line})}, :message => %Q{<title> too short (< 20 characters)}, :snippet => elms.first.to_s, :icon => 'fa-header' })
56
+ page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO', :type => 'page title too short',
57
+ :title => %Q{<title> too short (line #{elms.first.line})},
58
+ :message => %Q{<title> too short (< 20 characters)},
59
+ :snippet => elms.first.to_s, :icon => 'fa-header'})
50
60
  end
51
61
  if title.length > 55
52
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => 'page title too long', :title => %Q{<title> too long (line #{elms.first.line})}, :message => %Q{<title> too long (> 55 characters)}, :snippet => elms.first.to_s, :icon => 'fa-header' })
62
+ page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO', :type => 'page title too long',
63
+ :title => %Q{<title> too long (line #{elms.first.line})},
64
+ :message => %Q{<title> too long (> 55 characters)},
65
+ :snippet => elms.first.to_s, :icon => 'fa-header'})
53
66
  end
54
67
  end
55
68
  end
@@ -63,18 +76,34 @@ module Blinkr
63
76
  lines << elm.line
64
77
  snippets << elm.to_s
65
78
  end
66
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'HTML Compatibility/Correctness', :type => '<meta name="description"> tag declared more than once', :title => %Q{<meta name="description"> tag declared more than once (lines #{lines.join(', ')})}, :message => %Q{<meta name="description"> tag declared more than once}, :snippet => snippets.join('\n'), :icon => 'fa-header' })
79
+ page.errors << Blinkr::Error.new({:severity => :info, :category => 'HTML Compatibility/Correctness',
80
+ :type => '<meta name="description"> tag declared more than once',
81
+ :title => %Q{<meta name="description"> tag declared more than once (lines #{lines.join(', ')})},
82
+ :message => %Q{<meta name="description"> tag declared more than once},
83
+ :snippet => snippets.join('\n'), :icon => 'fa-header'})
67
84
  elsif elms.empty?
68
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => '<meta name="description"> tag missing', :title => %Q{<meta name="description"> tag missing}, :message => %Q{<meta name="description"> tag missing}, :icon => 'fa-header' })
85
+ page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO',
86
+ :type => '<meta name="description"> tag missing',
87
+ :title => %Q{<meta name="description"> tag missing},
88
+ :message => %Q{<meta name="description"> tag missing},
89
+ :icon => 'fa-header'})
69
90
  else
70
91
  desc = elms.first['content']
71
92
  @descriptions[desc] ||= {}
72
93
  @descriptions[desc][page.response.effective_url] = page
73
94
  if desc.length < 60
74
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => '<meta name="description"> too short', :title => %Q{<meta name="description"> too short (lines #{elms.first.line})}, :message => %Q{<meta name="description"> too short (< 60 characters)}, :snippet => elms.first.to_s, :icon => 'fa-header' })
95
+ page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO',
96
+ :type => '<meta name="description"> too short',
97
+ :title => %Q{<meta name="description"> too short (lines #{elms.first.line})},
98
+ :message => %Q{<meta name="description"> too short (< 60 characters)},
99
+ :snippet => elms.first.to_s, :icon => 'fa-header'})
75
100
  end
76
101
  if desc.length > 115
77
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => '<meta name="description"> too long', :title => %Q{<meta name="description"> too long (lines #{elms.first.line})}, :message => %Q{<meta name="description"> too long (> 115 characters)}, :snippet => elms.first.to_s, :icon => 'fa-header' })
102
+ page.errors << Blinkr::Error.new({:severity => :info, :category => 'SEO',
103
+ :type => '<meta name="description"> too long',
104
+ :title => %Q{<meta name="description"> too long (lines #{elms.first.line})},
105
+ :message => %Q{<meta name="description"> too long (> 115 characters)},
106
+ :snippet => elms.first.to_s, :icon => 'fa-header'})
78
107
  end
79
108
  end
80
109
  end
@@ -1,3 +1,5 @@
1
+ require 'blinkr/error'
2
+
1
3
  module Blinkr
2
4
  module Extensions
3
5
  class Resources
@@ -11,7 +13,9 @@ module Blinkr
11
13
  start = error['errorString'].rindex('server replied: ')
12
14
  message = error['errorString'].slice(start.nil? ? 0 : start + 16, error['errorString'].length) unless error['errorString'].nil?
13
15
  code = error['errorCode'].nil? ? nil : error['errorCode'].to_i
14
- page.errors << OpenStruct.new({ :severity => 'danger', :category => 'Resources missing', :type => 'Resource loading error', :title => error['url'], :code => code, :message => message, :icon => 'fa-file-image-o' })
16
+ page.errors << Blinkr::Error.new(:severity => 'danger', :category => 'Resources missing',
17
+ :type => 'Resource loading error', :title => error['url'],
18
+ :code => code, :message => message, :icon => 'fa-file-image-o')
15
19
  end
16
20
  end
17
21
 
@@ -0,0 +1,20 @@
1
+ class Numeric
2
+ def duration
3
+ rest, secs = self.divmod( 60 ) # self is the time difference t2 - t1
4
+ rest, mins = rest.divmod( 60 )
5
+ days, hours = rest.divmod( 24 )
6
+
7
+ # the above can be factored out as:
8
+ # days, hours, mins, secs = self.duration_as_arr
9
+ #
10
+ # this is not so great, because it could include zero values:
11
+ # self.duration_as_arr.zip ['Days','Hours','Minutes','Seconds']).flatten.join ' '
12
+
13
+ result = []
14
+ result << "#{days} Days" if days > 0
15
+ result << "#{hours} Hours" if hours > 0
16
+ result << "#{mins} Minutes" if mins > 0
17
+ result << "#{secs} Seconds" if secs > 0
18
+ return result.join(' ')
19
+ end
20
+ end
@@ -2,13 +2,15 @@ require 'uri'
2
2
 
3
3
  module Blinkr
4
4
  module HttpUtils
5
-
6
- def sanitize dest, src
5
+
6
+ def sanitize(dest, src)
7
+ return nil if (dest.nil? || src.nil?)
8
+
7
9
  # src is the page that tried to load the URL
8
10
  # URI fails to handle #! style fragments, so we chomp them
9
11
  src = src[0, src.rindex('#!')] unless src.rindex('#!').nil?
10
12
  src_uri = URI(src)
11
-
13
+
12
14
  # Remove the query and fragment from the SRC URI, as we are going to use it to resolve the relative dest URIs
13
15
  src_uri.query = nil
14
16
  src_uri.fragment = nil
@@ -23,13 +25,16 @@ module Blinkr
23
25
  dest_uri.fragment = nil if @config.ignore_fragments
24
26
 
25
27
  # If we have a relative URI, or just a fragment, join what we have to our base URL
26
- dest_uri = URI.join(src_uri, dest) if ( empty?(dest_uri.path) && !empty?(dest_uri.fragment ) ) || dest_uri.relative?
28
+ dest_uri = URI.join(src_uri, dest) if (empty?(dest_uri.path) && !empty?(dest_uri.fragment)) || dest_uri.relative?
27
29
 
28
30
  # If we have an absolute path URI, join it to the base URL
29
31
  dest_uri = URI.join(base_uri.scheme, base_uri.hostname, base_uri.port, dest_uri) if empty?(dest_uri.scheme) && empty?(dest_uri.hostname)
30
-
32
+
31
33
  dest = dest_uri.to_s
32
34
  rescue URI::InvalidURIError, URI::InvalidComponentError, URI::BadURIError
35
+ # ignored
36
+ rescue StandardError
37
+ return nil
33
38
  end
34
39
  dest.chomp('#').chomp('index.html')
35
40
  end
@@ -0,0 +1,59 @@
1
+ require 'manticore'
2
+ require 'blinkr/cache'
3
+ require 'blinkr/http_utils'
4
+
5
+ module Blinker
6
+ class ManticoreWrapper
7
+ include HttpUtils
8
+
9
+ attr_reader count
10
+
11
+ def initialize(config, context)
12
+ @config = config.validate
13
+ @client = Manticore::Client.new({:pool_max => (@config.maxconnects || 50),
14
+ :pool_max_per_route => 10
15
+ })
16
+ @count = 0
17
+ @context = context
18
+ end
19
+
20
+ def process_all(urls, limit, opts = {}, &block)
21
+ urls.each do |url|
22
+ process url, limit, opts, &block
23
+ end
24
+ @client.execute!
25
+ end
26
+
27
+ def process(url, limit, opts = {}, &block)
28
+ _process url, limit, limit, opts, &block
29
+ end
30
+
31
+ def name
32
+ 'manticore'
33
+ end
34
+
35
+ private
36
+
37
+ def _process(url, limit, max, opts = {}, &block)
38
+ unless @config.skipped? url
39
+ resp = @client.async.get(url)
40
+ resp.on_complete do |resp|
41
+ if retry? resp
42
+ if limit > 1
43
+ puts "Loading #{url} via manticore (attempt #{max - limit + 2} of #{max})" if @config.verbose
44
+ _process(url, limit - 1, max, &Proc.new)
45
+ else
46
+ puts "Loading #{url} via manticore failed" if @config.verbose
47
+ response = @client.respond_with(:code => 0, :status_message => "Server timed out after #{max} retries",
48
+ :body => '').get(url)
49
+ block.call response, nil, nil
50
+ end
51
+ else
52
+ block.call resp, nil, nil
53
+ end
54
+ @count += 1
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -2,9 +2,9 @@ require 'typhoeus'
2
2
  require 'ostruct'
3
3
  require 'tempfile'
4
4
  require 'blinkr/http_utils'
5
+ require 'blinkr/cache'
5
6
  require 'parallel'
6
7
 
7
-
8
8
  module Blinkr
9
9
  class PhantomJSWrapper
10
10
  include HttpUtils
@@ -17,16 +17,17 @@ module Blinkr
17
17
  @config = config.validate
18
18
  @context = context
19
19
  @count = 0
20
+ @cache = Blinkr::Cache.new
20
21
  end
21
22
 
22
- def process_all urls, limit, &block
23
- Parallel.each(urls, :in_threads => @config.phantomjs_threads) do |url|
24
- process url, limit, &block
23
+ def process_all urls, limit, opts = {}, &block
24
+ Parallel.each(urls, :in_threads => (@config.phantomjs_threads || Parallel.processor_count * 2)) do |url|
25
+ process url, limit, opts, &block
25
26
  end
26
27
  end
27
28
 
28
- def process url, limit, &block
29
- _process url, limit, limit, &block
29
+ def process url, limit, opts = {}, &block
30
+ _process url, limit, limit, opts, &block
30
31
  end
31
32
 
32
33
  def name
@@ -35,15 +36,25 @@ module Blinkr
35
36
 
36
37
  private
37
38
 
38
- def _process url, limit, max, &block
39
+ def _process url, limit, max, opts = {}, &block
39
40
  raise "limit must be set. url: #{url}, limit: #{limit}, max: #{max}" if limit.nil?
40
41
  unless @config.skipped? url
41
42
  Tempfile.open('blinkr') do|f|
43
+ # Try and sidestep any unnecessary requests by checking the cache
44
+ request = Typhoeus::Request.new(url)
45
+ if @cache.get(request)
46
+ return block.call response, @cache.get("#{url}-resourceErrors"), @cache.get("#{url}-javascriptErrors")
47
+ end
48
+
42
49
  if system "phantomjs #{SNAP_JS} #{url} #{@config.viewport} #{f.path}"
43
50
  json = JSON.load(File.read(f.path))
44
- response = Typhoeus::Response.new(code: 200, body: json['content'], effective_url: json['url'], mock: true)
51
+ response = Typhoeus::Response.new(:code => 200, :body => json['content'], :effective_url => json['url'],
52
+ :mock => true)
45
53
  response.request = Typhoeus::Request.new(url)
46
54
  Typhoeus.stub(url).and_return(response)
55
+ @cache.set(response.request, response)
56
+ @cache.set("#{url}-resourceErrors", json['resourceErrors'])
57
+ @cache.set("#{url}-javascriptErrors", json['javascriptErrors'])
47
58
  block.call response, json['resourceErrors'], json['javascriptErrors']
48
59
  else
49
60
  if limit > 1
@@ -51,9 +62,11 @@ module Blinkr
51
62
  _process url, limit - 1, max, &block
52
63
  else
53
64
  puts "Loading #{url} via phantomjs failed" if @config.verbose
54
- response = Typhoeus::Response.new(code: 0, status_message: "Server timed out after #{max} retries", mock: true)
65
+ response = Typhoeus::Response.new(:code => 0, :status_message => "Server timed out after #{max} retries",
66
+ :mock => true)
55
67
  response.request = Typhoeus::Request.new(url)
56
68
  Typhoeus.stub(url).and_return(response)
69
+ @cache.set(response.request, response)
57
70
  block.call response, nil, nil
58
71
  end
59
72
  end
@@ -1,238 +1,214 @@
1
- doctype html
2
- head
3
- link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"
4
- script src="http://code.jquery.com/jquery-1.11.0.min.js"
5
- script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"
6
- link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet"
7
- meta name="viewport" content="width=device-width, initial-scale=1"
8
- title Blinkr
9
- body
10
-
11
- .container
12
- .row
13
- h1 Blinkr
14
- p.lead Blackbox testing for websites
15
-
16
- h2
17
- | Pages
18
- .label.label-primary.pull-right = blinkr.error_count
19
- .panel.panel-default
20
- .panel-heading
21
- h4
22
- a> data-toggle="collapse" href="#filters" id="control-filters"
23
- i.fa.fa-caret-square-o-down
24
- | Filters
25
- .panel-collapse.collapse.in#filters
26
- .panel-body
27
- form.row
28
- .col-md-4#severities
29
- .panel.panel-default
30
- .panel-heading
31
- .checkbox
32
- label
33
- input.severities type="checkbox" value="severities" onchange="severities()" checked="true"
34
- | Severity
35
- .label.label-primary< = blinkr.error_count
36
- .panel-body
37
- - blinkr.severities.each do |severity, metadata|
1
+ doctype html
2
+ html[ng-app="blinkr"]
3
+ head
4
+ meta[http-equiv="Content-Type" content="text/html; charset=UTF-8"]
5
+ link[href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"]
6
+ link[href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet"]
7
+ title
8
+ | Blinkr
9
+ meta[content="width=device-width, initial-scale=1" name="viewport"]
10
+ body
11
+ div[ng-controller="ErrorCtrl"]
12
+ .container
13
+ h1
14
+ | Blinkr
15
+ p.lead
16
+ | Blackbox testing for websites
17
+ h2
18
+ ' Pages
19
+ .label.label-default>
20
+ | {{numPages}}
21
+ ' Errors
22
+ .label.label-default
23
+ | {{report.total}}
24
+ .panel.panel-default
25
+ .panel-heading
26
+ h4
27
+ a#control-filters[data-toggle="collapse" href="#filters"]
28
+ i.fa.fa-caret-square-o-down
29
+ | Filters
30
+ #filters.panel-collapse.collapse.in
31
+ .panel-body
32
+ form.row
33
+ #severities.col-md-2
34
+ .panel.panel-default
35
+ .panel-heading
38
36
  .checkbox
39
37
  label
40
- input.severity type="checkbox" value="severity-#{metadata.id}" onchange="updateSeverities(); filter()" checked="true"
41
- | #{severity.capitalize}
42
- div< class="label label-#{severity}" = metadata.count
43
- .col-md-8#types style="padding-left:20px; border-left: 1px solid #ccc;"
44
- .row
45
- - blinkr.categories.each do |category, metadata|
46
- .col-md-6
38
+ ' Severity
39
+ .panel-body[ng-repeat="(key, value) in filters.severities"]
40
+ .checkbox
41
+ label>
42
+ input.type>[ng-model="filters.severities[key].enabled" type="checkbox"]
43
+ ' {{key}}
44
+ div[class="label label-{{key}}"]
45
+ ' {{value.count}}
46
+ #category.col-md-5[style="padding-left:20px; border-left: 1px solid #ccc;"]
47
+ .row
48
+ .col-md-12
47
49
  .panel.panel-default
48
50
  .panel-heading
49
51
  .checkbox
50
52
  label
51
- input.category type="checkbox" value="category-#{metadata.id}" onchange="category()" checked="true"
52
- | #{category}
53
- - metadata.severities.each do |severity, count|
54
- div< class="label label-#{severity}" = count
55
- .panel-body
56
- - metadata.types.each do |type, type_metadata|
57
- .checkbox
58
- label
59
- input.type type="checkbox" value="type-#{metadata.id}-#{type_metadata.id}" onchange="updateCategory(); filter();" checked="true" data-category="category-#{metadata.id}"
60
- | #{type}
61
- - type_metadata.severities.each do |severity, count|
62
- div< class="label label-#{severity}" = count
63
- javascript:
64
- $('#filters').on('show.bs.collapse', function () {
65
- $('a#control-filters').html('<i class="fa fa-caret-square-o-down"></i>');
66
- });
67
- $('#filters').on('hide.bs.collapse', function () {
68
- $('a#control-filters').html('<i class="fa fa-caret-square-o-right"></i>');
69
- });
70
- .spinner.center-block style="padding-top: 2em"
71
- img src="http://static.jboss.org/www/images/_images_icons_ajax-loader-8.gif"
72
- javascript:
73
- $( '.spinner' ).hide();
74
- #pages
75
- - blinkr.pages.each_with_index do |(url, page), index|
76
-
77
- div class="page panel panel-#{page.max_severity}"
53
+ ' Category
54
+ .panel-body[ng-repeat="(key, value) in filters.categories"]
55
+ .checkbox
56
+ label>
57
+ input.type[ng-model="filters.categories[key].enabled" type="checkbox"]
58
+ ' {{key}}
59
+ .label.label-default
60
+ | {{value.count}}
61
+ #types.col-md-5[style="padding-left:20px; border-left: 1px solid #ccc;"]
62
+ .row
63
+ .col-md-12
64
+ .panel.panel-default
65
+ .panel-heading
66
+ .checkbox
67
+ label>
68
+ ' Type
69
+ .panel-body[ng-repeat="(key, value) in filters.types"]
70
+ .checkbox
71
+ label>
72
+ input.type[ng-model="filters.types[key].enabled" type="checkbox"]
73
+ | {{key}}
74
+ .label.label-default
75
+ | {{value.count}}
76
+ .page[ng-repeat="(url,page) in report.pages | showPage:filters"]
77
+ div[class="panel panel-{{page.max_severity}}"]
78
78
  .panel-heading
79
- a> data-toggle="collapse" href="#p-#{index}" id="control-p-#{index}"
80
- i.fa.fa-caret-square-o-right>
81
- ' #{url}
82
- a> href="#{url}" target='_blank'
83
- i.fa.fa-external-link
84
- a href="view-source:#{url}" target='_blank'
85
- i.fa.fa-file-code-o
86
- .panel-body.collapse id="p-#{index}"
87
- ul.list-group
88
- - page.errors.each do |error|
89
- == engine.transform page, error do
90
- li.list-group-item.error data-ids="type-#{blinkr.categories[error.category].id}-#{blinkr.categories[error.category].types[error.type].id} severity-#{error.severity} category-#{blinkr.categories[error.category].id}"
91
- - unless error.icon.nil?
92
- i class="fa fa-2x #{error.icon} pull-left"
93
- - unless error.code.nil? && error.message.nil?
94
- div class="pull-right label label-#{error.severity}"
95
- - unless error.code.nil?
96
- ' #{error.code}
97
- - unless error.message.nil?
98
- | #{error.message}
99
- div
100
- ' #{error.title}
101
- - if error.url
102
- a href="#{error.url}" target='_blank'
103
- i.fa.fa-external-link
104
- - if error.detail
105
- p= error.detail
106
- - if error.snippet
107
- pre #{error.snippet}
108
- javascript:
109
- $('#p-#{index}').on('show.bs.collapse', function () {
110
- $('a#control-p-#{index}').html('<i class="fa fa-caret-square-o-down"></i>');
111
- });
112
- $('#p-#{index}').on('hide.bs.collapse', function () {
113
- $('a#control-p-#{index}').html('<i class="fa fa-caret-square-o-right"></i>');
114
- });
115
-
116
- - if blinkr.pages.length == 0
117
- .panel.panel-success
118
- .panel-heading
119
- a id='resources'
120
- | No errors detected
79
+ .panel-title
80
+ ul.list-inline
81
+ li
82
+ a>[data-toggle="collapse" href="#"]
83
+ i.fa.fa-caret-square-o-right
84
+ | {{url}} ({{page.errors.length}} total errors)
85
+ li
86
+ a[href="{{url}}" target="_blank"]
87
+ i.fa.fa-external-link
88
+ li
89
+ a[href="{{url}}" target="_blank"]
90
+ i.fa.fa-file-code-o
91
+ .panel-body.collapse.panel-collapse
92
+ ul
93
+ li.list-group-item.error[ng-repeat="(i,page_error) in page.errors | displayError:filters"]
94
+ i.fa.fa-2x.fa-bookmark-o.pull-left
95
+ div[class="pull-right label label-{{page_error.severity}}"]
96
+ | {{page_error.code}} {{page_error.message}}
97
+ div
98
+ ' {{page_error.title}}
99
+ a[href="{{page_error.title}}" target="_blank"]
100
+ i.fa.fa-external-link
101
+ pre
102
+ | {{page_error.snippet}}
103
+ script[src="http://code.jquery.com/jquery-2.1.4.min.js"]
104
+ script[src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"]
105
+ script[src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"]
106
+ javascript:
107
+ var blinkr = angular.module('blinkr', []);
108
+ blinkr.factory('report', function reportFactory() {
109
+ return #{{errors}};
110
+ });
121
111
 
122
- - engine.append(blinkr).each do |result|
123
- == result
112
+ blinkr.controller("ErrorCtrl", ['$scope', 'report', function ($scope, report) {
113
+ $scope.report = report;
114
+ $scope.filters = {severities: {}, categories: {}, types: {}};
115
+ $scope.report = report;
116
+ $scope.numPages = Object.keys(report.pages).length;
117
+ window.scope = $scope;
124
118
 
125
- javascript:
126
- $('[data-toggle="tooltip"]').tooltip({'placement': 'top'});
119
+ Object.keys($scope.report.severity).forEach(function (s) {
120
+ $scope.filters.severities[s] = {enabled: false, count: report.severity[s].count};
121
+ });
127
122
 
128
- function category() {
129
- $( "#filters #types input.category[type=checkbox]" ).each( function() {
130
- var category = $( this ).val();
131
- var checked = $( this ).prop("checked");
132
- $( "#filters #types input.type[type=checkbox][data-category=" + category + "]" ).each( function() {
133
- $( this ).prop( "checked", checked );
123
+ Object.keys($scope.report.category).forEach(function (c) {
124
+ $scope.filters.categories[c] = {enabled: false, count: report.category[c].count};
134
125
  });
135
- });
136
- filter();
137
- }
138
126
 
139
- function severities() {
140
- $( "#filters #severities input.severities[type=checkbox]" ).each( function() {
141
- var checked = $( this ).prop("checked");
142
- $( "#filters #severities input.severity[type=checkbox]" ).each( function() {
143
- $( this ).prop( "checked", checked );
127
+ Object.keys($scope.report.type).forEach(function (t) {
128
+ $scope.filters.types[t] = {enabled: false, count: report.type[t].count};
144
129
  });
130
+ }]);
131
+
132
+ blinkr.filter('displayError', function () {
133
+ return function (errors, filters) {
134
+ var returned_errors = [];
135
+ errors.forEach(function (error) {
136
+ if (showError(error, filters)) {
137
+ returned_errors.push(error);
138
+ }
139
+ });
140
+ panelBind();
141
+ return returned_errors;
142
+ }
145
143
  });
146
- filter();
147
- }
148
144
 
149
- function updateCategory() {
150
- $( "#filters #types input.category[type=checkbox]" ).each( function() {
151
- var category = $( this ).val();
152
- var checked = 0;
153
- var unchecked = 0;
154
- $( "#filters #types input.type[type=checkbox][data-category=" + category + "]" ).each( function() {
155
- if ( $( this ).prop( "checked" ) ) {
156
- checked++;
157
- } else {
158
- unchecked++;
159
- }
160
- });
161
- if (unchecked == 0) {
162
- $( this ).prop( "checked", true );
163
- } else {
164
- $( this ).prop( "checked", false );
145
+ blinkr.filter('showPage', function () {
146
+ return function (all_pages, filters) {
147
+ var pages = {};
148
+ Object.keys(all_pages).forEach(function (url) {
149
+ var page = all_pages[url];
150
+ page.errors.forEach(function (error) {
151
+ if (showError(error, filters)) {
152
+ pages[url] = all_pages[url];
153
+ }
154
+ });
155
+ });
156
+ return pages;
165
157
  }
166
158
  });
167
- }
168
159
 
169
- function updateSeverities() {
170
- $( "#filters #severities input.severities[type=checkbox]" ).each( function() {
171
- var checked = 0;
172
- var unchecked = 0;
173
- $( "#filters #severities input.severity[type=checkbox]" ).each( function() {
174
- if ( $( this ).prop( "checked" ) ) {
175
- checked++;
176
- } else {
177
- unchecked++;
160
+ function enabledFilters(filters, filterName) {
161
+ var enabled = [];
162
+ Object.keys(filters[filterName]).forEach(function (f) {
163
+ if (filters[filterName][f].enabled) {
164
+ enabled.push(f);
178
165
  }
179
166
  });
180
- if (unchecked == 0) {
181
- $( this ).prop( "checked", true );
182
- } else {
183
- $( this ).prop( "checked", false );
184
- }
185
- });
186
- }
167
+ return enabled;
168
+ }
187
169
 
188
- function filter() {
189
- $( ".pages" ).hide();
190
- $( ".spinner" ).show();
191
- var selectedTypes = [];
192
- var selectedSeverities = [];
193
- $( "#filters #severities input[type=checkbox]" ).each( function() {
194
- if ( $( this ).prop( "checked" ) ) {
195
- selectedSeverities.push( $( this ).val() );
196
- }
197
- });
198
- $( "#filters #types input.type[type=checkbox]" ).each( function() {
199
- if ( $( this ).prop( "checked" ) ) {
200
- selectedTypes.push( $( this ).val() );
170
+ function showError(error, filters) {
171
+ var categories = enabledFilters(filters, 'categories'),
172
+ severities = enabledFilters(filters, 'severities'),
173
+ types = enabledFilters(filters, 'types'),
174
+ possibleReturn = false;
175
+
176
+ if (categories.length > 0 && categories.indexOf(error.category) > -1) {
177
+ possibleReturn = true;
201
178
  }
202
- });
203
- $( "#pages .page" ).each( function() {
204
- var count = 0;
205
- $( "li.error[data-ids]", this ).each( function() {
206
- var showType = false;
207
- var showSeverity = false;
208
- var ids = $( this ).attr("data-ids");
209
- for (i = 0; i < selectedTypes.length; i++) {
210
- if (ids.indexOf(selectedTypes[i]) >= 0) {
211
- showType = true;
212
- continue;
213
- }
179
+
180
+ if (possibleReturn) {
181
+ if (severities.length > 0) {
182
+ possibleReturn = severities.indexOf(error.severity) > -1;
214
183
  }
215
- for (i = 0; i < selectedSeverities.length; i++) {
216
- if (ids.indexOf(selectedSeverities[i]) >= 0) {
217
- showSeverity = true;
218
- continue;
219
- }
184
+ } else {
185
+ if (severities.length > 0) {
186
+ possibleReturn = severities.indexOf(error.severity) > -1;
220
187
  }
221
- if (showSeverity && showType) {
222
- $( this ).show();
223
- count++;
224
- } else {
225
- $( this ).hide();
188
+ }
189
+
190
+ if (possibleReturn) {
191
+ if (types.length > 0) {
192
+ possibleReturn = !!(types.length > 0 && types.indexOf(error.type) > -1);
226
193
  }
227
- });
228
- if (count == 0) {
229
- $( this ).hide();
230
194
  } else {
231
- $( this ).show();
195
+ if (types.length > 0) {
196
+ possibleReturn = !!(types.length > 0 && types.indexOf(error.type) > -1);
197
+ }
232
198
  }
233
- });
234
- $( ".pages" ).show();
235
- $( ".spinner" ).hide();
236
- }
237
199
 
238
-
200
+ return possibleReturn;
201
+ }
202
+
203
+ window.panelBind = function () {
204
+ $('.panel-title').find('a[data-toggle=collapse]').on('click', function (e) {
205
+ e.preventDefault();
206
+ var target = $(this).parents('.panel').find('.panel-collapse');
207
+ target.collapse('toggle');
208
+ });
209
+ $('.page').find('panel-collapse').collapse({toggle: false});
210
+ };
211
+
212
+ angular.element(document).ready(function () {
213
+ panelBind();
214
+ });