houston-core 0.7.0 → 0.8.0.pre

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +45 -45
  3. data/app/assets/javascripts/houston/app/models/ticket.coffee +7 -5
  4. data/app/assets/javascripts/houston/app/views/new_ticket_view.coffee +1 -21
  5. data/app/assets/javascripts/houston/core/handlebars_helpers.coffee +0 -6
  6. data/app/assets/stylesheets/houston/application/commit.scss +16 -0
  7. data/app/assets/stylesheets/houston/application/navigation.scss +1 -0
  8. data/app/assets/stylesheets/houston/application/new_ticket_view.scss +0 -4
  9. data/app/assets/stylesheets/houston/application/projects.css.scss +0 -14
  10. data/app/assets/stylesheets/houston/application/release_form.scss +1 -1
  11. data/app/assets/stylesheets/houston/application/teams.scss +68 -0
  12. data/app/assets/templates/tickets/index.hbs +0 -1
  13. data/app/controllers/github/pulls_controller.rb +1 -0
  14. data/app/controllers/project_tickets_controller.rb +1 -5
  15. data/app/controllers/projects_controller.rb +10 -12
  16. data/app/controllers/teams_controller.rb +60 -0
  17. data/app/controllers/users_controller.rb +12 -12
  18. data/app/helpers/commit_helper.rb +3 -5
  19. data/app/helpers/exposure_helper.rb +1 -1
  20. data/app/helpers/ticket_helper.rb +0 -13
  21. data/app/interactors/ticket_report.rb +0 -7
  22. data/app/mailers/project_notification.rb +2 -23
  23. data/app/mailers/view_mailer.rb +0 -1
  24. data/app/models/ability.rb +28 -19
  25. data/app/models/commit.rb +0 -4
  26. data/app/models/project.rb +14 -29
  27. data/app/models/release.rb +0 -18
  28. data/app/models/role.rb +0 -22
  29. data/app/models/team.rb +43 -0
  30. data/app/models/team_user.rb +68 -0
  31. data/app/models/ticket.rb +2 -22
  32. data/app/models/user.rb +21 -30
  33. data/app/views/devise/sessions/new.html.erb +9 -7
  34. data/app/views/layouts/_mobile_navigation.html.erb +5 -3
  35. data/app/views/layouts/_navigation.html.erb +11 -10
  36. data/app/views/layouts/_tester_bar.html.erb +1 -1
  37. data/app/views/layouts/application.html.erb +1 -2
  38. data/app/views/project_tickets/_tickets.html.erb +0 -1
  39. data/app/views/project_tickets/index.xls.erb +0 -35
  40. data/app/views/project_tickets/new.html.erb +1 -2
  41. data/app/views/projects/_form.html.erb +7 -12
  42. data/app/views/projects/index.html.erb +10 -17
  43. data/app/views/releases/_changelog.html.erb +11 -14
  44. data/app/views/teams/_form.html.erb +45 -0
  45. data/app/views/teams/edit.html.erb +8 -0
  46. data/app/views/teams/index.html.erb +62 -0
  47. data/app/views/teams/new.html.erb +7 -0
  48. data/app/views/users/_form.html.erb +2 -11
  49. data/app/views/users/index.html.erb +8 -20
  50. data/app/views/users/new.html.erb +1 -1
  51. data/config/initializers/inflections.rb +7 -6
  52. data/config/routes.rb +19 -17
  53. data/db/migrate/20160812233255_repurpose_users_role.rb +16 -0
  54. data/db/migrate/20160813001242_create_teams.rb +42 -0
  55. data/db/structure.sql +110 -116
  56. data/lib/houston/boot.rb +0 -1
  57. data/lib/houston/boot/configuration.rb +26 -11
  58. data/lib/houston/boot/events.rb +0 -4
  59. data/lib/houston/version.rb +1 -1
  60. data/templates/new-instance/config/abilities.rb +68 -51
  61. data/templates/new-instance/config/conversations/mentions/unfurl_tasks.rb +1 -1
  62. data/templates/new-instance/config/events/deploy/notify_deployer_when_finished.rb +24 -16
  63. data/templates/new-instance/config/events/tests/email_when_completed.rb +11 -0
  64. data/templates/new-instance/config/integrations/ldap.rb +4 -0
  65. data/templates/new-instance/config/main.rb +10 -17
  66. data/templates/new-module/test/fixtures/users.yml +1 -2
  67. data/test/acceptance/creating_a_release_test.rb +1 -1
  68. data/test/factories/project_factory.rb +1 -0
  69. data/test/factories/user_factory.rb +3 -1
  70. data/test/fixtures/teams.yml +2 -0
  71. data/test/fixtures/teams_users.yml +5 -0
  72. data/test/fixtures/users.yml +1 -2
  73. data/test/integration/ci_integration_test.rb +2 -2
  74. data/test/support/config.rb +3 -5
  75. data/test/unit/controllers/deploys_controller_test.rb +11 -5
  76. data/test/unit/controllers/users_controller_test.rb +46 -0
  77. data/test/unit/models/release_test.rb +0 -9
  78. data/test/unit/models/serializer_test.rb +0 -8
  79. data/test/unit/models/ticket_test.rb +0 -32
  80. metadata +21 -24
  81. data/app/assets/javascripts/houston/app/table_row_expander.coffee +0 -52
  82. data/app/assets/javascripts/houston/app/views/problems_view.coffee +0 -114
  83. data/app/assets/stylesheets/houston/application/follow_up.scss +0 -44
  84. data/app/controllers/project_exceptions_controller.rb +0 -36
  85. data/app/controllers/project_pretickets_controller.rb +0 -27
  86. data/app/mailers/deploy_notification.rb +0 -33
  87. data/app/models/antecedent.rb +0 -15
  88. data/app/models/ticket_antecedent.rb +0 -39
  89. data/app/models/value_statement.rb +0 -9
  90. data/app/presenters/problem_presenter.rb +0 -49
  91. data/app/presenters/tester_presenter.rb +0 -15
  92. data/app/views/project_notification/follow_up.html.erb +0 -26
  93. data/app/views/project_pretickets/show.html.erb +0 -49
  94. data/app/views/releases/_antecedents.html.erb +0 -12
  95. data/config/initializers/hard_coded_knowledge.rb +0 -141
  96. data/db/migrate/20130427223925_create_project_quotas.rb +0 -14
  97. data/db/migrate/20140506035755_create_value_statements.rb +0 -9
  98. data/lib/houston/boot/ticket_antecedent_serializer.rb +0 -21
  99. data/test/unit/models/ticket_antecedents_test.rb +0 -38
@@ -4,6 +4,7 @@ class Project < ActiveRecord::Base
4
4
  include FeatureState
5
5
  include Houston::Props
6
6
 
7
+ belongs_to :team
7
8
  has_many :releases, dependent: :destroy
8
9
  has_many :commits, dependent: :destroy, extend: CommitSynchronizer
9
10
  has_many :tickets, dependent: :destroy, extend: TicketSynchronizer
@@ -12,22 +13,9 @@ class Project < ActiveRecord::Base
12
13
  has_many :test_runs, dependent: :destroy
13
14
  has_many :tests, dependent: :destroy
14
15
  has_many :deploys
15
- has_many :roles, -> { joins(:user).merge(User.unretired) }, dependent: :destroy, validate: false
16
- has_many :value_statements, dependent: :destroy
17
16
  has_many :pull_requests, class_name: "Github::PullRequest"
18
17
  belongs_to :head, class_name: "Commit", foreign_key: "head_sha", primary_key: "sha"
19
18
 
20
- Houston.config.project_roles.each do |role|
21
- collection_name = role.downcase.gsub(' ', '_').pluralize
22
- class_eval <<-RUBY
23
- has_many :#{collection_name}, -> { where(Role.arel_table[:name].eq("#{role}")) }, class_name: "User", through: :roles, source: :user
24
- RUBY
25
- end
26
-
27
- accepts_nested_attributes_for :roles, :allow_destroy => true, # <-- !todo: authorized access only
28
- reject_if: proc { |attrs| attrs[:user_id].blank? or attrs[:name].blank? }
29
- accepts_nested_attributes_for :value_statements, :allow_destroy => true
30
-
31
19
  before_validation :generate_default_slug, :set_default_color
32
20
  validates_presence_of :name, :slug, :color
33
21
 
@@ -50,6 +38,8 @@ class Project < ActiveRecord::Base
50
38
  Houston.config.project_colors[color]
51
39
  end
52
40
 
41
+
42
+
53
43
  def environments
54
44
  @environments ||= deploys.environments.map(&:downcase).uniq
55
45
  end
@@ -66,6 +56,8 @@ class Project < ActiveRecord::Base
66
56
  Environment.new(self, environment_name)
67
57
  end
68
58
 
59
+
60
+
69
61
  def extended_attributes
70
62
  raise NotImplementedError, "This feature has been deprecated; use props"
71
63
  end
@@ -82,14 +74,14 @@ class Project < ActiveRecord::Base
82
74
  raise NotImplementedError, "This feature has been deprecated; use props"
83
75
  end
84
76
 
85
- def testers
86
- @testers ||= User.testers
87
- end
77
+
88
78
 
89
79
  def self.[](slug)
90
80
  find_by_slug(slug)
91
81
  end
92
82
 
83
+
84
+
93
85
  def self.with_feature(feature)
94
86
  where ["? = ANY(projects.selected_features)", feature]
95
87
  end
@@ -113,24 +105,17 @@ class Project < ActiveRecord::Base
113
105
  # Teammates
114
106
  # ------------------------------------------------------------------------- #
115
107
 
108
+ delegate *Houston.config.roles.map { |role| role.downcase.gsub(" ", "_").pluralize }, to: :team
109
+
116
110
  def teammates
117
- roles.participants.to_users
111
+ return User.none if team.nil?
112
+ team.users
118
113
  end
119
114
 
120
115
  def followers # <-- redefine followers to be everyone who participates in or follows the project
121
- roles.to_users
122
- end
123
-
124
- def add_teammate(user_or_id, role="Follower")
125
- attributes = {project: self, name: role}
126
- attributes[user_or_id.is_a?(User) ? :user : :user_id] = user_or_id
127
- roles.create!(attributes)
128
- end
129
-
130
- def is_teammate?(user_or_id)
131
- roles.for_user(user_or_id).any?
116
+ puts "DEPRECATED: Project#followers is deprecated; use Project#teammates instead"
117
+ teammates
132
118
  end
133
- alias :teammate? :is_teammate?
134
119
 
135
120
  # ------------------------------------------------------------------------- #
136
121
 
@@ -3,7 +3,6 @@ class Release < ActiveRecord::Base
3
3
  after_create :load_commits!, :if => :can_read_commits?
4
4
  after_create :release_each_ticket!
5
5
  after_create :release_each_task!
6
- after_create :release_each_antecedent!
7
6
  after_create { Houston.observer.fire "release:create", release: self }
8
7
  after_save :update_search_vector, :if => :search_vector_should_change?
9
8
 
@@ -18,8 +17,6 @@ class Release < ActiveRecord::Base
18
17
 
19
18
  default_scope { order("created_at DESC") }
20
19
 
21
- delegate :maintainers, :to => :project
22
-
23
20
  validates_presence_of :user_id
24
21
  validates_uniqueness_of :deploy_id, :allow_nil => true
25
22
  validates_associated :release_changes
@@ -189,15 +186,6 @@ class Release < ActiveRecord::Base
189
186
 
190
187
 
191
188
 
192
- def antecedents
193
- @antecedents ||= (tickets.map(&:antecedents) + commits.map(&:antecedents))
194
- .flatten
195
- .uniq
196
- .sort
197
- end
198
-
199
-
200
-
201
189
  def ignore?
202
190
  !project.show_release_notes_for?(environment_name)
203
191
  end
@@ -242,12 +230,6 @@ private
242
230
  end
243
231
  end
244
232
 
245
- def release_each_antecedent!
246
- antecedents.each do |antecedent|
247
- antecedent.released!(self)
248
- end
249
- end
250
-
251
233
  # http://www.postgresql.org/docs/9.1/static/textsearch-controls.html#TEXTSEARCH-HEADLINE
252
234
  def self.ts_headline(column, query, options={})
253
235
  column = arel_table[column] if column.is_a?(Symbol)
@@ -5,29 +5,8 @@ class Role < ActiveRecord::Base
5
5
 
6
6
  validates :user_id, presence: true
7
7
  validates :project, presence: true
8
- validates :name, presence: true, inclusion: {in: Houston.config.project_roles, message: "\"%{value}\" is unknown. It must be #{Houston.config.project_roles.to_sentence(last_word_connector: ", or ")}"}
9
-
10
-
11
- Houston.config.project_roles.each do |role|
12
- method_name = role.downcase.gsub(' ', '_')
13
- class_eval <<-RUBY
14
- def #{method_name}?
15
- name == "#{role}"
16
- end
17
-
18
- def self.#{method_name.pluralize}
19
- where(name: "#{role}")
20
- end
21
- RUBY
22
- end
23
-
24
8
 
25
9
  class << self
26
-
27
- def participants
28
- where arel_table[:name].not_eq("Follower")
29
- end
30
-
31
10
  def to_users
32
11
  User.where(id: all.select(:user_id))
33
12
  end
@@ -49,7 +28,6 @@ class Role < ActiveRecord::Base
49
28
  def any?
50
29
  count > 0
51
30
  end
52
-
53
31
  end
54
32
 
55
33
  end
@@ -0,0 +1,43 @@
1
+ class Team < ActiveRecord::Base
2
+ include Houston::Props
3
+
4
+ has_and_belongs_to_many :users, readonly: true
5
+ has_many :projects
6
+ has_many :roles, -> { joins(:user).merge(User.unretired) }, class_name: "TeamUser", dependent: :destroy, validate: false
7
+
8
+ default_scope -> { order(name: :asc) }
9
+
10
+ accepts_nested_attributes_for :roles, :allow_destroy => true, # <-- !todo: authorized access only
11
+ reject_if: proc { |attrs| attrs[:user_id].blank? }
12
+
13
+
14
+
15
+ Houston.config.roles.each do |role|
16
+ collection_name = role.downcase.gsub(' ', '_').pluralize
17
+ class_eval <<-RUBY
18
+ has_many :#{collection_name}, -> { where(Role.arel_table[:name].eq("#{role}")) }, class_name: "User", through: :roles, source: :user
19
+ RUBY
20
+ end
21
+
22
+
23
+
24
+ class << self
25
+ def projects
26
+ Project.where(team_id: all.select(:id))
27
+ end
28
+
29
+ def project_ids
30
+ projects.ids
31
+ end
32
+ end
33
+
34
+
35
+
36
+ def add_teammate(user_or_id, *desired_roles)
37
+ teammate = roles.find_or_initialize_by((user_or_id.is_a?(User) ? :user : :user_id) => user_or_id)
38
+ teammate.team = self
39
+ teammate.roles = teammate.roles | desired_roles
40
+ teammate.save!
41
+ end
42
+
43
+ end
@@ -0,0 +1,68 @@
1
+ class TeamUser < ActiveRecord::Base
2
+
3
+ belongs_to :user
4
+ belongs_to :team
5
+ has_many :projects, through: :team
6
+
7
+ validates :user_id, presence: true
8
+ validates :team, presence: true
9
+ validate :roles_should_all_be_defined_by_config
10
+
11
+
12
+
13
+ Houston.config.roles.each do |role|
14
+ method_name = role.downcase.gsub(' ', '_')
15
+ class_eval <<-RUBY
16
+ def #{method_name}?
17
+ name == "#{role}"
18
+ end
19
+
20
+ def self.#{method_name.pluralize}
21
+ where(name: "#{role}")
22
+ end
23
+ RUBY
24
+ end
25
+
26
+
27
+
28
+ class << self
29
+ def to_users
30
+ User.where(id: all.select(:user_id))
31
+ end
32
+
33
+ def to_projects
34
+ Project.where(team_id: all.select(:team_id))
35
+ end
36
+
37
+ def with_role(role)
38
+ where ["? = ANY(roles)", role]
39
+ end
40
+
41
+ def for_user(user_or_id)
42
+ user_id = user_or_id.is_a?(User) ? user_or_id.id : user_or_id
43
+ where user_id: user_id
44
+ end
45
+
46
+ def any?
47
+ count > 0
48
+ end
49
+ end
50
+
51
+
52
+
53
+ def roles
54
+ super || []
55
+ end
56
+
57
+
58
+
59
+ private
60
+
61
+ def roles_should_all_be_defined_by_config
62
+ roles.each do |role|
63
+ next if Houston.config.roles.member?(role)
64
+ errors.add :roles, "includes #{role.inspect} which is not a defined role (#{Houston.config.roles.join(", ")})"
65
+ end
66
+ end
67
+
68
+ end
@@ -4,7 +4,7 @@ class Ticket < ActiveRecord::Base
4
4
  self.inheritance_column = nil
5
5
 
6
6
  versioned only: [:summary, :description, :type,
7
- :tags, :antecedents, :prerequisites,
7
+ :tags, :prerequisites,
8
8
  :closed_at, :resolution, :milestone_id]
9
9
 
10
10
  belongs_to :project
@@ -29,13 +29,11 @@ class Ticket < ActiveRecord::Base
29
29
  before_save :parse_ticket_description, if: :description_changed?
30
30
  before_save :find_reporter, if: :find_reporter?
31
31
  after_save :propagate_milestone_change, if: :milestone_id_changed?
32
- after_save :resolve_antecedents!, if: :just_resolved?
33
- after_save :close_antecedents!, if: :just_closed?
34
32
  after_save :updated_milestone_attributes
35
33
 
36
34
  attr_readonly :number, :project_id
37
35
 
38
- delegate :testers, :maintainers, :ticket_tracker, to: :project
36
+ delegate :ticket_tracker, to: :project
39
37
  delegate :nosync?, to: "self.class"
40
38
 
41
39
 
@@ -200,16 +198,6 @@ class Ticket < ActiveRecord::Base
200
198
 
201
199
 
202
200
 
203
- def antecedents
204
- (super || []).map { |s| TicketAntecedent.from_s(self, s) }
205
- end
206
-
207
- def antecedents=(antecedents)
208
- super Array(antecedents).map(&:to_s)
209
- end
210
-
211
-
212
-
213
201
  def tags
214
202
  (super || []).map(&TicketTag.method(:from_s))
215
203
  end
@@ -309,14 +297,6 @@ private
309
297
  closed_at_changed? && closed_at_was.nil?
310
298
  end
311
299
 
312
- def resolve_antecedents!
313
- antecedents.each(&:resolve!)
314
- end
315
-
316
- def close_antecedents!
317
- antecedents.each(&:close!)
318
- end
319
-
320
300
  def remote_ticket
321
301
  @remote_ticket ||= ticket_tracker.find_ticket_by_number(number)
322
302
  end
@@ -2,8 +2,11 @@ class User < ActiveRecord::Base
2
2
  include Retirement
3
3
  include Houston::Props
4
4
 
5
- has_many :roles, :dependent => :destroy
6
- has_many :credentials, :class_name => "UserCredentials", :dependent => :destroy
5
+ ROLES = %w{Owner Admin Member}.freeze
6
+
7
+ has_many :roles, class_name: "TeamUser", dependent: :destroy
8
+ has_and_belongs_to_many :teams
9
+ has_many :credentials, :class_name => "UserCredentials", dependent: :destroy
7
10
  has_many :tickets, foreign_key: "reporter_id"
8
11
  has_and_belongs_to_many :commits
9
12
  belongs_to :current_project, class_name: "Project"
@@ -12,27 +15,14 @@ class User < ActiveRecord::Base
12
15
 
13
16
  default_scope { order("last_name, first_name") }
14
17
 
15
- default_value_for :role, Houston.config.default_role
16
-
17
18
  validates :first_name, :last_name, :email, :presence => true, :length => {:minimum => 2}
18
- validates :role, presence: true, inclusion: {in: Houston.config.roles, message: "%{value} is not one of the configured roles (#{Houston.config.roles.join(", ")})"}
19
+ validates :role, presence: true, inclusion: {in: ROLES, message: "%{value} is not one of the configured roles (#{ROLES.join(", ")})"}
19
20
  validate :all_email_addresses_must_be_unique
20
21
 
21
22
 
22
23
 
23
- Houston.config.project_roles.each do |role|
24
- method_name = role.downcase.gsub(' ', '_')
25
- collection_name = method_name.pluralize
26
-
27
- class_eval <<-RUBY
28
- def self.#{collection_name}
29
- Role.#{collection_name}.to_users
30
- end
31
- RUBY
32
- end
33
-
34
- Houston.config.roles.each do |role|
35
- method_name = role.downcase.gsub(' ', '_')
24
+ ROLES.each do |role|
25
+ method_name = role.downcase.gsub(" ", "_")
36
26
  collection_name = method_name.pluralize
37
27
 
38
28
  class_eval <<-RUBY
@@ -47,15 +37,18 @@ class User < ActiveRecord::Base
47
37
  end
48
38
 
49
39
 
50
- def self.administrators
51
- where(administrator: true)
40
+ def developer?
41
+ puts "DEPRECATED: User#developer? will be removed"
42
+ legacy_role == "Developer"
52
43
  end
53
44
 
54
-
55
- def self.participants
56
- Role.participants.to_users
45
+ def tester?
46
+ puts "DEPRECATED: User#tester? will be removed"
47
+ legacy_role == "Tester"
57
48
  end
58
49
 
50
+
51
+
59
52
  def self.with_primary_email(email)
60
53
  email = email.downcase if email
61
54
  where(email: email)
@@ -72,8 +65,6 @@ class User < ActiveRecord::Base
72
65
  with_email_address(email_address).first
73
66
  end
74
67
 
75
-
76
-
77
68
  def email=(value)
78
69
  value = value.downcase if value
79
70
  super(value)
@@ -99,11 +90,11 @@ class User < ActiveRecord::Base
99
90
  end
100
91
 
101
92
  def follows?(project)
102
- roles.to_projects.member?(project)
93
+ Role.where(user: self).to_projects.member?(project)
103
94
  end
104
95
 
105
96
  def followed_projects
106
- roles.to_projects.unretired
97
+ Role.where(user: self).to_projects.unretired
107
98
  end
108
99
 
109
100
  def view_options
@@ -154,7 +145,7 @@ class User < ActiveRecord::Base
154
145
  # ------------------------------------------------------------------------- #
155
146
 
156
147
  def self.find_ldap_entry(ldap_connection, auth_key_value)
157
- filter = Net::LDAP::Filter.eq(Houston::TMI::FIELD_USED_FOR_LDAP_LOGIN, auth_key_value)
148
+ filter = Net::LDAP::Filter.eq(Houston.config.authentication_strategy_configuration[:field], auth_key_value)
158
149
  ldap_connection.ldap.search(filter: filter).first
159
150
  end
160
151
 
@@ -162,7 +153,7 @@ class User < ActiveRecord::Base
162
153
  email = entry.mail.first.downcase
163
154
  user = where(email: email).first
164
155
  if user && user.username.nil?
165
- user.update_column :username, entry[Houston::TMI::FIELD_USED_FOR_LDAP_LOGIN][0].to_s
156
+ user.update_column :username, entry[Houston.config.authentication_strategy_configuration[:field]][0].to_s
166
157
  end
167
158
  user
168
159
  end
@@ -170,7 +161,7 @@ class User < ActiveRecord::Base
170
161
  def self.create_from_ldap_entry(attributes, entry)
171
162
  create!(
172
163
  email: entry.mail.first.downcase,
173
- username: entry[Houston::TMI::FIELD_USED_FOR_LDAP_LOGIN][0].to_s,
164
+ username: entry[Houston.config.authentication_strategy_configuration[:field]][0].to_s,
174
165
  password: attributes[:password],
175
166
  first_name: entry.givenname.first,
176
167
  last_name: entry.sn.first )