blinkr 0.2.9 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ });