crazy_ivan 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -5
- data/bin/crazy_ivan +5 -32
- data/bin/test_report2campfire +6 -1
- data/lib/crazy_ivan/process_manager.rb +48 -0
- data/lib/crazy_ivan/report_assembler.rb +35 -22
- data/lib/crazy_ivan/templates/index.html +200 -154
- data/lib/crazy_ivan/test_runner.rb +57 -9
- data/lib/crazy_ivan/vendor/open4.rb +1 -1
- data/lib/crazy_ivan/version.rb +1 -1
- data/lib/crazy_ivan.rb +18 -15
- metadata +3 -12
- data/lib/crazy_ivan/html_asset_crush.rb +0 -56
- data/lib/crazy_ivan/templates/css/ci.css +0 -34
- data/lib/crazy_ivan/templates/javascript/builder.js +0 -136
- data/lib/crazy_ivan/templates/javascript/controls.js +0 -965
- data/lib/crazy_ivan/templates/javascript/dragdrop.js +0 -974
- data/lib/crazy_ivan/templates/javascript/effects.js +0 -1123
- data/lib/crazy_ivan/templates/javascript/json-template.js +0 -544
- data/lib/crazy_ivan/templates/javascript/prototype.js +0 -4917
- data/lib/crazy_ivan/templates/javascript/scriptaculous.js +0 -68
- data/lib/crazy_ivan/templates/javascript/slider.js +0 -275
data/README.rdoc
CHANGED
@@ -52,11 +52,10 @@ Crazy Ivan (CI) is simplest possible continuous integration tool.
|
|
52
52
|
to execute at the /:
|
53
53
|
|
54
54
|
/shopify
|
55
|
-
/.ci
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
conclusion
|
55
|
+
/.ci/update
|
56
|
+
version
|
57
|
+
test
|
58
|
+
conclusion
|
60
59
|
|
61
60
|
* crazy_ivan first executes `update` and captures the output:
|
62
61
|
|
data/bin/crazy_ivan
CHANGED
@@ -16,9 +16,10 @@ require "logger"
|
|
16
16
|
Syslog.open('crazy_ivan', Syslog::LOG_PID | Syslog::LOG_CONS)
|
17
17
|
|
18
18
|
Signal.trap("INT") do
|
19
|
-
Syslog.debug("Interrupted - Now dropping a note in the test output and exiting.")
|
19
|
+
# Syslog.debug("Interrupted - Now dropping a note in the test output and exiting.")
|
20
|
+
Syslog.debug("Interrupted - exiting.")
|
20
21
|
CrazyIvan.interrupt_test
|
21
|
-
|
22
|
+
ProcessManager.unlock
|
22
23
|
puts
|
23
24
|
exit
|
24
25
|
end
|
@@ -52,35 +53,6 @@ end
|
|
52
53
|
|
53
54
|
options = {}
|
54
55
|
|
55
|
-
|
56
|
-
class CrazyIvan::ProcessManager
|
57
|
-
PidFile = '/tmp/crazy_ivan.pid'
|
58
|
-
|
59
|
-
def self.acquire_lock
|
60
|
-
lock_exclusively!
|
61
|
-
yield
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.unlock
|
65
|
-
File.new(PidFile).flock(File::LOCK_UN)
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.ci_already_running?
|
69
|
-
File.exists?(PidFile) && !File.new(PidFile).flock(File::LOCK_EX | File::LOCK_NB)
|
70
|
-
end
|
71
|
-
|
72
|
-
def self.lock_exclusively!
|
73
|
-
pid = Integer(File.read(PidFile)) if File.exists?(PidFile)
|
74
|
-
File.open('/tmp/crazy_ivan.pid', "w+") { |fp| fp << Process.pid }
|
75
|
-
|
76
|
-
if ci_already_running?
|
77
|
-
Process.kill("INT", pid)
|
78
|
-
Syslog.debug("Detected another running CI process #{pid}; interrupting it and starting myself")
|
79
|
-
File.new(PidFile).flock(File::LOCK_EX)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
56
|
ARGV.options do |opts|
|
85
57
|
opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} test_reports_path"
|
86
58
|
|
@@ -102,7 +74,8 @@ ARGV.options do |opts|
|
|
102
74
|
when /setup/
|
103
75
|
CrazyIvan::setup
|
104
76
|
when /\w+/ # a directory for test results
|
105
|
-
|
77
|
+
ProcessManager.acquire_lock do
|
78
|
+
Syslog.debug "Generating reports in #{ARGV[0]}"
|
106
79
|
CrazyIvan::generate_test_reports_in(ARGV[0])
|
107
80
|
end
|
108
81
|
else
|
data/bin/test_report2campfire
CHANGED
@@ -93,4 +93,9 @@ Campfire.basic_auth ARGV[1], 'x'
|
|
93
93
|
|
94
94
|
campfire_room_id = campfire_url.path[/\d+/]
|
95
95
|
campfire_room = Campfire.room(campfire_room_id)
|
96
|
-
|
96
|
+
|
97
|
+
if report['test']['exit_status'].nil?
|
98
|
+
campfire_room.message "#{report['project_name']} #{report['version']['output']} built happily: #{ARGV[2]}"
|
99
|
+
else
|
100
|
+
campfire_room.message "#{report['project_name']} #{report['version']['output']} broke. Please take a look at #{ARGV[2]}"
|
101
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class ProcessManager
|
2
|
+
@@pidfile = '/tmp/crazy_ivan.pid'
|
3
|
+
|
4
|
+
def self.pidfile=(file)
|
5
|
+
@@pidfile = file
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.acquire_lock
|
9
|
+
lock_exclusively!
|
10
|
+
yield
|
11
|
+
unlock
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.unlock
|
15
|
+
File.new(@@pidfile).flock(File::LOCK_UN)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.ci_already_running?
|
19
|
+
File.exists?(@@pidfile) && !File.new(@@pidfile).flock(File::LOCK_EX | File::LOCK_NB)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.lock_exclusively!(options = {})
|
23
|
+
pid = Integer(File.read(@@pidfile)) if File.exists?(@@pidfile)
|
24
|
+
|
25
|
+
Syslog.debug "Acquiring lock"
|
26
|
+
|
27
|
+
if options[:interrupt_existing_process]
|
28
|
+
File.open(@@pidfile, "w+") { |fp| fp << Process.pid }
|
29
|
+
|
30
|
+
if ci_already_running?
|
31
|
+
Process.kill("INT", pid)
|
32
|
+
Syslog.debug("Detected another running CI process #{pid}; interrupting it and starting myself")
|
33
|
+
File.new(@@pidfile).flock(File::LOCK_EX)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
if ci_already_running?
|
37
|
+
msg = "Detected another running CI process #{pid} - terminating myself"
|
38
|
+
Syslog.warning msg
|
39
|
+
puts msg
|
40
|
+
Process.kill("INT", 0)
|
41
|
+
else
|
42
|
+
Syslog.debug("Locked CI process pid file")
|
43
|
+
Syslog.debug("Writing to pid file with #{Process.pid}")
|
44
|
+
File.open(@@pidfile, "w+") { |fp| fp << Process.pid }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -7,14 +7,23 @@ class ReportAssembler
|
|
7
7
|
def initialize(projects_directory, output_directory)
|
8
8
|
@runners = []
|
9
9
|
@projects_directory = projects_directory
|
10
|
-
@output_directory = output_directory
|
10
|
+
@output_directory = File.expand_path(output_directory, projects_directory)
|
11
|
+
end
|
12
|
+
|
13
|
+
def different_than_last_version?(runner)
|
14
|
+
project_path = File.join(@output_directory, runner.project_name)
|
15
|
+
|
16
|
+
Dir.chdir(project_path) do
|
17
|
+
version = runner.results[:version][:output]
|
18
|
+
Dir["#{version}.json"].size == 0
|
19
|
+
end
|
11
20
|
end
|
12
21
|
|
13
22
|
def generate
|
14
23
|
Dir.chdir(@projects_directory) do
|
15
24
|
Dir['*'].each do |dir|
|
16
25
|
if File.directory?(dir)
|
17
|
-
runners << TestRunner.new(File.join(@projects_directory, dir))
|
26
|
+
runners << TestRunner.new(File.join(@projects_directory, dir), self)
|
18
27
|
end
|
19
28
|
end
|
20
29
|
end
|
@@ -37,10 +46,15 @@ class ReportAssembler
|
|
37
46
|
# Update the report in currently_building.json with the version output and error
|
38
47
|
runner.version!
|
39
48
|
update_project(runner)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
|
50
|
+
if different_than_last_version?(runner)
|
51
|
+
# Empty the currently_building.json and add to recents.json this new report with the test output and error
|
52
|
+
runner.test! # update_project will be called from within the runner to stream the test output
|
53
|
+
update_project(runner)
|
54
|
+
else
|
55
|
+
flush_build_progress(runner)
|
56
|
+
Syslog.debug("Already tested #{runner.project_name} version #{runner.results[:version][:output]} - skipping test")
|
57
|
+
end
|
44
58
|
end
|
45
59
|
end
|
46
60
|
end
|
@@ -48,10 +62,6 @@ class ReportAssembler
|
|
48
62
|
def filename_from_version(string)
|
49
63
|
s = string[0..240]
|
50
64
|
|
51
|
-
if Dir["#{s}*.json"].size > 0
|
52
|
-
s += "-#{Dir["#{s}*.json"].size}"
|
53
|
-
end
|
54
|
-
|
55
65
|
return s
|
56
66
|
end
|
57
67
|
|
@@ -65,16 +75,21 @@ class ReportAssembler
|
|
65
75
|
return filtered_results
|
66
76
|
end
|
67
77
|
|
68
|
-
def flush_build_progress
|
69
|
-
File.
|
70
|
-
|
78
|
+
def flush_build_progress(runner)
|
79
|
+
project_results_path = File.join(@output_directory, runner.project_name)
|
80
|
+
|
81
|
+
Dir.chdir(project_results_path) do
|
82
|
+
File.open("currently_building.json", 'w+') do |f|
|
83
|
+
f.puts({}.to_json)
|
84
|
+
end
|
71
85
|
end
|
72
86
|
end
|
73
87
|
|
74
88
|
def update_project(runner)
|
75
|
-
|
76
|
-
|
77
|
-
|
89
|
+
project_path = File.expand_path(runner.project_name, @output_directory)
|
90
|
+
FileUtils.mkdir_p(project_path)
|
91
|
+
|
92
|
+
Dir.chdir(project_path) do
|
78
93
|
filename = ''
|
79
94
|
|
80
95
|
if runner.still_building?
|
@@ -93,7 +108,7 @@ class ReportAssembler
|
|
93
108
|
|
94
109
|
if runner.finished?
|
95
110
|
Syslog.debug "Runner is FINISHED"
|
96
|
-
flush_build_progress
|
111
|
+
flush_build_progress(runner)
|
97
112
|
update_recent(runner.results, filename)
|
98
113
|
end
|
99
114
|
end
|
@@ -125,10 +140,8 @@ class ReportAssembler
|
|
125
140
|
end
|
126
141
|
|
127
142
|
def update_index
|
128
|
-
|
129
|
-
|
130
|
-
File.
|
131
|
-
f.print index_template
|
132
|
-
end
|
143
|
+
FileUtils.cp(File.expand_path("index.html", TEMPLATES_PATH), 'index.html')
|
144
|
+
FileUtils.mkdir_p('javascript')
|
145
|
+
FileUtils.cp(File.expand_path("date.js", File.join(TEMPLATES_PATH, 'javascript')), 'javascript/date.js')
|
133
146
|
end
|
134
147
|
end
|
@@ -1,183 +1,229 @@
|
|
1
|
-
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
2
|
-
"http://www.w3.org/TR/html4/loose.dtd">
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
3
2
|
<html>
|
4
3
|
<head>
|
5
4
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
6
5
|
<title>Crazy Ivan: CI straight up.</title>
|
7
6
|
|
8
|
-
<
|
9
|
-
<script type="text/javascript" src="javascript/
|
10
|
-
<script type="text/javascript" src="javascript/scriptaculous.js"></script>
|
11
|
-
<script type="text/javascript" src="javascript/date.js"></script>
|
12
|
-
<script type="text/javascript" src="javascript/json-template.js"></script>
|
7
|
+
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
|
8
|
+
<script type="text/javascript" charset="utf-8" src="javascript/date.js"></script>
|
13
9
|
<script type="text/javascript">
|
14
|
-
|
15
|
-
|
10
|
+
// Simple JavaScript Templating
|
11
|
+
// John Resig - http://ejohn.org/ - MIT Licensed
|
12
|
+
(function(){
|
13
|
+
var cache = {};
|
14
|
+
|
15
|
+
this.tmpl = function tmpl(str, data){
|
16
|
+
// Figure out if we're getting a template, or if we need to
|
17
|
+
// load the template - and be sure to cache the result.
|
18
|
+
var fn = !/\W/.test(str) ?
|
19
|
+
cache[str] = cache[str] ||
|
20
|
+
tmpl(document.getElementById(str).innerHTML) :
|
21
|
+
|
22
|
+
// Generate a reusable function that will serve as a template
|
23
|
+
// generator (and which will be cached).
|
24
|
+
new Function("obj",
|
25
|
+
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
26
|
+
|
27
|
+
// Introduce the data as local variables using with(){}
|
28
|
+
"with(obj){p.push('" +
|
29
|
+
|
30
|
+
// Convert the template into pure JavaScript
|
31
|
+
str
|
32
|
+
.replace(/[\r\t\n]/g, " ")
|
33
|
+
.split("<%").join("\t")
|
34
|
+
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
35
|
+
.replace(/\t=(.*?)%>/g, "',$1,'")
|
36
|
+
.split("\t").join("');")
|
37
|
+
.split("%>").join("p.push('")
|
38
|
+
.split("\r").join("\\'")
|
39
|
+
+ "');}return p.join('');");
|
40
|
+
|
41
|
+
// Provide some basic currying to the user
|
42
|
+
return data ? fn( data ) : fn;
|
43
|
+
};
|
44
|
+
})();
|
45
|
+
</script>
|
46
|
+
<style type="text/css" media="screen">
|
47
|
+
body {
|
48
|
+
margin: 2.5em 3em;
|
49
|
+
padding: 0;
|
50
|
+
background: #fff;
|
51
|
+
color: #333;
|
52
|
+
font: 100%/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
16
53
|
}
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
54
|
+
|
55
|
+
h1 { margin: 0;}
|
56
|
+
|
57
|
+
pre { margin: 0;}
|
58
|
+
|
59
|
+
.error {
|
60
|
+
color: red;
|
61
|
+
}
|
62
|
+
|
63
|
+
.project h2 { margin: 12px 0 0 0;}
|
64
|
+
|
65
|
+
.tests { margin: 0 0 0 18px;}
|
66
|
+
.tests .test { font-size: 80%; margin-right: 8px}
|
67
|
+
.tests a.test:hover { text-decoration: underline;}
|
68
|
+
.tests .test:first-child { font-size: 100%;}
|
69
|
+
.tests .test.active { font-weight: bold;}
|
70
|
+
|
71
|
+
.result .timestamp { margin-right: 12px;}
|
72
|
+
.result .version { margin: 6px 0 6px 12px }
|
73
|
+
.result .output { padding: 5px; color: silver; background: black; margin: 12px 18px 8px 18px; overflow: auto }
|
74
|
+
.result .output .update { margin: 6px 0 6px 12px }
|
75
|
+
.result .output .test { margin: 6px 0 6px 12px}
|
76
|
+
|
77
|
+
.footer {
|
78
|
+
margin: 24px 0 0 0;
|
79
|
+
font-size: 60%;
|
80
|
+
width: 100%;
|
81
|
+
text-align: center;
|
82
|
+
}
|
83
|
+
</style>
|
84
|
+
</head>
|
85
|
+
<body>
|
86
|
+
<h1>Projects</h1>
|
87
|
+
<div id="projects"></div>
|
88
|
+
|
89
|
+
<div class="footer">
|
90
|
+
<a href="http://github.com/edward/crazy_ivan">Crazy Ivan on Github</a>
|
91
|
+
</div>
|
92
|
+
|
93
|
+
<!-- templates -->
|
94
|
+
|
95
|
+
<!-- project template -->
|
96
|
+
<script type="text/html" id="projectTemplate">
|
97
|
+
<div id="<%= projectId %>" class="project">
|
98
|
+
<h2><%= projectName %></h2>
|
21
99
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
100
|
+
<div class="tests"></div>
|
101
|
+
<div class="results"></div>
|
102
|
+
<div>
|
103
|
+
</script>
|
104
|
+
|
105
|
+
<!-- test link template -->
|
106
|
+
<script type="text/html" id="resultLinkTemplate">
|
107
|
+
<a id="<%= projectDomId %>-<%= version.output %>" class="test"><%= shortTimeStamp %></a>
|
108
|
+
</script>
|
109
|
+
|
110
|
+
<!-- build result holder -->
|
111
|
+
<script type="text/html" id="resultTemplate">
|
112
|
+
<div class="result <%= projectDomId %>-<%= version.output %>" style="display: none">
|
113
|
+
<div>
|
114
|
+
<span class="timestamp"><%= timestamp.finish %></span>
|
115
|
+
<span class="version"><%= version.output %></span>
|
116
|
+
</div>
|
28
117
|
|
29
|
-
|
30
|
-
|
118
|
+
<div class="output">
|
119
|
+
<div class="version" style="display: none"><pre class="error"><%= version.error %></pre></div>
|
31
120
|
|
32
|
-
|
33
|
-
|
34
|
-
|
121
|
+
<div class="update" style="display: none">
|
122
|
+
<pre><%= update.output %></pre>
|
123
|
+
<pre class="error"><%= update.error %></pre>
|
124
|
+
</div>
|
35
125
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
126
|
+
<div class="test">
|
127
|
+
<pre><%= test.output.replace(/\</g, "<").replace(/\>/g, ">") %></pre>
|
128
|
+
<pre class="error"><%= test.error %></pre>
|
129
|
+
</div>
|
130
|
+
</div>
|
131
|
+
</div>
|
132
|
+
</script>
|
133
|
+
|
134
|
+
<script type="text/javascript" charset="utf-8">
|
135
|
+
var json = {projects: []};
|
42
136
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
recent_versions = transport.responseText.evalJSON().recent_versions;
|
49
|
-
}
|
137
|
+
jQuery(document).ready(function($) {
|
138
|
+
$.getJSON("projects.json", function(data) {
|
139
|
+
jQuery.each(data.projects, function(i, projectName) {
|
140
|
+
addProjectToJson(projectName);
|
141
|
+
});
|
50
142
|
});
|
51
|
-
|
52
|
-
}
|
143
|
+
});
|
53
144
|
|
54
|
-
|
55
|
-
var
|
56
|
-
|
145
|
+
function addProjectToJson(name) {
|
146
|
+
var project = {'name': name, reports: []};
|
147
|
+
var recentVersionsJsonPath = name + "/recent.json";
|
148
|
+
|
149
|
+
jQuery.getJSON(recentVersionsJsonPath, function(data) {
|
150
|
+
jQuery.each(data.recent_versions, function(i, version) {
|
151
|
+
addReportToProject(project, version);
|
152
|
+
});
|
153
|
+
});
|
57
154
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
155
|
+
json.projects.push(project);
|
156
|
+
}
|
157
|
+
|
158
|
+
function addReportToProject(project, version) {
|
159
|
+
var name = project.name;
|
160
|
+
var resultJsonPath = name + "/" + version + ".json";
|
161
|
+
jQuery.getJSON(resultJsonPath, function(data) {
|
162
|
+
project.reports.push(data);
|
163
|
+
trigger_render();
|
63
164
|
});
|
64
|
-
return report;
|
65
165
|
}
|
66
166
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
}
|
167
|
+
function sortReports(reports) {
|
168
|
+
return reports.sort(function(report_a, report_b) {
|
169
|
+
// Not sure why providing a 3-letter day trips up Date.js sometimes
|
170
|
+
a = Date.parse(report_a.timestamp.finish.substring(4));
|
171
|
+
b = Date.parse(report_b.timestamp.finish.substring(4));
|
172
|
+
|
173
|
+
return Date.compare(a, b);
|
75
174
|
});
|
76
|
-
return build_in_progress;
|
77
175
|
}
|
78
176
|
|
79
|
-
var
|
80
|
-
|
81
|
-
|
177
|
+
var timeout = null;
|
178
|
+
function trigger_render() {
|
179
|
+
if (timeout) { clearTimeout(timeout) }
|
180
|
+
timeout = setTimeout(render, 50);
|
82
181
|
}
|
83
182
|
|
84
|
-
var render = function(
|
85
|
-
|
86
|
-
<h1>Projects</h1> \
|
87
|
-
<div class='projects'> \
|
88
|
-
{.section projects} \
|
89
|
-
{.repeated section @} \
|
90
|
-
<div class='project'> \
|
91
|
-
<h2>{name}</h2> \
|
92
|
-
{.section build_in_progress} \
|
93
|
-
{.section timestamp} \
|
94
|
-
<div>[build in progress – started at {start}]</div> \
|
95
|
-
{.end} \
|
96
|
-
{.end} \
|
97
|
-
<div class='tests'> \
|
98
|
-
{.section reports} \
|
99
|
-
{.repeated section @} \
|
100
|
-
<a class='test {.section test} {.section exit_status} error {.end} {.end} {.section update} {.section exit_status} error {.end} {.end}' href='#'>{timestamp.finish}</a> \
|
101
|
-
{.end} \
|
102
|
-
{.end} \
|
103
|
-
</div> \
|
104
|
-
<div class='results'> \
|
105
|
-
{.section reports} \
|
106
|
-
{.repeated section @} \
|
107
|
-
<div class='result'> \
|
108
|
-
<div> \
|
109
|
-
<span class='timestamp'>{timestamp.finish}</span> \
|
110
|
-
<span class='version'>{version.output}</span> \
|
111
|
-
</div> \
|
112
|
-
\
|
113
|
-
<div class='output'> \
|
114
|
-
{.section version} \
|
115
|
-
{.section exit_status} \
|
116
|
-
<div class='version'> \
|
117
|
-
<pre class='error'>{error}</pre> \
|
118
|
-
</div> \
|
119
|
-
{.end} \
|
120
|
-
{.end} \
|
121
|
-
\
|
122
|
-
<div class='update'> \
|
123
|
-
<pre>{update.output}</pre> \
|
124
|
-
{.section update} \
|
125
|
-
{.section exit_status} \
|
126
|
-
<pre class='error'>{update.error}</pre> \
|
127
|
-
{.end} \
|
128
|
-
{.end} \
|
129
|
-
</div> \
|
130
|
-
\
|
131
|
-
<div class='test'> \
|
132
|
-
<pre>{test.output}</pre> \
|
133
|
-
{.section test} \
|
134
|
-
{.section exit_status} \
|
135
|
-
<pre class='error'>{test.error}</pre> \
|
136
|
-
{.end} \
|
137
|
-
{.end} \
|
138
|
-
</div> \
|
139
|
-
</div> \
|
140
|
-
</div> \
|
141
|
-
{.end} \
|
142
|
-
{.or} \
|
143
|
-
<p>No test reports found. Please run crazy_ivan.</p> \
|
144
|
-
{.end} \
|
145
|
-
</div> \
|
146
|
-
</div> \
|
147
|
-
{.end} \
|
148
|
-
{.or} \
|
149
|
-
<p>No projects found.</p> \
|
150
|
-
{.end} \
|
151
|
-
</div> \
|
152
|
-
");
|
183
|
+
var render = function() {
|
184
|
+
$('#projects').empty();
|
153
185
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
186
|
+
jQuery.each(json.projects, function(i, project) {
|
187
|
+
var name = project.name;
|
188
|
+
var domId = name.replace(/\./g, ""); // remove . from id name
|
189
|
+
|
190
|
+
// create project holder div
|
191
|
+
$('#projects').append(tmpl("projectTemplate", {projectName: name, projectId: domId}));
|
192
|
+
|
193
|
+
project.reports = sortReports(project.reports);
|
194
|
+
|
195
|
+
jQuery.each(project.reports, function(i, report) {
|
196
|
+
var version = report.version.output;
|
197
|
+
var resultJsonPath = name + "/" + version + ".json";
|
198
|
+
var domId = name.replace(/\./g, ""); // remove . from id name
|
199
|
+
|
200
|
+
// Not sure why providing a 3-letter day trips up Date.js sometimes
|
201
|
+
report["shortTimeStamp"] = Date.parse(report.timestamp.finish.substring(4)).toString("HH:mm");
|
202
|
+
report["projectDomId"] = domId;
|
203
|
+
|
204
|
+
$("#" + domId + " .results").append(tmpl("resultTemplate", report));
|
205
|
+
$("#" + domId + " .tests").prepend(tmpl("resultLinkTemplate", report));
|
206
|
+
|
207
|
+
// add failed/success indication to link - inlining in the template screws up
|
208
|
+
if (report.test.exit_status) {
|
209
|
+
$("#" + domId + '-' + version).addClass('error');
|
210
|
+
}
|
169
211
|
});
|
170
212
|
});
|
171
|
-
|
172
|
-
$$('.results').invoke('hide')
|
173
213
|
}
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
214
|
+
|
215
|
+
// listen to clicking of test result links
|
216
|
+
$('#projects .project .tests a.test').live('click', function(e) {
|
217
|
+
$('.result:visible').hide();
|
218
|
+
|
219
|
+
if($(e.target).hasClass('active')) {
|
220
|
+
$('.test').removeClass('active');
|
221
|
+
} else {
|
222
|
+
$('.test').removeClass('active');
|
223
|
+
$('.result.' + e.target.id).show();
|
224
|
+
$(e.target).addClass('active');
|
225
|
+
}
|
226
|
+
});
|
227
|
+
</script>
|
182
228
|
</body>
|
183
229
|
</html>
|