coverband 4.0.1.alpha → 4.0.1.beta
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.
- checksums.yaml +5 -5
- data/.gitignore +1 -1
- data/.rubocop.yml +5 -15
- data/.travis.yml +1 -0
- data/LICENSE +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +19 -2
- data/Rakefile +1 -1
- data/changes.md +3 -0
- data/coverband.gemspec +7 -4
- data/lib/coverband.rb +2 -3
- data/lib/coverband/configuration.rb +5 -1
- data/lib/coverband/integrations/background.rb +19 -10
- data/lib/coverband/reporters/html_report.rb +5 -1
- data/lib/coverband/reporters/web.rb +6 -59
- data/lib/coverband/utils/html_formatter.rb +18 -2
- data/lib/coverband/utils/result.rb +1 -0
- data/lib/coverband/utils/s3_report.rb +1 -1
- data/lib/coverband/utils/source_file.rb +1 -1
- data/lib/coverband/version.rb +1 -1
- data/public/application.css +37 -3
- data/public/application.js +50 -44
- data/test/fixtures/app/controllers/sample_controller.rb +10 -0
- data/test/fixtures/app/models/user.rb +10 -0
- data/test/fixtures/never.rb +2 -0
- data/test/fixtures/sample.rb +16 -0
- data/test/fixtures/skipped.rb +4 -0
- data/test/fixtures/skipped_and_executed.rb +8 -0
- data/test/fixtures/utf-8.rb +3 -0
- data/test/rails4_dummy/config/coverband.rb +1 -1
- data/test/rails4_dummy/config/routes.rb +1 -0
- data/test/rails5_dummy/config/coverband.rb +3 -2
- data/test/rails5_dummy/config/routes.rb +1 -0
- data/test/rails_test_helper.rb +6 -6
- data/test/test_helper.rb +50 -2
- data/test/unit/adapters_base_test.rb +2 -1
- data/test/unit/adapters_file_store_test.rb +2 -1
- data/test/unit/adapters_redis_store_test.rb +2 -1
- data/test/unit/background_test.rb +12 -7
- data/test/unit/collectors_coverage_test.rb +3 -2
- data/test/unit/configuration_test.rb +2 -1
- data/test/unit/coverband_test.rb +1 -1
- data/test/unit/full_stack_test.rb +1 -1
- data/test/unit/middleware_test.rb +2 -1
- data/test/unit/rack_server_checkout_test.rb +7 -8
- data/test/unit/rails_full_stack_test.rb +26 -10
- data/test/unit/reports_base_test.rb +1 -1
- data/test/unit/reports_console_test.rb +2 -1
- data/test/unit/reports_html_test.rb +2 -1
- data/test/unit/reports_web_test.rb +3 -2
- data/test/unit/utils/file_list_test.rb +54 -0
- data/test/unit/utils/lines_classifier_test.rb +109 -0
- data/test/unit/utils/result_test.rb +104 -0
- data/test/unit/{utils_s3_report_test.rb → utils/s3_report_test.rb} +4 -4
- data/test/unit/utils/source_file_line_test.rb +165 -0
- data/test/unit/utils/source_file_test.rb +149 -0
- data/views/layout.erb +10 -1
- metadata +65 -13
- data/Gemfile.rails4.lock +0 -164
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('../../test_helper', File.dirname(__FILE__))
|
4
|
+
|
5
|
+
####
|
6
|
+
# Thanks for all the help SimpleCov https://github.com/colszowka/simplecov-html
|
7
|
+
# initial version of test pulled into Coverband from Simplecov 12/19/2018
|
8
|
+
####
|
9
|
+
describe 'result' do
|
10
|
+
describe 'with a (mocked) Coverage.result' do
|
11
|
+
let(:original_result) do
|
12
|
+
{
|
13
|
+
source_fixture('sample.rb') => [nil, 1, 1, 1, nil, nil, 1, 1, nil, nil],
|
14
|
+
source_fixture('app/models/user.rb') => [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil],
|
15
|
+
source_fixture('app/controllers/sample_controller.rb') => [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil]
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'a simple cov result initialized from that' do
|
20
|
+
subject { Coverband::Utils::Result.new(original_result) }
|
21
|
+
|
22
|
+
it 'has 3 filenames' do
|
23
|
+
assert_equal 3, subject.filenames.count
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has 3 source files' do
|
27
|
+
assert_equal 3, subject.source_files.count
|
28
|
+
subject.source_files.each do |source_file|
|
29
|
+
assert source_file.is_a?(Coverband::Utils::SourceFile)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns an instance of Coverband::Utils::FileList for source_files and files' do
|
34
|
+
assert subject.files.is_a?(Coverband::Utils::FileList)
|
35
|
+
assert subject.source_files.is_a?(Coverband::Utils::FileList)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'has files equal to source_files' do
|
39
|
+
assert_equal subject.source_files, subject.files
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'has accurate covered percent' do
|
43
|
+
# in our fixture, there are 13 covered line (result in 1) in all 15 relevant line (result in non-nil)
|
44
|
+
assert_equal 86.66666666666667, subject.covered_percent
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'has accurate covered percentages' do
|
48
|
+
assert_equal [80.0, 80.0, 100.0], subject.covered_percentages
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'has accurate least covered file' do
|
52
|
+
assert subject.least_covered_file.match(/sample_controller.rb/)
|
53
|
+
end
|
54
|
+
|
55
|
+
[:covered_percent,
|
56
|
+
:covered_percentages,
|
57
|
+
:least_covered_file,
|
58
|
+
:covered_strength,
|
59
|
+
:covered_lines,
|
60
|
+
:missed_lines,
|
61
|
+
:total_lines].each do |msg|
|
62
|
+
it "responds to #{msg}" do
|
63
|
+
assert(subject.respond_to?(msg))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'dumped with to_hash' do
|
68
|
+
it 'is a hash' do
|
69
|
+
assert subject.to_hash.is_a?(Hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'loaded back with from_hash' do
|
73
|
+
let(:dumped_result) do
|
74
|
+
Coverband::Utils::Result.from_hash(subject.to_hash)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'has 3 source files' do
|
78
|
+
assert_equal subject.source_files.count, dumped_result.source_files.count
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'has the same covered_percent' do
|
82
|
+
assert_equal subject.covered_percent, dumped_result.covered_percent
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'has the same covered_percentages' do
|
86
|
+
assert_equal subject.covered_percentages, dumped_result.covered_percentages
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'has the same timestamp' do
|
90
|
+
assert_equal subject.created_at.to_i, dumped_result.created_at.to_i
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'has the same command_name' do
|
94
|
+
assert_equal subject.command_name, dumped_result.command_name
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'has the same original_result' do
|
98
|
+
assert_equal subject.original_result, dumped_result.original_result
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -1,8 +1,7 @@
|
|
1
|
-
require File.expand_path('
|
2
|
-
require 'aws-sdk'
|
1
|
+
require File.expand_path('../../test_helper', File.dirname(__FILE__))
|
3
2
|
|
4
3
|
module Coverband
|
5
|
-
class S3ReportTest < Test
|
4
|
+
class S3ReportTest < Minitest::Test
|
6
5
|
def html_version
|
7
6
|
Coverband::VERSION
|
8
7
|
end
|
@@ -15,8 +14,9 @@ module Coverband
|
|
15
14
|
object = mock('object')
|
16
15
|
s3.expects(:bucket).with('coverage-bucket').returns(bucket)
|
17
16
|
bucket.expects(:object).with('coverband/index.html').returns(object)
|
18
|
-
File.expects(:read).
|
17
|
+
File.expects(:read).with("#{Coverband.configuration.root}/coverage/index.html").returns("content ./assets/#{html_version}/")
|
19
18
|
object.expects(:put).with(body: 'content ')
|
19
|
+
Aws::S3::Client.expects(:new).returns(nil)
|
20
20
|
Aws::S3::Resource.expects(:new).returns(s3)
|
21
21
|
else
|
22
22
|
# AWS v1
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('../../test_helper', File.dirname(__FILE__))
|
4
|
+
|
5
|
+
####
|
6
|
+
# Thanks for all the help SimpleCov https://github.com/colszowka/simplecov-html
|
7
|
+
# initial version of test pulled into Coverband from Simplecov 12/19/2018
|
8
|
+
####
|
9
|
+
describe Coverband::Utils::SourceFile::Line do
|
10
|
+
describe 'a source line' do
|
11
|
+
subject do
|
12
|
+
Coverband::Utils::SourceFile::Line.new('# the ruby source', 5, 3)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns "# the ruby source" as src' do
|
16
|
+
assert_equal '# the ruby source', subject.src
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns the same for source as for src' do
|
20
|
+
assert_equal subject.src, subject.source
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'has line number 5' do
|
24
|
+
assert_equal 5, subject.line_number
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'has equal line_number, line and number' do
|
28
|
+
assert_equal subject.line, subject.line_number
|
29
|
+
assert_equal subject.number, subject.line_number
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'flagged as skipped!' do
|
33
|
+
before do
|
34
|
+
subject.skipped!
|
35
|
+
end
|
36
|
+
it 'is not covered' do
|
37
|
+
refute subject.covered?
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'is skipped' do
|
41
|
+
assert subject.skipped?
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'is not missed' do
|
45
|
+
refute subject.missed?
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'is not never' do
|
49
|
+
refute subject.never?
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'status is skipped' do
|
53
|
+
assert_equal 'skipped', subject.status
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'A source line with coverage' do
|
59
|
+
subject do
|
60
|
+
Coverband::Utils::SourceFile::Line.new('# the ruby source', 5, 3)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'has coverage of 3' do
|
64
|
+
assert_equal 3, subject.coverage
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'is covered' do
|
68
|
+
assert subject.covered?
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'is not skipped' do
|
72
|
+
refute subject.skipped?
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'is not missed' do
|
76
|
+
refute subject.missed?
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'is not never' do
|
80
|
+
refute subject.never?
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'status is covered' do
|
84
|
+
assert_equal 'covered', subject.status
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'A source line without coverage' do
|
89
|
+
subject do
|
90
|
+
Coverband::Utils::SourceFile::Line.new('# the ruby source', 5, 0)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'has coverage of 0' do
|
94
|
+
assert_equal 0, subject.coverage
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'is not covered' do
|
98
|
+
refute subject.covered?
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'is not skipped' do
|
102
|
+
refute subject.skipped?
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'is missed' do
|
106
|
+
assert subject.missed?
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'is not never' do
|
110
|
+
refute subject.never?
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'status is missed' do
|
114
|
+
assert_equal 'missed', subject.status
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'A source line with no code' do
|
119
|
+
subject do
|
120
|
+
Coverband::Utils::SourceFile::Line.new('# the ruby source', 5, nil)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'has nil coverage' do
|
124
|
+
assert_nil subject.coverage
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'is not covered' do
|
128
|
+
refute subject.covered?
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'is not skipped' do
|
132
|
+
refute subject.skipped?
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'is not missed' do
|
136
|
+
refute subject.missed?
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'is never' do
|
140
|
+
assert subject.never?
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'status is never' do
|
144
|
+
assert_equal 'never', subject.status
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'raises ArgumentError when initialized with invalid src' do
|
149
|
+
assert_raises ArgumentError do
|
150
|
+
Coverband::Utils::SourceFile::Line.new(:symbol, 5, 3)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'raises ArgumentError when initialized with invalid line_number' do
|
155
|
+
assert_raises ArgumentError do
|
156
|
+
Coverband::Utils::SourceFile::Line.new('some source', 'five', 3)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'raises ArgumentError when initialized with invalid coverage' do
|
161
|
+
assert_raises ArgumentError do
|
162
|
+
Coverband::Utils::SourceFile::Line.new('some source', 5, 'three')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('../../test_helper', File.dirname(__FILE__))
|
4
|
+
|
5
|
+
####
|
6
|
+
# Thanks for all the help SimpleCov https://github.com/colszowka/simplecov-html
|
7
|
+
# initial version of test pulled into Coverband from Simplecov 12/19/2018
|
8
|
+
####
|
9
|
+
describe Coverband::Utils::SourceFile do
|
10
|
+
COVERAGE_FOR_SAMPLE_RB = [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil, nil, nil, nil, nil, nil, nil].freeze
|
11
|
+
describe 'a source file initialized with some coverage data' do
|
12
|
+
subject do
|
13
|
+
Coverband::Utils::SourceFile.new(source_fixture('sample.rb'), COVERAGE_FOR_SAMPLE_RB)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has a filename' do
|
17
|
+
assert subject.filename
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'has source equal to src' do
|
21
|
+
assert_equal subject.source, subject.src
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'has a project filename which removes the project directory' do
|
25
|
+
assert_equal '/test/fixtures/sample.rb', subject.project_filename
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has source_lines equal to lines' do
|
29
|
+
assert_equal subject.source_lines, subject.lines
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has 16 source lines' do
|
33
|
+
assert_equal 16, subject.lines.count
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'has all source lines of type Coverband::Utils::SourceFile::Line' do
|
37
|
+
subject.lines.each do |line|
|
38
|
+
assert line.is_a?(Coverband::Utils::SourceFile::Line)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "has 'class Foo' as line(2).source" do
|
43
|
+
assert_equal "class Foo\n", subject.line(2).source
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns lines number 2, 3, 4, 7 for covered_lines' do
|
47
|
+
assert_equal [2, 3, 4, 7], subject.covered_lines.map(&:line)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns lines number 8 for missed_lines' do
|
51
|
+
assert_equal [8], subject.missed_lines.map(&:line)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'returns lines number 1, 5, 6, 9, 10, 16 for never_lines' do
|
55
|
+
assert_equal [1, 5, 6, 9, 10, 16], subject.never_lines.map(&:line)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'returns line numbers 11, 12, 13, 14, 15 for skipped_lines' do
|
59
|
+
assert_equal [11, 12, 13, 14, 15], subject.skipped_lines.map(&:line)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'has 80% covered_percent' do
|
63
|
+
assert_equal 80.0, subject.covered_percent
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'simulating potential Ruby 1.9 defect -- see Issue #56' do
|
68
|
+
subject do
|
69
|
+
Coverband::Utils::SourceFile.new(source_fixture('sample.rb'), COVERAGE_FOR_SAMPLE_RB + [nil])
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'has 16 source lines regardless of extra data in coverage array' do
|
73
|
+
# Do not litter test output with known warning
|
74
|
+
capture_stderr { assert_equal 16, subject.lines.count }
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'prints a warning to stderr if coverage array contains more data than lines in the file' do
|
78
|
+
captured_output = capture_stderr do
|
79
|
+
subject.lines
|
80
|
+
end
|
81
|
+
|
82
|
+
assert(captured_output.match(/^Warning: coverage data provided/))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'a file that is never relevant' do
|
87
|
+
COVERAGE_FOR_NEVER_RB = [nil, nil].freeze
|
88
|
+
|
89
|
+
subject do
|
90
|
+
Coverband::Utils::SourceFile.new(source_fixture('never.rb'), COVERAGE_FOR_NEVER_RB)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'has 0.0 covered_strength' do
|
94
|
+
assert_equal 0.0, subject.covered_strength
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'has 0.0 covered_percent' do
|
98
|
+
assert_equal 100.0, subject.covered_percent
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'a file where nothing is ever executed mixed with skipping #563' do
|
103
|
+
COVERAGE_FOR_SKIPPED_RB = [nil, nil, nil, nil].freeze
|
104
|
+
|
105
|
+
subject do
|
106
|
+
Coverband::Utils::SourceFile.new(source_fixture('skipped.rb'), COVERAGE_FOR_SKIPPED_RB)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'has 0.0 covered_strength' do
|
110
|
+
assert_equal 0.0, subject.covered_strength
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'has 0.0 covered_percent' do
|
114
|
+
assert_equal 0.0, subject.covered_percent
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'a file where everything is skipped and missed #563' do
|
119
|
+
COVERAGE_FOR_SKIPPED_RB_2 = [nil, nil, 0, nil].freeze
|
120
|
+
|
121
|
+
subject do
|
122
|
+
Coverband::Utils::SourceFile.new(source_fixture('skipped.rb'), COVERAGE_FOR_SKIPPED_RB_2)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'has 0.0 covered_strength' do
|
126
|
+
assert_equal 0.0, subject.covered_strength
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'has 0.0 covered_percent' do
|
130
|
+
assert_equal 0.0, subject.covered_percent
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'a file where everything is skipped/irrelevamt but executed #563' do
|
135
|
+
COVERAGE_FOR_SKIPPED_AND_EXECUTED_RB = [nil, nil, 1, 1, 0, nil, nil, nil].freeze
|
136
|
+
|
137
|
+
subject do
|
138
|
+
Coverband::Utils::SourceFile.new(source_fixture('skipped_and_executed.rb'), COVERAGE_FOR_SKIPPED_AND_EXECUTED_RB)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'has 0.0 covered_strength' do
|
142
|
+
assert_equal 0.0, subject.covered_strength
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'has 0.0 covered_percent' do
|
146
|
+
assert_equal 0.0, subject.covered_percent
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/views/layout.erb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html xmlns='http://www.w3.org/1999/xhtml'>
|
3
3
|
<head>
|
4
|
-
<title>
|
4
|
+
<title>Coverband: <%= Coverband::VERSION %></title>
|
5
5
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
6
6
|
<script src='<%= assets_path('application.js') %>' type='text/javascript'></script>
|
7
7
|
<link href='<%= assets_path('application.css') %>' media='screen, projection, print' rel='stylesheet' type='text/css'>
|
@@ -14,6 +14,15 @@
|
|
14
14
|
<img src="<%= assets_path('loading.gif') %>" alt="loading"/>
|
15
15
|
</div>
|
16
16
|
<div id="wrapper" style="display:none;">
|
17
|
+
<div id="header">
|
18
|
+
<a href='<%= base_path %>'>Coverband Admin</a>
|
19
|
+
<%= button("#{base_path}collect_coverage", 'force coverage collection') %>
|
20
|
+
<%= button("#{base_path}reload_files", 'reload Coverband files') %>
|
21
|
+
<%= button("#{base_path}clear", 'clear coverage report', delete: true) %>
|
22
|
+
</div>
|
23
|
+
<% if notice.to_s.length > 0 %>
|
24
|
+
<div class="notice"><%= notice %></div>
|
25
|
+
<% end %>
|
17
26
|
<div class="timestamp">Generated <%= timeago(Time.now) %></div>
|
18
27
|
<ul class="group_tabs"></ul>
|
19
28
|
|