houston-core 0.5.4 → 0.5.5
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 +4 -4
- data/Gemfile.lock +20 -22
- data/README.md +1 -1
- data/app/adapters/houston/adapters/version_control/git_adapter/repo.rb +6 -3
- data/app/assets/javascripts/app/boot.coffee +9 -0
- data/app/assets/javascripts/app/infinite_scroll.coffee +6 -3
- data/app/assets/javascripts/app/models/ticket.coffee +1 -1
- data/app/assets/javascripts/core/app.coffee +4 -1
- data/app/assets/javascripts/core/core_ext/array.coffee +11 -0
- data/app/assets/javascripts/core/core_ext/date.coffee +8 -0
- data/app/assets/javascripts/core/handlebars_helpers.coffee +12 -8
- data/app/assets/javascripts/vendor.js +2 -2
- data/app/assets/stylesheets/application/mobile.scss +96 -0
- data/app/assets/stylesheets/application/test.scss +58 -0
- data/app/assets/stylesheets/application/test_run.scss +14 -5
- data/app/assets/stylesheets/application/timeline.scss +2 -4
- data/app/concerns/commit_synchronizer.rb +38 -2
- data/app/controllers/application_controller.rb +3 -0
- data/app/controllers/hooks_controller.rb +18 -0
- data/app/controllers/project_tests_controller.rb +46 -0
- data/app/helpers/commit_helper.rb +7 -0
- data/app/helpers/test_run_helper.rb +16 -0
- data/app/models/commit.rb +4 -0
- data/app/models/github/pull_request.rb +7 -7
- data/app/models/milestone.rb +1 -1
- data/app/models/run_tests_on_post_receive.rb +2 -0
- data/app/models/test.rb +4 -0
- data/app/models/test_result.rb +1 -1
- data/app/models/test_run.rb +25 -2
- data/app/views/layouts/_mobile_navigation.html.erb +100 -0
- data/app/views/layouts/application.html.erb +20 -10
- data/app/views/layouts/dashboard.html.erb +1 -1
- data/app/views/layouts/minimal.html.erb +1 -1
- data/app/views/layouts/naked_dashboard.html.erb +1 -1
- data/app/views/project_notification/test_run.html.erb +97 -120
- data/app/views/project_tests/_commits.html.erb +14 -0
- data/app/views/project_tests/index.html.erb +39 -0
- data/app/views/projects/_form.html.erb +6 -2
- data/config/application.rb +1 -2
- data/config/routes.rb +2 -0
- data/db/migrate/20151108221505_convert_pull_request_labels_to_array.rb +22 -0
- data/db/migrate/20151108223154_sync_body_also_for_pull_requests.rb +5 -0
- data/db/migrate/20151108233510_add_props_to_pull_requests.rb +5 -0
- data/db/structure.sql +10 -1
- data/houston.gemspec +4 -5
- data/lib/houston/version.rb +1 -1
- data/test/integration/web_hook_test.rb +7 -1
- data/test/unit/concerns/commit_synchronizer_test.rb +13 -0
- data/test/unit/models/pull_request_test.rb +17 -0
- data/vendor/assets/javascripts/showdown.js +2489 -0
- data/vendor/assets/javascripts/slideout.js +493 -0
- metadata +25 -29
- data/lib/tasks/config.rake +0 -255
- data/vendor/assets/javascripts/Markdown.Converter.js +0 -1412
@@ -44,6 +44,7 @@ h2.test-result-banner {
|
|
44
44
|
&.pass { @include badge-style(#5DB64C); letter-spacing: 0.088em; } // grass
|
45
45
|
&.error { @include badge-style(#DDC522); letter-spacing: 0.088em; } // mustard
|
46
46
|
&.aborted { @include badge-style(#888888); letter-spacing: 0.088em; } // gray
|
47
|
+
&.pending { @include badge-style(#888888); letter-spacing: 0.088em; } // gray
|
47
48
|
&.fail { @include badge-style(#E24E32); letter-spacing: 0.160em; } // tomato
|
48
49
|
|
49
50
|
a {
|
@@ -75,12 +76,20 @@ h2.test-result-banner {
|
|
75
76
|
.test {
|
76
77
|
display: block;
|
77
78
|
margin: 0;
|
78
|
-
padding: 0.18em 0 0.18em 5.25em;
|
79
|
-
text-indent: -4em;
|
80
79
|
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
& > a {
|
81
|
+
display: block;
|
82
|
+
padding: 0.18em 0 0.18em 5.25em;
|
83
|
+
text-indent: -4em;
|
84
|
+
|
85
|
+
color: inherit;
|
86
|
+
text-decoration: none !important;
|
87
|
+
|
88
|
+
&:hover { background-color: #edf9ff; }
|
89
|
+
}
|
90
|
+
|
91
|
+
&.fail > a { background-color: rgba(226, 78, 50, 0.15); }
|
92
|
+
&.fail > a:hover { background-color: rgba(226, 78, 50, 0.33); }
|
84
93
|
}
|
85
94
|
|
86
95
|
.test-suite-name {
|
@@ -39,11 +39,11 @@ $lightGray: #eee;
|
|
39
39
|
|
40
40
|
.timeline-event {
|
41
41
|
margin-left: 50px;
|
42
|
-
padding: 4px 4px 4px
|
42
|
+
padding: 4px 4px 4px 70px;
|
43
43
|
border-left: solid 2px $lightGray;
|
44
44
|
position: relative;
|
45
45
|
font-size: 0.92em;
|
46
|
-
text-indent: -
|
46
|
+
text-indent: -57px;
|
47
47
|
line-height: 1.25em;
|
48
48
|
|
49
49
|
.timeline-event-time {
|
@@ -131,8 +131,6 @@ $lightGray: #eee;
|
|
131
131
|
&.timeline-event-ticket-created,
|
132
132
|
&.timeline-event-ticket-closed {
|
133
133
|
padding-right: 100px;
|
134
|
-
text-indent: -57px;
|
135
|
-
padding-left: 70px;
|
136
134
|
}
|
137
135
|
|
138
136
|
&.timeline-event-release {
|
@@ -8,7 +8,12 @@ module CommitSynchronizer
|
|
8
8
|
expected_commits = repo.all_commits
|
9
9
|
|
10
10
|
create_missing_commits! expected_commits - existing_commits
|
11
|
-
|
11
|
+
|
12
|
+
reachable_commits = project.commits.reachable.pluck(:sha)
|
13
|
+
flag_unreachable_commits! reachable_commits - expected_commits
|
14
|
+
|
15
|
+
unreachable_commits = project.commits.unreachable.pluck(:sha)
|
16
|
+
flag_reachable_commits! unreachable_commits & expected_commits
|
12
17
|
end
|
13
18
|
end
|
14
19
|
|
@@ -29,7 +34,13 @@ module CommitSynchronizer
|
|
29
34
|
end
|
30
35
|
|
31
36
|
|
32
|
-
def synchronize(native_commits)
|
37
|
+
def synchronize(native_commits=[])
|
38
|
+
if block_given?
|
39
|
+
native_commits = Houston.benchmark("[commits.synchonize] reading commits") do
|
40
|
+
yield repo
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
33
44
|
native_commits = native_commits.reject(&:nil?)
|
34
45
|
return [] if native_commits.empty?
|
35
46
|
|
@@ -100,6 +111,31 @@ private
|
|
100
111
|
Houston.report_exception $!
|
101
112
|
end
|
102
113
|
|
114
|
+
def flag_reachable_commits!(reachable_commits)
|
115
|
+
return if reachable_commits.none?
|
116
|
+
|
117
|
+
# Inserting this to help troubleshoot a scenario where PG::TRDeadlockDetected
|
118
|
+
# is raised from this method. This is a recoverable scenario, so we report
|
119
|
+
# the exception (with additional context) but do not re-raise it.
|
120
|
+
query = <<-SQL
|
121
|
+
SELECT query, state, waiting, pid
|
122
|
+
FROM pg_stat_activity
|
123
|
+
WHERE state <> 'idle' AND waiting='t'
|
124
|
+
SQL
|
125
|
+
waiting_queries = connection.select_all(query).to_hash
|
126
|
+
.reject { |result| result["query"] == query }
|
127
|
+
|
128
|
+
project.commits.where(sha: reachable_commits).update_all(unreachable: false)
|
129
|
+
|
130
|
+
Rails.logger.info "[commits:sync] #{reachable_commits.length} reachable commits for #{project.name}"
|
131
|
+
|
132
|
+
rescue exceptions_wrapping(PG::TRDeadlockDetected)
|
133
|
+
$!.additional_information["project"] = project.slug
|
134
|
+
$!.additional_information["reachable_commits"] = reachable_commits.join("\n")
|
135
|
+
$!.additional_information["waiting_queries"] = MultiJson.dump(waiting_queries)
|
136
|
+
Houston.report_exception $!
|
137
|
+
end
|
138
|
+
|
103
139
|
def repo
|
104
140
|
project.repo
|
105
141
|
end
|
@@ -28,4 +28,22 @@ class HooksController < ApplicationController
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
def trigger
|
32
|
+
event = "hooks:#{params[:hook]}"
|
33
|
+
unless Houston.observer.observed?(event)
|
34
|
+
render text: "A hook with the slug '#{params[:hook]}' is not defined", status: 404
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
38
|
+
payload = params.except(:action, :controller).merge({
|
39
|
+
sender: {
|
40
|
+
ip: request.remote_ip,
|
41
|
+
agent: request.user_agent
|
42
|
+
}
|
43
|
+
})
|
44
|
+
|
45
|
+
Houston.observer.fire event, payload
|
46
|
+
head 200
|
47
|
+
end
|
48
|
+
|
31
49
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class ProjectTestsController < ApplicationController
|
2
|
+
|
3
|
+
def index
|
4
|
+
@project = Project.find_by_slug! params[:slug]
|
5
|
+
@test = @project.tests.find params[:id]
|
6
|
+
@totals = Hash[@test.test_results.group(:status).pluck(:status, "COUNT(*)")]
|
7
|
+
|
8
|
+
begin
|
9
|
+
head = params.fetch :at, @project.repo.branch("master")
|
10
|
+
stop_shas = @test.introduced_in_shas
|
11
|
+
@commits = Houston.benchmark("[project_tests#index] fetch commits") {
|
12
|
+
@project.repo.ancestors(head, including_self: true, limit: 100, hide: stop_shas) }
|
13
|
+
|
14
|
+
if @commits.any?
|
15
|
+
@runs = @project.test_runs.where(sha: @commits.map(&:sha))
|
16
|
+
|
17
|
+
@commits.each do |commit|
|
18
|
+
def commit.date
|
19
|
+
@date ||= committed_at.to_date
|
20
|
+
end
|
21
|
+
def commit.time
|
22
|
+
committed_at
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@results = @test.test_results.where(test_run_id: @runs.map(&:id))
|
27
|
+
.joins(:test_run)
|
28
|
+
.select("test_runs.sha", :*)
|
29
|
+
.index_by { |result| result[:sha] }
|
30
|
+
@runs = @runs.index_by(&:sha)
|
31
|
+
end
|
32
|
+
rescue Houston::Adapters::VersionControl::CommitNotFound
|
33
|
+
@commits = []
|
34
|
+
@exception = $!
|
35
|
+
end
|
36
|
+
|
37
|
+
if request.xhr?
|
38
|
+
if @commits.any?
|
39
|
+
render partial: "project_tests/commits"
|
40
|
+
else
|
41
|
+
head 204
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -61,4 +61,11 @@ module CommitHelper
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
def commit_test_message(commit)
|
65
|
+
message = commit.message[/^.*$/]
|
66
|
+
return message unless @project
|
67
|
+
return message unless @project.repo.respond_to? :commit_url
|
68
|
+
link_to message, @project.repo.commit_url(commit), target: "_blank"
|
69
|
+
end
|
70
|
+
|
64
71
|
end
|
@@ -33,4 +33,20 @@ module TestRunHelper
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
def commit_test_status(test_run, test_result)
|
37
|
+
status = test_result.status if test_result
|
38
|
+
status = "untested" if test_run.nil?
|
39
|
+
status = "pending" if test_run && test_run.pending?
|
40
|
+
status = "aborted" if test_run && test_run.aborted?
|
41
|
+
status ||= "unknown"
|
42
|
+
|
43
|
+
css = "project-test-status project-test-status-#{status}"
|
44
|
+
|
45
|
+
if test_run
|
46
|
+
link_to status, test_run_url(slug: test_run.project.slug, commit: test_run.sha), class: css
|
47
|
+
else
|
48
|
+
"<span class=\"#{css}\">#{status}</span>".html_safe
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
36
52
|
end
|
data/app/models/commit.rb
CHANGED
@@ -17,8 +17,7 @@ module Github
|
|
17
17
|
after_create { Houston.observer.fire "github:pull:opened", self }
|
18
18
|
after_update { Houston.observer.fire "github:pull:updated", self, changes }
|
19
19
|
|
20
|
-
validates :project_id, :title, :number, :repo, :url, :base_ref, :base_sha, :head_ref, :head_sha,
|
21
|
-
presence: true
|
20
|
+
validates :project_id, :title, :number, :repo, :url, :base_ref, :base_sha, :head_ref, :head_sha, :username, presence: true
|
22
21
|
validates :number, uniqueness: { scope: :project_id }
|
23
22
|
|
24
23
|
class << self
|
@@ -92,16 +91,16 @@ module Github
|
|
92
91
|
number: github_pr["number"])
|
93
92
|
.merge_attributes(github_pr)
|
94
93
|
end
|
95
|
-
end
|
96
94
|
|
95
|
+
def labeled(*labels)
|
96
|
+
where(["labels && ARRAY[?]", labels])
|
97
|
+
end
|
98
|
+
end
|
97
99
|
|
98
100
|
|
99
|
-
def labels
|
100
|
-
super.split(/\n/)
|
101
|
-
end
|
102
101
|
|
103
102
|
def labels=(value)
|
104
|
-
super Array(value).uniq
|
103
|
+
super Array(value).uniq
|
105
104
|
end
|
106
105
|
|
107
106
|
def add_label!(label, options={})
|
@@ -131,6 +130,7 @@ module Github
|
|
131
130
|
end
|
132
131
|
|
133
132
|
self.title = pr["title"]
|
133
|
+
self.body = pr["body"]
|
134
134
|
self.head_sha = pr["head"]["sha"]
|
135
135
|
self.head_ref = pr["head"]["ref"]
|
136
136
|
self.labels = pr["labels"] if pr.key?("labels")
|
data/app/models/milestone.rb
CHANGED
@@ -4,7 +4,7 @@ class Milestone < ActiveRecord::Base
|
|
4
4
|
belongs_to :project
|
5
5
|
has_many :tickets, -> { reorder("NULLIF(tickets.extended_attributes->'milestoneSequence', '')::int") }
|
6
6
|
|
7
|
-
versioned only: [:name, :start_date, :end_date, :band, :lanes], class_name: "MilestoneVersion", initial_version: true
|
7
|
+
versioned only: [:name, :start_date, :end_date, :band, :lanes, :destroyed_at], class_name: "MilestoneVersion", initial_version: true
|
8
8
|
|
9
9
|
default_scope { where(destroyed_at: nil).order(:start_date) }
|
10
10
|
|
@@ -148,6 +148,8 @@ class RunTestsOnPostReceive
|
|
148
148
|
return if test_run.project.code_climate_repo_token.blank?
|
149
149
|
CodeClimate::CoverageReport.publish!(test_run)
|
150
150
|
test_run.project.feature_working! :publish_coverage_to_code_climate
|
151
|
+
rescue Houston::Adapters::VersionControl::CommitNotFound
|
152
|
+
# Got a bad Test Run, nothing we can do about it.
|
151
153
|
rescue Net::OpenTimeout, Net::ReadTimeout
|
152
154
|
test_run.project.feature_broken! :publish_coverage_to_code_climate
|
153
155
|
Rails.logger.warn "\e[31m[push:publish:codeclimate] #{$!.class}: #{$!.message}\e[0m"
|
data/app/models/test.rb
CHANGED
data/app/models/test_result.rb
CHANGED
data/app/models/test_run.rb
CHANGED
@@ -63,6 +63,24 @@ class TestRun < ActiveRecord::Base
|
|
63
63
|
AND test_runs.completed_at=most_recent_test_runs.completed_at
|
64
64
|
SQL
|
65
65
|
end
|
66
|
+
|
67
|
+
def rebuild_tests!(options={})
|
68
|
+
test_runs = where("tests is not null")
|
69
|
+
.where("id NOT IN (SELECT DISTINCT test_run_id FROM test_results)")
|
70
|
+
if options[:progress]
|
71
|
+
require "progressbar"
|
72
|
+
pbar = ProgressBar.new("test runs", test_runs.count)
|
73
|
+
end
|
74
|
+
test_runs.find_each do |test_run|
|
75
|
+
if test_run.read_attribute(:tests).nil?
|
76
|
+
test_run.update_column :tests, nil
|
77
|
+
else
|
78
|
+
test_run.save_tests_and_results
|
79
|
+
end
|
80
|
+
pbar.inc if options[:progress]
|
81
|
+
end
|
82
|
+
pbar.finish if options[:progress]
|
83
|
+
end
|
66
84
|
end
|
67
85
|
|
68
86
|
|
@@ -181,6 +199,10 @@ class TestRun < ActiveRecord::Base
|
|
181
199
|
completed_at.present?
|
182
200
|
end
|
183
201
|
|
202
|
+
def pending?
|
203
|
+
!completed?
|
204
|
+
end
|
205
|
+
|
184
206
|
def has_results?
|
185
207
|
result.present? and !aborted?
|
186
208
|
end
|
@@ -223,8 +245,9 @@ class TestRun < ActiveRecord::Base
|
|
223
245
|
def tests
|
224
246
|
@tests ||= test_results.includes(:error).joins(:test).select("test_results.*", "tests.suite", "tests.name").map do |test_result|
|
225
247
|
message, backtrace = test_result.error.output.split("\n\n") if test_result.error
|
226
|
-
{
|
227
|
-
|
248
|
+
{ test_id: test_result.test_id,
|
249
|
+
suite: test_result[:suite],
|
250
|
+
name: test_result[:name].to_s.gsub(/^(test :|: )/, ""),
|
228
251
|
status: test_result.status,
|
229
252
|
duration: test_result.duration,
|
230
253
|
error_message: message,
|
@@ -0,0 +1,100 @@
|
|
1
|
+
<div class="navbar navbar-fixed-top navbar-inverse">
|
2
|
+
<div class="navbar-inner">
|
3
|
+
<div class="container-fluid">
|
4
|
+
<%= link_to Houston.config.title, main_app.root_url, class: "brand" %>
|
5
|
+
|
6
|
+
<ul class="nav pull-right nav-inline">
|
7
|
+
<% if current_user -%>
|
8
|
+
<li class="current-user dropdown">
|
9
|
+
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><%= avatar_for(current_user, size: 30) %> <b class="caret"></b></a>
|
10
|
+
<ul class="dropdown-menu">
|
11
|
+
<li><%= link_to "Settings", main_app.edit_user_path(current_user) %></li>
|
12
|
+
<li><%= link_to "Sign out", main_app.destroy_user_session_path %></li>
|
13
|
+
</ul>
|
14
|
+
</li>
|
15
|
+
<% else -%>
|
16
|
+
<li><%= link_to "Sign in", main_app.new_user_session_path %></li>
|
17
|
+
<% end -%>
|
18
|
+
</ul>
|
19
|
+
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div id="slideout_menu" class="slideout-menu">
|
25
|
+
<div class="general-navbar">
|
26
|
+
<ul class="nav">
|
27
|
+
<% Houston.config.navigation.each do |navigation| %><%= render_navigation navigation %><% end %>
|
28
|
+
|
29
|
+
<li class="divider-horizontal"></li>
|
30
|
+
|
31
|
+
<% if can?(:read, Project) -%>
|
32
|
+
<%= render_nav_link "Projects", main_app.projects_path, icon: "fa-database" %>
|
33
|
+
<% end -%>
|
34
|
+
|
35
|
+
<% if can?(:read, User) -%>
|
36
|
+
<%= render_nav_link "Team", main_app.users_path, icon: "fa-user" %>
|
37
|
+
<% end -%>
|
38
|
+
|
39
|
+
<% if can?(:read, :job) -%>
|
40
|
+
<%= render_nav_link "Jobs", main_app.jobs_path, icon: "fa-user" %>
|
41
|
+
<% end -%>
|
42
|
+
|
43
|
+
</ul>
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<% if current_project && current_project.persisted? %>
|
47
|
+
<div class="project-navbar <%= current_project.color %>">
|
48
|
+
<ul class="nav nav-inline">
|
49
|
+
<% if current_user -%>
|
50
|
+
<li class="dropdown current-project <%= current_project && current_project.color %>">
|
51
|
+
<a href="#" title="Feedback" class="dropdown-toggle" data-toggle="dropdown">
|
52
|
+
<%= current_project ? current_project.name : "Select Project" %>
|
53
|
+
</a>
|
54
|
+
<ul class="dropdown-menu">
|
55
|
+
<% followed_projects.each do |project| %>
|
56
|
+
<% if project == current_project %>
|
57
|
+
<li class="current">
|
58
|
+
<b class="bubble <%= project.color %>"></b> <%= project.name %></a>
|
59
|
+
</li>
|
60
|
+
<% else %>
|
61
|
+
<li>
|
62
|
+
<% path = if !current_feature
|
63
|
+
# we're not on a project page,
|
64
|
+
# just refresh the page and set the project
|
65
|
+
"?project=#{project.slug}"
|
66
|
+
elsif !project.features.include?(current_feature)
|
67
|
+
# we're using a feature that this project
|
68
|
+
# doesn't support. Navigate to the root URL
|
69
|
+
# and set the project
|
70
|
+
main_app.root_path(project: project.slug)
|
71
|
+
else
|
72
|
+
feature_path(project, current_feature)
|
73
|
+
end %>
|
74
|
+
<%= link_to path do %>
|
75
|
+
<b class="bubble <%= project.color %>"></b> <%= project.name %></a>
|
76
|
+
<% end %>
|
77
|
+
</li>
|
78
|
+
<% end %>
|
79
|
+
<% end %>
|
80
|
+
</ul>
|
81
|
+
</li>
|
82
|
+
<% end %>
|
83
|
+
</ul>
|
84
|
+
<% if current_project.features.any? %>
|
85
|
+
<ul class="nav">
|
86
|
+
<% current_project.features.each do |feature| %>
|
87
|
+
<%= render_nav_for_feature(feature) %>
|
88
|
+
<% end %>
|
89
|
+
</ul>
|
90
|
+
<% else %>
|
91
|
+
<div class="project-no-features">
|
92
|
+
No features are enabled for <%= current_project.name %>.
|
93
|
+
<% if can?(:update, current_project) %>
|
94
|
+
You can enable features in <%= link_to "Project Settings", main_app.edit_project_path(current_project) %>.
|
95
|
+
<% end %>
|
96
|
+
</div>
|
97
|
+
<% end %>
|
98
|
+
</div>
|
99
|
+
<% end %>
|
100
|
+
</div>
|