crazy_ivan 1.1.1 → 1.2.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.
- 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>
|