houston-core 0.7.0.beta4 → 0.7.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -2
  3. data/.travis.yml +50 -0
  4. data/Gemfile +1 -2
  5. data/Gemfile.lock +1 -7
  6. data/README.md +21 -9
  7. data/app/assets/stylesheets/houston/application/actions.scss +25 -4
  8. data/app/assets/stylesheets/houston/application/sprint.scss +1 -1
  9. data/app/assets/stylesheets/houston/application/test.scss +1 -1
  10. data/app/assets/stylesheets/houston/application/test_run.scss +2 -2
  11. data/app/assets/stylesheets/houston/application/timeline.scss +1 -1
  12. data/app/assets/stylesheets/houston/core/roboto.scss.erb +2 -2
  13. data/app/controllers/actions_controller.rb +13 -5
  14. data/app/controllers/errors_controller.rb +2 -2
  15. data/app/controllers/releases_controller.rb +1 -1
  16. data/app/controllers/triggers_controller.rb +1 -1
  17. data/app/helpers/actions_helper.rb +36 -1
  18. data/app/helpers/navigation_helper.rb +6 -1
  19. data/app/models/github/pull_request.rb +13 -4
  20. data/app/models/task.rb +1 -1
  21. data/app/presenters/task_presenter.rb +6 -6
  22. data/app/views/actions/_actions.html.erb +12 -0
  23. data/app/views/actions/index.html.erb +19 -12
  24. data/app/views/actions/running.html.erb +47 -0
  25. data/app/views/actions/show.html.erb +13 -14
  26. data/app/views/errors/_actions.html.erb +1 -1
  27. data/app/views/errors/index.html.erb +5 -1
  28. data/app/views/layouts/_navigation.html.erb +10 -5
  29. data/config/initializers/add_navigation_renderers.rb +8 -6
  30. data/config/initializers/houston_deliver.rb +16 -0
  31. data/config/routes.rb +1 -0
  32. data/db/structure.sql +1 -1
  33. data/houston-core.gemspec +1 -1
  34. data/lib/generators/module_generator.rb +5 -1
  35. data/lib/houston/boot/actions.rb +4 -1
  36. data/lib/houston/boot/configuration.rb +8 -1
  37. data/lib/houston/boot/extensions.rb +35 -9
  38. data/lib/houston/boot/serializer.rb +6 -1
  39. data/lib/houston/version.rb +1 -1
  40. data/script/cibuild +2 -2
  41. data/templates/new-module/lib/houston/%name%.rb +30 -7
  42. data/test/acceptance/creating_a_release_test.rb +14 -21
  43. data/test/data/bare_repo.git/FETCH_HEAD +1 -1
  44. data/test/data/bare_repo.git/objects/60/3a4970ec7ed5eb3f7a578850c21b7162424465 +1 -0
  45. data/test/data/bare_repo.git/objects/b9/1a4fe778398bc87b29842ca84a2727b4763c9d +1 -0
  46. data/test/data/bare_repo.git/objects/ba/b88c00ec62b5f30bf720ab1136401d1f9d2310 +2 -0
  47. data/test/data/bare_repo.git/refs/heads/master +1 -1
  48. data/test/integration/commits_api_test.rb +1 -0
  49. data/test/test_helper.rb +2 -2
  50. data/test/unit/models/sprint_test.rb +1 -1
  51. data/tmp/.keep +0 -0
  52. metadata +16 -5
data/app/models/task.rb CHANGED
@@ -17,7 +17,7 @@ class Task < ActiveRecord::Base
17
17
 
18
18
  attr_readonly :number
19
19
 
20
- default_scope { order(:number) }
20
+ default_scope { order(:number).joins(:ticket) }
21
21
 
22
22
 
23
23
 
@@ -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
- <!--<td class="action-run-now"></td>-->
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">&mdash;</td>
43
- <td class="action-reliability">&mdash;</td>
44
- <td class="action-duration">&mdash;</td>
45
+ <td class="action-last" data-timestamp="0">&mdash;</td>
46
+ <td class="action-reliability" data-position="0">&mdash;</td>
47
+ <td class="action-count">0</td>
48
+ <td class="action-duration" data-position="0">&mdash;</td>
45
49
  <% end %>
46
- <!--<td class="action-run-now">
47
- <%= button_to "Run now", run_action_path(slug: action[:name]), :class => "btn btn-default" %>
48
- </td>-->
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
- 2: { sorter: 'timestamp' },
63
- 3: { sorter: 'percent' },
64
- 4: { sorter: 'property' }
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
- <% @actions.each do |action| %>
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').tablesorter();
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"><%# format_action_params action.params %></td>
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
- $('#triggers').tablesorter();
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 current_user.administrator? %>
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
- <li><%= link_to "Users", main_app.users_path %></li>
13
- <li><%= link_to "Actions", main_app.actions_path %></li>
14
- <li><%= link_to "Triggers", main_app.triggers_path %></li>
15
- <li><%= link_to "Errors", main_app.errors_path %></li>
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
- if can?(:read, Sprint)
3
- render_nav_link "Sprint", main_app.current_sprint_path, icon: "fa-burndown"
4
- end
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
- if can?(:read, Github::PullRequest)
9
- render_nav_link "Pulls", main_app.pulls_path, icon: "octokit-pull-request"
10
- end
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
@@ -66,7 +66,7 @@ CREATE TABLE actions (
66
66
  succeeded boolean,
67
67
  error_id integer,
68
68
  trigger character varying,
69
- params jsonb
69
+ params text
70
70
  );
71
71
 
72
72
 
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
@@ -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 = ReadonlyHash.new(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 add_navigation_renderer(name, &block)
11
- @navigation_renderers[name] = block
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[slug]
38
+ @available_project_features.fetch(slug)
27
39
  end
28
40
 
29
41
  def add_project_feature(slug, &block)
30
- dsl = ProjectFeatureDsl.new
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 ProjectFeature
109
- attr_accessor :name, :slug, :icon, :path_block, :ability_block, :fields
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 ProjectFeatureDsl
151
+ class FeatureDsl
126
152
  attr_reader :feature
127
153
 
128
- def initialize
129
- @feature = ProjectFeature.new
154
+ def initialize(feature)
155
+ @feature = feature
130
156
  end
131
157
 
132
158
  def name(value)