houston-core 0.7.0.beta4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -2
- data/.travis.yml +50 -0
- data/Gemfile +1 -2
- data/Gemfile.lock +1 -7
- data/README.md +21 -9
- data/app/assets/stylesheets/houston/application/actions.scss +25 -4
- data/app/assets/stylesheets/houston/application/sprint.scss +1 -1
- data/app/assets/stylesheets/houston/application/test.scss +1 -1
- data/app/assets/stylesheets/houston/application/test_run.scss +2 -2
- data/app/assets/stylesheets/houston/application/timeline.scss +1 -1
- data/app/assets/stylesheets/houston/core/roboto.scss.erb +2 -2
- data/app/controllers/actions_controller.rb +13 -5
- data/app/controllers/errors_controller.rb +2 -2
- data/app/controllers/releases_controller.rb +1 -1
- data/app/controllers/triggers_controller.rb +1 -1
- data/app/helpers/actions_helper.rb +36 -1
- data/app/helpers/navigation_helper.rb +6 -1
- data/app/models/github/pull_request.rb +13 -4
- data/app/models/task.rb +1 -1
- data/app/presenters/task_presenter.rb +6 -6
- data/app/views/actions/_actions.html.erb +12 -0
- data/app/views/actions/index.html.erb +19 -12
- data/app/views/actions/running.html.erb +47 -0
- data/app/views/actions/show.html.erb +13 -14
- data/app/views/errors/_actions.html.erb +1 -1
- data/app/views/errors/index.html.erb +5 -1
- data/app/views/layouts/_navigation.html.erb +10 -5
- data/config/initializers/add_navigation_renderers.rb +8 -6
- data/config/initializers/houston_deliver.rb +16 -0
- data/config/routes.rb +1 -0
- data/db/structure.sql +1 -1
- data/houston-core.gemspec +1 -1
- data/lib/generators/module_generator.rb +5 -1
- data/lib/houston/boot/actions.rb +4 -1
- data/lib/houston/boot/configuration.rb +8 -1
- data/lib/houston/boot/extensions.rb +35 -9
- data/lib/houston/boot/serializer.rb +6 -1
- data/lib/houston/version.rb +1 -1
- data/script/cibuild +2 -2
- data/templates/new-module/lib/houston/%name%.rb +30 -7
- data/test/acceptance/creating_a_release_test.rb +14 -21
- data/test/data/bare_repo.git/FETCH_HEAD +1 -1
- data/test/data/bare_repo.git/objects/60/3a4970ec7ed5eb3f7a578850c21b7162424465 +1 -0
- data/test/data/bare_repo.git/objects/b9/1a4fe778398bc87b29842ca84a2727b4763c9d +1 -0
- data/test/data/bare_repo.git/objects/ba/b88c00ec62b5f30bf720ab1136401d1f9d2310 +2 -0
- data/test/data/bare_repo.git/refs/heads/master +1 -1
- data/test/integration/commits_api_test.rb +1 -0
- data/test/test_helper.rb +2 -2
- data/test/unit/models/sprint_test.rb +1 -1
- data/tmp/.keep +0 -0
- metadata +16 -5
data/app/models/task.rb
CHANGED
@@ -22,13 +22,13 @@ class TaskPresenter
|
|
22
22
|
project = ticket.project
|
23
23
|
{ id: task.id,
|
24
24
|
|
25
|
-
projectId: project.id,
|
26
|
-
projectSlug: project.slug,
|
27
|
-
projectTitle: project.name,
|
28
|
-
projectColor: project.color,
|
25
|
+
projectId: project && project.id,
|
26
|
+
projectSlug: project && project.slug,
|
27
|
+
projectTitle: project && project.name,
|
28
|
+
projectColor: project && project.color,
|
29
29
|
|
30
|
-
ticketSystem: project.ticket_tracker_name,
|
31
|
-
ticketUrl: ticket.ticket_tracker_ticket_url,
|
30
|
+
ticketSystem: project && project.ticket_tracker_name,
|
31
|
+
ticketUrl: project && ticket.ticket_tracker_ticket_url,
|
32
32
|
ticketNumber: ticket.number,
|
33
33
|
ticketType: ticket.type.to_s.downcase.dasherize,
|
34
34
|
ticketSequence: ticket.extended_attributes["sequence"], # <-- embeds knowledge of Houston::Scheduler
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% @actions.each do |action| %>
|
2
|
+
<tr class="action" data-timestamp="<%= action.started_at.iso8601 %>">
|
3
|
+
<td class="table-margin"></td>
|
4
|
+
<td class="action-time"><%= format_time action.started_at %></td>
|
5
|
+
<td class="action-duration"><%= format_duration action.duration %></td>
|
6
|
+
<td class="action-trigger"><%= action.trigger %></td>
|
7
|
+
<td class="action-params"><%= format_action_params action.params %></td>
|
8
|
+
<td class="action-succeded"><%= format_action_state action %></td>
|
9
|
+
<td class="action-exception"><%= action.error.message if action.error %><td>
|
10
|
+
<td class="table-margin"></td>
|
11
|
+
</tr>
|
12
|
+
<% end %>
|
@@ -16,10 +16,12 @@
|
|
16
16
|
<tr>
|
17
17
|
<td class="table-margin"></td>
|
18
18
|
<th class="action-name">Name</th>
|
19
|
+
<th class="action-params">Params</th>
|
19
20
|
<th class="action-last">Last Run</th>
|
20
21
|
<th class="action-reliability">Reliability</th>
|
22
|
+
<th class="action-count">Count</th>
|
21
23
|
<th class="action-duration">Duration</th>
|
22
|
-
|
24
|
+
<td class="action-run-now"></td>
|
23
25
|
<td class="table-margin"></td>
|
24
26
|
</tr>
|
25
27
|
</thead>
|
@@ -28,24 +30,28 @@
|
|
28
30
|
<tr class="action">
|
29
31
|
<td class="table-margin"></td>
|
30
32
|
<td class="action-name"><%= link_to action[:name], action_path(slug: action[:name]) %></td>
|
33
|
+
<td class="action-params"><%= action[:required_params].join(", ") %></td>
|
31
34
|
<% if action[:last] %>
|
32
35
|
<td class="action-last" data-timestamp="<%= action[:last].started_at.iso8601 %>">
|
33
36
|
<%= format_time action[:last].started_at, today: false %>
|
34
37
|
<%= format_action_state action[:last] %>
|
35
38
|
</td>
|
36
|
-
<td class="action-reliability">
|
39
|
+
<td class="action-reliability" data-position="<%= action[:successful_runs].to_f / action[:runs] %>">
|
37
40
|
<%= number_to_percentage 100.0 * action[:successful_runs].to_f / action[:runs], precision: 1 %>
|
38
|
-
<span class="action-success-rate">/<%= action[:runs] %></span>
|
39
41
|
</td>
|
42
|
+
<td class="action-count"><%= action[:runs] %></td>
|
40
43
|
<td class="action-duration" data-position="<%= action[:avg_duration] %>"><%= format_duration action[:avg_duration] %></td>
|
41
44
|
<% else %>
|
42
|
-
<td class="action-last">—</td>
|
43
|
-
<td class="action-reliability">—</td>
|
44
|
-
<td class="action-
|
45
|
+
<td class="action-last" data-timestamp="0">—</td>
|
46
|
+
<td class="action-reliability" data-position="0">—</td>
|
47
|
+
<td class="action-count">0</td>
|
48
|
+
<td class="action-duration" data-position="0">—</td>
|
45
49
|
<% end %>
|
46
|
-
|
47
|
-
|
48
|
-
|
50
|
+
<td class="action-run-now">
|
51
|
+
<% if action[:required_params].empty? %>
|
52
|
+
<%= button_to "Run now", run_action_path(slug: action[:name]), :class => "btn btn-default" %>
|
53
|
+
<% end %>
|
54
|
+
</td>
|
49
55
|
<td class="table-margin"></td>
|
50
56
|
</tr>
|
51
57
|
<% end %>
|
@@ -59,9 +65,10 @@
|
|
59
65
|
$(function() {
|
60
66
|
$('#actions').tablesorter({
|
61
67
|
headers: {
|
62
|
-
|
63
|
-
|
64
|
-
|
68
|
+
3: { sorter: 'timestamp' },
|
69
|
+
4: { sorter: 'property' },
|
70
|
+
5: { sorter: 'integer' },
|
71
|
+
6: { sorter: 'property' }
|
65
72
|
}
|
66
73
|
});
|
67
74
|
});
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<% content_for :title do %>
|
2
|
+
<h1 class="project-banner space-below">
|
3
|
+
Running Actions
|
4
|
+
</h1>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<div class="nomargin">
|
8
|
+
<table id="actions" class="table table-sortable table-striped">
|
9
|
+
<thead>
|
10
|
+
<tr>
|
11
|
+
<td class="table-margin"></td>
|
12
|
+
<th class="action-time">Started</th>
|
13
|
+
<th class="action-duration">Duration</th>
|
14
|
+
<th class="action-name">Action</th>
|
15
|
+
<th class="action-trigger">Trigger</th>
|
16
|
+
<th class="action-params">Params</th>
|
17
|
+
<td class="table-margin"></td>
|
18
|
+
</tr>
|
19
|
+
</thead>
|
20
|
+
<tbody>
|
21
|
+
<% @actions.each do |action| %>
|
22
|
+
<tr class="action">
|
23
|
+
<td class="table-margin"></td>
|
24
|
+
<td class="action-time"><%= format_time action.started_at %></td>
|
25
|
+
<td class="action-duration"><%= format_time_ago action.started_at %></td>
|
26
|
+
<td class="action-name"><%= link_to action[:name], action_path(slug: action[:name]) %></td>
|
27
|
+
<td class="action-trigger"><%= action.trigger %></td>
|
28
|
+
<td class="action-params"><%= format_action_params action.params %></td>
|
29
|
+
<td class="table-margin"></td>
|
30
|
+
</tr>
|
31
|
+
<% end %>
|
32
|
+
</tbody>
|
33
|
+
</table>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
|
37
|
+
<% content_for :javascripts do %>
|
38
|
+
<script type="text/javascript">
|
39
|
+
$(function() {
|
40
|
+
$('#actions')
|
41
|
+
.tablesorter()
|
42
|
+
.on('click', '.action-params-short', function() {
|
43
|
+
$(this).hide().next().show();
|
44
|
+
});
|
45
|
+
});
|
46
|
+
</script>
|
47
|
+
<% end %>
|
@@ -19,19 +19,8 @@
|
|
19
19
|
<td class="table-margin"></td>
|
20
20
|
</tr>
|
21
21
|
</thead>
|
22
|
-
<tbody>
|
23
|
-
|
24
|
-
<tr class="action">
|
25
|
-
<td class="table-margin"></td>
|
26
|
-
<td class="action-time"><%= format_time action.started_at %></td>
|
27
|
-
<td class="action-duration"><%= format_duration action.duration %></td>
|
28
|
-
<td class="action-trigger"><%= action.trigger %></td>
|
29
|
-
<td class="action-params"><%= format_action_params action.params %></td>
|
30
|
-
<td class="action-succeded"><%= format_action_state action %></td>
|
31
|
-
<td class="action-exception"><%= action.error.message if action.error %><td>
|
32
|
-
<td class="table-margin"></td>
|
33
|
-
</tr>
|
34
|
-
<% end %>
|
22
|
+
<tbody class="infinite-scroll">
|
23
|
+
<%= render "actions/actions" %>
|
35
24
|
</tbody>
|
36
25
|
</table>
|
37
26
|
</div>
|
@@ -39,7 +28,17 @@
|
|
39
28
|
<% content_for :javascripts do %>
|
40
29
|
<script type="text/javascript">
|
41
30
|
$(function() {
|
42
|
-
$('#actions')
|
31
|
+
$('#actions')
|
32
|
+
.tablesorter()
|
33
|
+
.on('click', '.action-params-short', function() {
|
34
|
+
$(this).hide().next().show();
|
35
|
+
});
|
36
|
+
});
|
37
|
+
new InfiniteScroll({
|
38
|
+
load: function($tbody) {
|
39
|
+
var timestamp = $tbody.find('.action:last').attr('data-timestamp');
|
40
|
+
return $.get(window.location.pathname, {before: timestamp});
|
41
|
+
}
|
43
42
|
});
|
44
43
|
</script>
|
45
44
|
<% end %>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
<td class="action-time"><%= format_time action.finished_at %></td>
|
5
5
|
<td class="action-name"><%= link_to action[:name], action_path(slug: action[:name]) %></td>
|
6
6
|
<td class="action-trigger"><%= action.trigger %></td>
|
7
|
-
<td class="action-params"
|
7
|
+
<td class="action-params"><%= format_action_params action.params %></td>
|
8
8
|
<td class="action-error-message"><%= action.error.message %></td>
|
9
9
|
<td class="table-margin"></td>
|
10
10
|
</tr>
|
@@ -27,7 +27,11 @@
|
|
27
27
|
<% content_for :javascripts do %>
|
28
28
|
<script type="text/javascript">
|
29
29
|
$(function() {
|
30
|
-
$('#
|
30
|
+
$('#actions')
|
31
|
+
.tablesorter()
|
32
|
+
.on('click', '.action-params-short', function() {
|
33
|
+
$(this).hide().next().show();
|
34
|
+
});
|
31
35
|
});
|
32
36
|
new InfiniteScroll({
|
33
37
|
load: function($tbody) {
|
@@ -5,14 +5,19 @@
|
|
5
5
|
<ul class="nav pull-right">
|
6
6
|
<% if current_user -%>
|
7
7
|
|
8
|
-
<% if
|
8
|
+
<% if can?(:manage, User) || can?(:read, Action) %>
|
9
9
|
<li class="dropdown">
|
10
10
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-cog"></i> <b class="caret"></b></a>
|
11
11
|
<ul class="dropdown-menu">
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
<% if can?(:manage, User) %>
|
13
|
+
<li><%= link_to "Users", main_app.users_path %></li>
|
14
|
+
<% end %>
|
15
|
+
<% if can?(:read, Action) %>
|
16
|
+
<li><%= link_to "Actions", main_app.actions_path %></li>
|
17
|
+
<li><%= link_to "Running Actions", main_app.running_actions_path %></li>
|
18
|
+
<li><%= link_to "Triggers", main_app.triggers_path %></li>
|
19
|
+
<li><%= link_to "Errors", main_app.errors_path %></li>
|
20
|
+
<% end %>
|
16
21
|
</ul>
|
17
22
|
</li>
|
18
23
|
<% end %>
|
@@ -1,13 +1,15 @@
|
|
1
1
|
Houston.add_navigation_renderer :sprint do
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
name "Sprint"
|
3
|
+
icon "fa-burndown"
|
4
|
+
path { Houston::Application.routes.url_helpers.current_sprint_path }
|
5
|
+
ability { |ability| ability.can?(:read, Sprint) }
|
5
6
|
end
|
6
7
|
|
7
8
|
Houston.add_navigation_renderer :pulls do
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
name "Pulls"
|
10
|
+
icon "octokit-pull-request"
|
11
|
+
path { Houston::Application.routes.url_helpers.pulls_path }
|
12
|
+
ability { |ability| ability.can?(:read, Github::PullRequest) }
|
11
13
|
end
|
12
14
|
|
13
15
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Houston
|
2
|
+
|
3
|
+
def self.deliver!(message)
|
4
|
+
Houston.try({max_tries: 3},
|
5
|
+
Errno::ECONNRESET,
|
6
|
+
Errno::EPIPE,
|
7
|
+
Errno::ETIMEDOUT,
|
8
|
+
Net::OpenTimeout,
|
9
|
+
Net::ReadTimeout,
|
10
|
+
Net::SMTPServerBusy,
|
11
|
+
EOFError) do
|
12
|
+
message.deliver_now!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/config/routes.rb
CHANGED
@@ -237,6 +237,7 @@ Rails.application.routes.draw do
|
|
237
237
|
# Actions
|
238
238
|
|
239
239
|
get "actions", to: "actions#index", as: :actions
|
240
|
+
get "actions/running", to: "actions#running", as: :running_actions
|
240
241
|
get "actions/:slug", to: "actions#show", as: :action, constraints: { slug: /[^\/]+/ }
|
241
242
|
post "actions/:slug", to: "actions#run", as: :run_action, constraints: { slug: /[^\/]+/ }
|
242
243
|
|
data/db/structure.sql
CHANGED
data/houston-core.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["bob.lailfamily@gmail.com"]
|
11
11
|
|
12
12
|
spec.summary = %q{Mission Control for your projects and teams}
|
13
|
-
spec.homepage = "https://github.com/houston/houston"
|
13
|
+
spec.homepage = "https://github.com/houston/houston-core"
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
16
16
|
spec.executables = ["houston"]
|
@@ -13,7 +13,6 @@ module Generators
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def config
|
16
|
-
template "config/initializers/add_navigation_renderer.rb"
|
17
16
|
template "config/database.yml"
|
18
17
|
template "config/routes.rb"
|
19
18
|
empty_directory_with_keep_file "db"
|
@@ -41,6 +40,11 @@ module Generators
|
|
41
40
|
# do nothing
|
42
41
|
end
|
43
42
|
|
43
|
+
def assets_manifest
|
44
|
+
# do nothing
|
45
|
+
# added in Rails 5; Houston requires ~> 4.2.7
|
46
|
+
end
|
47
|
+
|
44
48
|
def readme
|
45
49
|
template "README.md"
|
46
50
|
end
|
data/lib/houston/boot/actions.rb
CHANGED
@@ -2,6 +2,9 @@ require "thread_safe"
|
|
2
2
|
|
3
3
|
module Houston
|
4
4
|
class Actions
|
5
|
+
class ExecutionContext < ReadonlyHash
|
6
|
+
end
|
7
|
+
|
5
8
|
|
6
9
|
def initialize
|
7
10
|
@actions = ThreadSafe::Hash.new
|
@@ -90,7 +93,7 @@ module Houston
|
|
90
93
|
private
|
91
94
|
|
92
95
|
def run!(params, options={})
|
93
|
-
params =
|
96
|
+
params = ExecutionContext.new(params)
|
94
97
|
trigger = options.fetch(:trigger, "manual")
|
95
98
|
|
96
99
|
::Action.record name, params, trigger do
|
@@ -147,7 +147,8 @@ module_function
|
|
147
147
|
|
148
148
|
def navigation(*args)
|
149
149
|
@navigation = args if args.any?
|
150
|
-
@navigation
|
150
|
+
return Houston.available_navigation_renderers unless @navigation
|
151
|
+
@navigation & Houston.available_navigation_renderers
|
151
152
|
end
|
152
153
|
|
153
154
|
def project_features(*args)
|
@@ -380,6 +381,12 @@ module_function
|
|
380
381
|
def on(*args, &block)
|
381
382
|
event_name, action_name = extract_trigger_and_action!(args)
|
382
383
|
event = Houston.get_registered_event(event_name)
|
384
|
+
|
385
|
+
unless event
|
386
|
+
puts "\e[31mUnregistered event: \e[1m#{event_name}\e[0;90m\n#{caller[0]}\e[0m\n\n"
|
387
|
+
return
|
388
|
+
end
|
389
|
+
|
383
390
|
action = assert_action! action_name, event.params, &block
|
384
391
|
action.assert_required_params! event.params
|
385
392
|
|
@@ -7,14 +7,26 @@ module Houston
|
|
7
7
|
|
8
8
|
|
9
9
|
|
10
|
-
def
|
11
|
-
@navigation_renderers
|
10
|
+
def available_navigation_renderers
|
11
|
+
@navigation_renderers.keys
|
12
12
|
end
|
13
13
|
|
14
14
|
def get_navigation_renderer(name)
|
15
15
|
@navigation_renderers.fetch(name)
|
16
16
|
end
|
17
17
|
|
18
|
+
def add_navigation_renderer(slug, &block)
|
19
|
+
dsl = FeatureDsl.new(GlobalFeature.new)
|
20
|
+
dsl.instance_eval(&block)
|
21
|
+
feature = dsl.feature
|
22
|
+
feature.slug = slug
|
23
|
+
raise ArgumentError, "Renderer must supply name, but #{slug.inspect} doesn't" unless feature.name
|
24
|
+
raise ArgumentError, "Renderer must supply icon, but #{slug.inspect} doesn't" unless feature.icon
|
25
|
+
raise ArgumentError, "Renderer must supply path lambda, but #{slug.inspect} doesn't" unless feature.path_block
|
26
|
+
|
27
|
+
@navigation_renderers[slug] = feature
|
28
|
+
end
|
29
|
+
|
18
30
|
|
19
31
|
|
20
32
|
|
@@ -23,11 +35,11 @@ module Houston
|
|
23
35
|
end
|
24
36
|
|
25
37
|
def get_project_feature(slug)
|
26
|
-
@available_project_features
|
38
|
+
@available_project_features.fetch(slug)
|
27
39
|
end
|
28
40
|
|
29
41
|
def add_project_feature(slug, &block)
|
30
|
-
dsl =
|
42
|
+
dsl = FeatureDsl.new(ProjectFeature.new)
|
31
43
|
dsl.instance_eval(&block)
|
32
44
|
feature = dsl.feature
|
33
45
|
feature.slug = slug
|
@@ -105,8 +117,21 @@ module Houston
|
|
105
117
|
|
106
118
|
private
|
107
119
|
|
108
|
-
class
|
109
|
-
attr_accessor :name, :slug, :icon, :path_block, :ability_block
|
120
|
+
class GlobalFeature
|
121
|
+
attr_accessor :name, :slug, :icon, :path_block, :ability_block
|
122
|
+
|
123
|
+
def path
|
124
|
+
path_block.call
|
125
|
+
end
|
126
|
+
|
127
|
+
def permitted?(ability)
|
128
|
+
return true if ability_block.nil?
|
129
|
+
ability_block.call ability
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class ProjectFeature < GlobalFeature
|
134
|
+
attr_accessor :fields
|
110
135
|
|
111
136
|
def initialize
|
112
137
|
self.fields = []
|
@@ -115,6 +140,7 @@ module Houston
|
|
115
140
|
def project_path(project)
|
116
141
|
path_block.call project
|
117
142
|
end
|
143
|
+
alias :path :project_path
|
118
144
|
|
119
145
|
def permitted?(ability, project)
|
120
146
|
return true if ability_block.nil?
|
@@ -122,11 +148,11 @@ module Houston
|
|
122
148
|
end
|
123
149
|
end
|
124
150
|
|
125
|
-
class
|
151
|
+
class FeatureDsl
|
126
152
|
attr_reader :feature
|
127
153
|
|
128
|
-
def initialize
|
129
|
-
@feature =
|
154
|
+
def initialize(feature)
|
155
|
+
@feature = feature
|
130
156
|
end
|
131
157
|
|
132
158
|
def name(value)
|