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.
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)