houston-core 0.5.6 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +94 -69
  3. data/app/adapters/houston/adapters/deployment/engineyard.rb +4 -3
  4. data/app/adapters/houston/adapters/version_control/git_adapter.rb +36 -42
  5. data/app/adapters/houston/adapters/version_control/git_adapter/github_repo.rb +2 -2
  6. data/app/adapters/houston/adapters/version_control/git_adapter/remote_repo.rb +23 -11
  7. data/app/adapters/houston/adapters/version_control/git_adapter/repo.rb +18 -8
  8. data/app/adapters/houston/adapters/version_control/null_repo.rb +8 -0
  9. data/app/assets/javascripts/core/handlebars_helpers.coffee +3 -3
  10. data/app/assets/stylesheets/application/github_repos.scss +77 -0
  11. data/app/assets/stylesheets/application/navigation.scss +2 -0
  12. data/app/assets/stylesheets/application/pull_requests.scss +44 -58
  13. data/app/assets/stylesheets/core/avatars.scss +5 -0
  14. data/app/assets/stylesheets/core/colors.scss.erb +10 -7
  15. data/app/concerns/commit_synchronizer.rb +3 -0
  16. data/app/controllers/github/pulls_controller.rb +12 -0
  17. data/app/controllers/project_tests_controller.rb +3 -3
  18. data/app/controllers/projects_controller.rb +45 -1
  19. data/app/controllers/releases_controller.rb +42 -26
  20. data/app/helpers/application_helper.rb +8 -0
  21. data/app/helpers/avatar_helper.rb +2 -2
  22. data/app/helpers/commit_helper.rb +2 -2
  23. data/app/helpers/oembed_helper.rb +8 -0
  24. data/app/helpers/project_helper.rb +4 -5
  25. data/app/helpers/release_helper.rb +11 -0
  26. data/app/helpers/timeline_helper.rb +1 -1
  27. data/app/helpers/url_helper.rb +0 -18
  28. data/app/interactors/cache_key_dependencies.rb +28 -0
  29. data/app/jobs/sync_all_tickets_job.rb +1 -0
  30. data/app/mailers/view_mailer.rb +0 -1
  31. data/app/models/commit.rb +1 -1
  32. data/app/models/github/pull_request.rb +82 -26
  33. data/app/models/github/pull_request_event.rb +2 -2
  34. data/app/models/milestone.rb +1 -0
  35. data/app/models/project.rb +14 -0
  36. data/app/models/project_dependencies.rb +5 -3
  37. data/app/models/task.rb +1 -1
  38. data/app/models/user.rb +41 -0
  39. data/app/views/commits/show.html.erb +9 -1
  40. data/app/views/github/pulls/index.html.erb +102 -0
  41. data/app/views/project_notification/new_release.html.erb +6 -0
  42. data/app/views/project_tickets/index.xls.erb +0 -7
  43. data/app/views/projects/_form.html.erb +29 -17
  44. data/app/views/projects/index.html.erb +3 -3
  45. data/app/views/projects/new_from_github.html.erb +67 -0
  46. data/app/views/releases/_commits.html.erb +1 -1
  47. data/app/views/releases/show.html.erb +9 -0
  48. data/app/views/users/_form.html.erb +35 -19
  49. data/config/application.rb +12 -0
  50. data/config/initializers/mime_types.rb +1 -0
  51. data/config/routes.rb +17 -3
  52. data/db/migrate/20151201042126_require_projects_to_have_name_and_slug.rb +6 -0
  53. data/db/migrate/20151202005557_add_head_sha_to_projects.rb +24 -0
  54. data/db/migrate/20151202011812_require_projects_to_have_color.rb +13 -0
  55. data/db/migrate/20151205204922_require_project_slugs_to_be_unique.rb +5 -0
  56. data/db/migrate/20151205214647_add_avatar_url_to_pull_requests.rb +5 -0
  57. data/db/migrate/20151209004458_add_json_labels_to_pull_requests.rb +5 -0
  58. data/db/migrate/20151209030113_add_timestamps_to_pull_requests.rb +6 -0
  59. data/db/structure.sql +31 -5
  60. data/houston.gemspec +7 -7
  61. data/lib/configuration.rb +3 -2
  62. data/lib/houston/version.rb +1 -1
  63. data/lib/rack/oembed.rb +23 -0
  64. data/templates/new-instance/config/jobs/cache_key_dependencies.rb +3 -0
  65. data/templates/new-instance/config/triggers/tests/slack_when_analyzed.rb +1 -4
  66. data/templates/new-instance/config/triggers/tests/slack_when_completed.rb +1 -1
  67. data/templates/new-instance/lib/slack_helpers.rb +1 -1
  68. data/test/integration/ticket_tasks_api_test.rb +1 -1
  69. data/test/unit/adapters/git_adapter_test.rb +29 -8
  70. data/test/unit/adapters/version_control_adapters_api_test.rb +2 -0
  71. data/test/unit/controllers/hooks_controller_test.rb +4 -4
  72. data/test/unit/models/commit_test.rb +2 -2
  73. data/test/unit/models/project_test.rb +2 -2
  74. data/test/unit/models/pull_request_test.rb +9 -4
  75. data/test/unit/models/task_test.rb +1 -1
  76. data/test/unit/models/ticket_test.rb +1 -1
  77. metadata +31 -16
@@ -35,8 +35,8 @@ module Github
35
35
  return unless pr && pr.persisted?
36
36
 
37
37
  case action
38
- when "labeled" then pr.add_label! payload["label"]["name"], as: actor
39
- when "unlabeled" then pr.remove_label! payload["label"]["name"], as: actor
38
+ when "labeled" then pr.add_label! payload["label"], as: actor
39
+ when "unlabeled" then pr.remove_label! payload["label"], as: actor
40
40
  end
41
41
  end
42
42
 
@@ -30,6 +30,7 @@ class Milestone < ActiveRecord::Base
30
30
  arel_table[:tickets_count].gt(0).and(
31
31
  arel_table[:closed_tickets_count].eq(arel_table[:tickets_count])))
32
32
  end
33
+ alias :closed :completed
33
34
 
34
35
  def uncompleted
35
36
  where(
@@ -15,6 +15,7 @@ class Project < ActiveRecord::Base
15
15
  has_many :roles, -> { joins(:user).merge(User.unretired) }, dependent: :destroy, validate: false
16
16
  has_many :value_statements, dependent: :destroy
17
17
  has_many :pull_requests, class_name: "Github::PullRequest"
18
+ belongs_to :head, class_name: "Commit", foreign_key: "head_sha", primary_key: "sha"
18
19
 
19
20
  Houston.config.project_roles.each do |role|
20
21
  collection_name = role.downcase.gsub(' ', '_').pluralize
@@ -27,6 +28,9 @@ class Project < ActiveRecord::Base
27
28
  reject_if: proc { |attrs| attrs[:user_id].blank? or attrs[:name].blank? }
28
29
  accepts_nested_attributes_for :value_statements, :allow_destroy => true
29
30
 
31
+ before_validation :generate_default_slug, :set_default_color
32
+ validates_presence_of :name, :slug, :color
33
+
30
34
 
31
35
 
32
36
  has_adapter :TicketTracker,
@@ -248,4 +252,14 @@ class Project < ActiveRecord::Base
248
252
 
249
253
 
250
254
 
255
+ private
256
+
257
+ def generate_default_slug
258
+ self.slug = self.name.to_s.underscore.gsub("/", "-").dasherize.gsub(".", "").gsub(/\s+/, "_") unless slug
259
+ end
260
+
261
+ def set_default_color
262
+ self.color = "default" unless color
263
+ end
264
+
251
265
  end
@@ -41,10 +41,12 @@ class ProjectDependencies < SimpleDelegator
41
41
  end
42
42
 
43
43
  def lockfile
44
- return "" if repo.nil?
44
+ return @lockfile if defined?(@lockfile)
45
+ return "" unless repo.exists?
45
46
 
46
- @lockfile = read_file("Gemfile.lock", commit: repo.branch("master")) unless defined?(@lockfile)
47
- @lockfile
47
+ Houston.benchmark "[project_dependencies.lockfile] #{slug}" do
48
+ @lockfile = read_file("Gemfile.lock", commit: head_sha)
49
+ end
48
50
  rescue Houston::Adapters::VersionControl::FileNotFound
49
51
  @lockfile = ""
50
52
  end
@@ -123,7 +123,7 @@ class Task < ActiveRecord::Base
123
123
 
124
124
 
125
125
 
126
- def committed!(commit)
126
+ def mark_committed!(commit)
127
127
  update_column :first_commit_at, commit.authored_at unless committed?
128
128
  Houston.observer.fire "task:committed", self
129
129
  end
@@ -117,6 +117,47 @@ class User < ActiveRecord::Base
117
117
 
118
118
 
119
119
 
120
+ # Extract to Houston::GitHub
121
+ # ------------------------------------------------------------------------- #
122
+
123
+ def self.find_by_github_username(username)
124
+ # If we can already identify the user who has the given username, return them
125
+ user = ::User.where(["view_options->'github_username' = ?", username]).first
126
+ return user if user
127
+
128
+ # Look up the email address of the GitHub user and see if we can
129
+ # identify the Houston user by the GitHub user's email address.
130
+ user = Houston.github.user(username)
131
+ user = find_by_email_address user.email if user
132
+
133
+ # We couldn't find the user by their email address, now
134
+ # we'll look at their nicknames
135
+ user = find_by_nickname username unless user
136
+
137
+ # We've failed to identify this user
138
+ unless user
139
+ Rails.logger.warn "\e[31m[pulls] Unable to identify a user for the GitHub username \e[1m#{username}\e[0m"
140
+ return nil
141
+ end
142
+
143
+ # If we have identified the user, store their username so that
144
+ # we can skip the email-lookup step in the future.
145
+ user.set_github_username! username
146
+ user
147
+ end
148
+
149
+ def github_username
150
+ view_options["github_username"]
151
+ end
152
+
153
+ def set_github_username!(username)
154
+ update_column :view_options, view_options.merge("github_username" => username)
155
+ end
156
+
157
+ # ------------------------------------------------------------------------- #
158
+
159
+
160
+
120
161
  # LDAP Overrides
121
162
  # ------------------------------------------------------------------------- #
122
163
 
@@ -1,3 +1,11 @@
1
+ <% content_for :meta do %>
2
+ <%= tag "meta", property: "og:type", content: "website" %>
3
+ <%= tag "meta", property: "og:site_name", content: "#{@project.slug} / commit" %>
4
+ <%= tag "meta", property: "og:title", content: @commit.summary %>
5
+ <%= tag "meta", property: "og:description", content: @commit.description %>
6
+ <%= tag "meta", property: "og:url", content: commit_url(@commit) %>
7
+ <% end %>
8
+
1
9
  <%= render partial: "projects/header", locals: {project: @project, postfix: @commit.sha[0...8]} %>
2
10
 
3
11
  <div class="commit-profile">
@@ -16,7 +24,7 @@
16
24
  <%= div_for(release) do %>
17
25
  <p class="release-header">
18
26
  <%= release.released_at.strftime("%l:%M %p") %>&nbsp;&nbsp;
19
- <%= link_to "details &rarr;".html_safe, release %>
27
+ <%= link_to "details &rarr;".html_safe, release_url(release) %>
20
28
 
21
29
  <% if can?(:read, @project.commits.build) %>
22
30
  <span class="commit-range"><%= link_to_release_commit_range(release) %></span>
@@ -0,0 +1,102 @@
1
+ <% content_for :title do %>
2
+ <h1 class="project-banner space-below">
3
+ <span id="pull_request_count" class="light"><%= @pulls.count %></span>
4
+ Pull Requests
5
+ </h1>
6
+ <% end %>
7
+
8
+ <div class="pull-request-labels pull-request-labels-toggle">
9
+ <% @labels.each do |label| %>
10
+ <%= check_box_tag label["name"], "1", @selected_labels.member?(label["name"]) %><%= label_tag label["name"] do %>
11
+ <%= pull_request_label(label) %>
12
+ <% end %>
13
+ <% end %>
14
+ </div>
15
+
16
+ <div class="nomargin">
17
+ <table id="pull_requests" class="table table-sortable table-striped">
18
+ <thead>
19
+ <tr>
20
+ <td class="table-margin"></td>
21
+ <td class="pull-request-avatar"></td>
22
+ <th class="pull-request-project">Project</th>
23
+ <th class="pull-request-title">Title</th>
24
+ <th class="pull-request-labels">Labels</th>
25
+ <th class="pull-request-age sort-asc">Age</th>
26
+ <td class="table-margin"></td>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <% @pulls.each do |pull| %>
31
+ <tr class="pull-request <%= pull.labels.map { |label| "pull-request-label-#{label["name"]}" }.join(" ") %>">
32
+ <td class="table-margin"></td>
33
+ <td class="pull-request-avatar">
34
+ <% if pull.user %>
35
+ <%= avatar_for pull.user, size: 32 %>
36
+ <% elsif pull.avatar_url %>
37
+ <%= image_tag pull.avatar_url, size: "32", alt: pull.username, class: "avatar avatar-not-our-user" %>
38
+ <% end %>
39
+ </td>
40
+ <td class="pull-request-project"><%= pull.project.slug %></td>
41
+ <td class="pull-request-title">
42
+ <%= link_to emojify(pull.title), pull.url, target: "_blank" %>
43
+ </td>
44
+ <td class="pull-request-labels">
45
+ <% pull.json_labels.each do |label| %>
46
+ <%= pull_request_label(label) %>
47
+ <% end %>
48
+ </td>
49
+ <td class="pull-request-age" data-timestamp="<%= pull.created_at.iso8601 if pull.created_at %>">
50
+ <%= format_time_ago(pull.created_at) %>
51
+ </td>
52
+ <td class="table-margin"></td>
53
+ </tr>
54
+ <% end %>
55
+ </tbody>
56
+ </table>
57
+ </div>
58
+
59
+ <% content_for :javascripts do %>
60
+ <script type="text/javascript">
61
+ $.tablesorter.addParser({
62
+ id: 'labels',
63
+ type: 'text',
64
+ is: function(s) { return false; }, // return false so this parser is not auto detected
65
+ format: function(s, table, td) {
66
+ var $td = $(td),
67
+ labels = _.map($td.find('.label'), function(el) { return $(el).text() });
68
+ return labels.sort().join(',');
69
+ }
70
+ });
71
+
72
+ $(function() {
73
+ $('#pull_requests').tablesorter({
74
+ headers: {
75
+ 4: {sorter: 'labels'},
76
+ 5: {sorter: 'timestamp'}
77
+ }
78
+ });
79
+
80
+ function filterPullRequests() {
81
+ var visibleClasses = $('.pull-request-labels-toggle :checkbox:checked').map(function(el) {
82
+ return 'pull-request-label-' + $(this).attr('name');
83
+ });
84
+
85
+ $('.pull-request').each(function() {
86
+ var $pull = $(this),
87
+ classes = $pull.attr('class').split(' '); classes.shift();
88
+ $pull.toggle(_.all(classes, function(cssClass) {
89
+ return _.contains(visibleClasses, cssClass);
90
+ }));
91
+ });
92
+
93
+ $('#pull_request_count').html($('.pull-request:visible').length);
94
+ }
95
+ filterPullRequests();
96
+
97
+ $('.pull-request-labels-toggle :checkbox').click(function(e) {
98
+ filterPullRequests();
99
+ });
100
+ });
101
+ </script>
102
+ <% end %>
@@ -29,3 +29,9 @@
29
29
  </span>
30
30
  </p>
31
31
  <% end %>
32
+
33
+ <% unless @release.new_record? %>
34
+ <p style="margin-top: 3em;">
35
+ <%= link_to "permalink", release_url(@release) %>
36
+ </p>
37
+ <% end %>
@@ -49,13 +49,11 @@
49
49
  <Column ss:AutoFitWidth="0" ss:Width="700"/>
50
50
  <Column ss:AutoFitWidth="0" ss:Width="85"/>
51
51
  <Column ss:AutoFitWidth="0" ss:Width="85"/>
52
- <Column ss:AutoFitWidth="0" ss:Width="85"/>
53
52
  <Row ss:StyleID="s65">
54
53
  <Cell><Data ss:Type="String">Number</Data></Cell>
55
54
  <Cell><Data ss:Type="String">Type</Data></Cell>
56
55
  <Cell><Data ss:Type="String">Reporter Name</Data></Cell>
57
56
  <Cell><Data ss:Type="String">Summary</Data></Cell>
58
- <Cell><Data ss:Type="String">Effort</Data></Cell>
59
57
  <Cell><Data ss:Type="String">Opened</Data></Cell>
60
58
  <Cell><Data ss:Type="String">Closed</Data></Cell>
61
59
  </Row>
@@ -65,11 +63,6 @@
65
63
  <Cell><Data ss:Type="String"><%= ticket.type %></Data></Cell>
66
64
  <Cell><Data ss:Type="String"><%= ticket.reporter_name %></Data></Cell>
67
65
  <Cell><Data ss:Type="String"><%= ticket.summary %></Data></Cell>
68
- <% if ticket.effort.blank? %>
69
- <Cell />
70
- <% else %>
71
- <Cell><Data ss:Type="Number"><%= ticket.effort %></Data></Cell>
72
- <% end %>
73
66
  <Cell ss:StyleID="s64"><Data ss:Type="DateTime"><%= xls_time ticket.opened_at %></Data></Cell>
74
67
  <% if ticket.closed_at %>
75
68
  <Cell ss:StyleID="s64"><Data ss:Type="DateTime"><%= xls_time ticket.closed_at %></Data></Cell>
@@ -1,23 +1,23 @@
1
- <%= form_for @project, :html => { :class => 'form-horizontal' } do |f| %>
1
+ <%= form_for @project, :html => { :class => "form-horizontal" } do |f| %>
2
2
  <fieldset>
3
3
  <div class="control-group">
4
- <%= f.label :name, :class => 'control-label' %>
4
+ <%= f.label :name, :class => "control-label" %>
5
5
  <div class="controls">
6
- <%= f.text_field :name, :class => 'text_field' %>
6
+ <%= f.text_field :name, :class => "text_field" %>
7
7
  </div>
8
8
  </div>
9
9
 
10
10
  <div class="control-group">
11
- <%= f.label :slug, :class => 'control-label' %>
11
+ <%= f.label :slug, :class => "control-label" %>
12
12
  <div class="controls">
13
- <%= f.text_field :slug, :class => 'text_field' %>
13
+ <%= f.text_field :slug, :class => "text_field" %>
14
14
  </div>
15
15
  </div>
16
16
 
17
17
  <div class="control-group">
18
- <%= f.label :color, :class => 'control-label' %>
18
+ <%= f.label :color, :class => "control-label" %>
19
19
  <div class="controls">
20
- <%= f.select :color, Houston.config.project_colors.keys.map { |name| [name.titleize, name] }, :class => 'select_field' %>
20
+ <%= f.select :color, [["None", "default"]] + Houston.config.project_colors.keys.map { |name| [name.titleize, name] }, :class => "select_field" %>
21
21
  <% if Project.count > 0 %>
22
22
  (Unused colors: <%= (Houston.config.project_colors.keys - Project.pluck(:color).uniq).map(&:titleize).join(", ") %>)
23
23
  <% end %>
@@ -42,7 +42,7 @@
42
42
  <div class="project-adapter-fields" data-adapter="<%= name %>">
43
43
  <% adapter.namespace.adapter(name).parameters.each do |parameter| %>
44
44
  <% errors = @project.errors[parameter].flatten %>
45
- <%= label_tag "project[extended_attributes][#{parameter}]", parameter.to_s.gsub('_', ' ').gsub(/\b('?[a-z])/) { $1.capitalize } %>
45
+ <%= label_tag "project[extended_attributes][#{parameter}]", parameter.to_s.gsub("_", " ").gsub(/\b('?[a-z])/) { $1.capitalize } %>
46
46
  <%= text_field_tag "project[extended_attributes][#{parameter}]", @project.extended_attributes[parameter.to_s], :class => "text_field #{"error" if errors.any?}" %>
47
47
  <% if errors.any? %>
48
48
  <span class="help-inline">&nbsp;<%= errors.to_sentence %></span>
@@ -58,9 +58,9 @@
58
58
  <hr />
59
59
 
60
60
  <div class="control-group">
61
- <%= f.label :code_climate_repo_token, "Code Climate Repo Token", :class => 'control-label' %>
61
+ <%= f.label :code_climate_repo_token, "Code Climate Repo Token", :class => "control-label" %>
62
62
  <div class="controls">
63
- <%= f.text_field :code_climate_repo_token, :class => 'text_field' %>
63
+ <%= f.text_field :code_climate_repo_token, :class => "text_field" %>
64
64
  </div>
65
65
  </div>
66
66
 
@@ -95,7 +95,7 @@
95
95
  <hr />
96
96
 
97
97
  <div class="control-group">
98
- <%= f.label :roles, "Teammates", :class => 'control-label' %>
98
+ <%= f.label :roles, "Teammates", :class => "control-label" %>
99
99
  <div class="controls changes-nested-editor">
100
100
  <%= f.nested_editor_for :roles do |f| -%>
101
101
  <%= f.select :user_id, [nil] + User.all.map { |user| [user.name, user.id] } %>
@@ -105,16 +105,19 @@
105
105
  </div>
106
106
 
107
107
  <div class="control-group">
108
- <%= f.label :min_passing_verdicts, "Min. Passing Verdicts", :class => 'control-label' %>
108
+ <%= f.label :min_passing_verdicts, "Min. Passing Verdicts", :class => "control-label" %>
109
109
  <div class="controls">
110
- <%= f.text_field :min_passing_verdicts, :class => 'text_field' %>
110
+ <%= f.text_field :min_passing_verdicts, :class => "text_field" %>
111
111
  </div>
112
112
  </div>
113
113
 
114
114
  <div class="form-actions">
115
- <%= f.submit nil, :class => 'btn btn-primary' %>
116
- <%= link_to 'Cancel', projects_path, :class => 'btn' %>
117
- <%= link_to 'Retire', retire_project_path(@project), :method => 'put', :class => 'btn btn-danger' if @project.persisted? && can?(:destroy, @project) %>
115
+ <%= f.submit nil, :class => "btn btn-primary" %>
116
+ <%= link_to "Cancel", projects_path, :class => "btn" %>
117
+
118
+ <% if @project.persisted? && can?(:destroy, @project) %>
119
+ <button class="btn btn-delete btn-danger" id="retire_project_button">Retire</button>
120
+ <% end %>
118
121
  </div>
119
122
  </fieldset>
120
123
  <% end %>
@@ -125,7 +128,7 @@
125
128
  NestedEditorFor.init();
126
129
 
127
130
  function dasherize(string) {
128
- return inflect.dasherize(inflect.underscore(string));
131
+ return inflect.dasherize(inflect.underscore(string)).replace(/\./g, '').replace(/\s+/g, '_');
129
132
  }
130
133
 
131
134
  var $name = $('#project_name'),
@@ -166,6 +169,15 @@
166
169
 
167
170
  $('.project-feature').click(showProjectFeatureParameters);
168
171
  showProjectFeatureParameters();
172
+
173
+ <% if @project.persisted? && can?(:destroy, @project) %>
174
+ $('#retire_project_button').click(function(e) {
175
+ e.preventDefault();
176
+ $.put('<%= retire_project_path(@project) %>')
177
+ .success(function() { window.location = '/projects'; })
178
+ .error(function() { console.log(arguments); });
179
+ });
180
+ <% end %>
169
181
  });
170
182
  </script>
171
183
  <% end %>
@@ -2,7 +2,8 @@
2
2
  <h1 class="project-banner space-below">
3
3
  Projects
4
4
  <%= link_to "New Project", new_project_path, :class => "btn btn-primary" if can?(:create, Project) %>
5
-
5
+ <%= link_to "Add Projects from GitHub", add_github_projects_path, :class => "btn btn-primary" if can?(:create, Project) %>
6
+
6
7
  <%= render partial: "projects/keyboard_shortcuts" %>
7
8
  </h1>
8
9
  <% end %>
@@ -75,9 +76,8 @@
75
76
  </td>
76
77
 
77
78
  <% KeyDependency.all.each do |dependency| %>
78
- <% version = ProjectDependency.new(project, dependency).version %>
79
79
  <td class="project-dependency">
80
- <%= version %>
80
+ <%= project.extended_attributes["key_dependency.#{dependency.slug}"] %>
81
81
  </td>
82
82
  <% end %>
83
83
 
@@ -0,0 +1,67 @@
1
+ <% content_for :title do %>
2
+ <h1 class="project-banner space-below">
3
+ Add Projects from GitHub
4
+ </h1>
5
+ <% end %>
6
+
7
+ <p>Select the repos to create projects for in Houston</p>
8
+
9
+ <%= form_tag do %>
10
+ <ul id="repos">
11
+ <% @repos.each do |repo| %>
12
+ <li class="repo <%= "disabled" if repo[:project] %> <%= "retired" if repo[:project] && repo[:project].retired? %>">
13
+ <% if repo[:project] %>
14
+ <input type="checkbox" checked disabled />
15
+ <% else %>
16
+ <input type="checkbox" name="repos[]" value="<%= repo[:full_name] %>" id="<%= repo[:full_name] %>" />
17
+ <% end %>
18
+
19
+ <label for="<%= repo[:full_name] %>">
20
+ <span class="repo-visibility">
21
+ <% if repo[:private] %>
22
+ <i class="fa fa-lock"></i>
23
+ <% else %>
24
+ <i class="fa fa-unlock"></i>
25
+ <% end %>
26
+ </span>
27
+ <span class="repo-owner">
28
+ <%= repo[:owner] %>
29
+ </span>
30
+ <span class="repo-name">
31
+ <% if repo[:project] %>
32
+ <span class="label <%= repo[:project].color %>">
33
+ <%= repo[:name] %>
34
+ </span>
35
+ <% else %>
36
+ <b><%= repo[:name] %></b>
37
+ <% end %>
38
+ </span>
39
+ </label>
40
+ </li>
41
+ <% end %>
42
+ </ul>
43
+
44
+ <div class="form-actions">
45
+ <button type="submit" class="btn btn-primary">Add</button>
46
+ </div>
47
+ <% end %>
48
+
49
+ <% content_for :javascripts do %>
50
+ <script type="text/javascript">
51
+ $(function() {
52
+ $(':checkbox').click(function(e) {
53
+ var $checkbox = $(e.target);
54
+ $checkbox.closest('.repo').toggleClass('selected', $checkbox.prop('checked'));
55
+ });
56
+
57
+ $('[type="submit"]').click(function(e) {
58
+ e.preventDefault();
59
+ $(e.target)
60
+ .prop('disabled', true)
61
+ .html('<i class="fa fa-spinner fa-spin"></i> Adding Projects...')
62
+ .closest('form')
63
+ .submit();
64
+ });
65
+ });
66
+ </script>
67
+ <% end %>