Merge branch 'release/4.3' into dev

pull/3302/head
Oliver Günther 9 years ago
commit 108160ed88
  1. 7
      app/controllers/repositories_controller.rb
  2. 2
      app/controllers/work_packages/bulk_controller.rb
  3. 2
      app/controllers/work_packages_controller.rb
  4. 5
      app/helpers/repositories_helper.rb
  5. 5
      app/models/comment_observer.rb
  6. 10
      app/models/journal_manager.rb
  7. 87
      app/models/journal_notification_mailer.rb
  8. 67
      app/models/journal_observer.rb
  9. 3
      app/models/message_observer.rb
  10. 3
      app/models/news_observer.rb
  11. 4
      app/models/project.rb
  12. 35
      app/models/repository.rb
  13. 6
      app/models/repository/git.rb
  14. 6
      app/models/repository/subversion.rb
  15. 56
      app/models/wiki_content.rb
  16. 10
      app/models/wiki_content_observer.rb
  17. 5
      app/models/work_package.rb
  18. 2
      app/services/create_work_package_service.rb
  19. 2
      app/services/update_work_package_service.rb
  20. 2
      app/views/repositories/settings/_vendor_form.html.erb
  21. 2
      app/workers/scm/create_repository_job.rb
  22. 7
      config/application.rb
  23. 22
      config/configuration.yml.example
  24. 10
      config/initializers/subscribe_listeners.rb
  25. 2
      config/locales/en.yml
  26. 31
      db/migrate/20150716163704_remove_filesystem_repositories.rb
  27. 4
      db/seeds/development.rb
  28. 8
      doc/apiv3-documentation.apib
  29. 26
      frontend/tests/integration/specs/work-packages/work-packages-spec.js
  30. 77
      lib/api/v3/repositories/revision_representer.rb
  31. 61
      lib/api/v3/repositories/revisions_api.rb
  32. 52
      lib/api/v3/repositories/revisions_by_work_package_api.rb
  33. 37
      lib/api/v3/repositories/revisions_collection_representer.rb
  34. 1
      lib/api/v3/root.rb
  35. 8
      lib/api/v3/utilities/path_helper.rb
  36. 6
      lib/api/v3/work_packages/work_package_representer.rb
  37. 1
      lib/api/v3/work_packages/work_packages_api.rb
  38. 6
      lib/open_project/scm/adapters/base.rb
  39. 2
      lib/open_project/scm/adapters/git.rb
  40. 23
      lib/open_project/scm/adapters/local_client.rb
  41. 4
      lib/open_project/scm/adapters/subversion.rb
  42. 51
      lib/open_project/scm/manageable_repository.rb
  43. 5
      lib/plugins/acts_as_event/lib/acts_as_event.rb
  44. 3
      lib/plugins/acts_as_journalized/lib/redmine/acts/journalized/deprecated.rb
  45. 11
      lib/plugins/acts_as_journalized/lib/redmine/acts/journalized/save_hooks.rb
  46. 6
      lib/plugins/acts_as_watchable/lib/acts_as_watchable.rb
  47. 4
      spec/features/boards/message_spec.rb
  48. 2
      spec/features/repositories/create_repository_spec.rb
  49. 14
      spec/features/work_packages/details/activity_comments_spec.rb
  50. 3
      spec/features/work_packages/work_packages_page.rb
  51. 4
      spec/legacy/unit/comment_spec.rb
  52. 133
      spec/legacy/unit/journal_observer_spec.rb
  53. 2
      spec/legacy/unit/journal_spec.rb
  54. 6
      spec/legacy/unit/user_spec.rb
  55. 4
      spec/legacy/unit/watcher_spec.rb
  56. 127
      spec/lib/api/v3/repositories/revision_representer_spec.rb
  57. 18
      spec/lib/api/v3/utilities/path_helper_spec.rb
  58. 20
      spec/lib/api/v3/work_packages/work_package_representer_spec.rb
  59. 2
      spec/lib/open_project/scm/adapters/git_adapter_spec.rb
  60. 2
      spec/lib/open_project/scm/adapters/subversion_adapter_spec.rb
  61. 156
      spec/models/journal_notification_mailer_spec.rb
  62. 26
      spec/models/repository/git_spec.rb
  63. 22
      spec/models/repository/subversion_spec.rb
  64. 21
      spec/models/work_package/work_package_action_mailer_spec.rb
  65. 4
      spec/models/work_package/work_package_copy_spec.rb
  66. 4
      spec/models/work_package_spec.rb
  67. 98
      spec/requests/api/v3/repositories/revisions_by_work_package_resource_spec.rb
  68. 97
      spec/requests/api/v3/repositories/revisions_resource_spec.rb
  69. 2
      spec/support/shared/acts_as_watchable.rb

@ -109,13 +109,8 @@ class RepositoriesController < ApplicationController
end
def destroy
if @project.repository.manageable?
flash[:notice] = I18n.t('repositories.delete_sucessful')
else
flash[:warning] = I18n.t('repositories.errors.unlink_failed_unmanageable')
end
@project.repository.destroy
flash[:notice] = I18n.t('repositories.delete_sucessful')
redirect_to settings_repository_tab_path
end

@ -61,7 +61,7 @@ class WorkPackages::BulkController < ApplicationController
work_package.assign_attributes attributes
call_hook(:controller_work_package_bulk_before_save, params: params, work_package: work_package)
JournalObserver.instance.send_notification = params[:send_notification] == '0' ? false : true
JournalManager.send_notification = params[:send_notification] == '0' ? false : true
unless work_package.save
unsaved_work_package_ids << work_package.id
end

@ -133,7 +133,7 @@ class WorkPackagesController < ApplicationController
def create
call_hook(:controller_work_package_new_before_save, params: params, work_package: work_package)
WorkPackageObserver.instance.send_notification = send_notifications?
JournalManager.send_notification = send_notifications?
work_package.attach_files(params[:attachments])

@ -188,10 +188,15 @@ module RepositoriesHelper
scms = OpenProject::Scm::Manager.enabled
vendor = repository.nil? ? nil : repository.vendor
## Set selected vendor
if vendor && !repository.new_record?
scms[vendor] = vendor
end
# Remove repositories that were configured to have no
# available types left.
scms.reject! { |_, klass| klass.available_types.empty? }
scms = [default_selected_option] + scms.keys
options_for_select(scms, vendor)
end

@ -29,13 +29,12 @@
class CommentObserver < ActiveRecord::Observer
def after_create(comment)
return unless Notifier.notify?(:news_comment_added)
return unless Setting.notified_events.include?('news_comment_added')
if comment.commented.is_a?(News)
news = comment.commented
recipients = news.recipients + news.watcher_recipients
users = User.find_all_by_mails(recipients)
users.each do |user|
recipients.uniq.each do |user|
UserMailer.news_comment_added(user, comment, User.current).deliver
end
end

@ -28,6 +28,12 @@
#++
class JournalManager
class << self
attr_accessor :send_notification
end
self.send_notification = true
def self.is_journalized?(obj)
not obj.nil? and obj.respond_to? :journals
end
@ -268,4 +274,8 @@ class JournalManager
journal.customizable_journals.build custom_field_id: cv.custom_field_id, value: cv.value
end
end
def self.reset_notification
@send_notification = true
end
end

@ -0,0 +1,87 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class JournalNotificationMailer
class << self
def distinguish_journals(journal, send_notification)
if send_notification
if journal.journable_type == 'WorkPackage' && journal.initial?
handle_work_package_create(journal.journable)
elsif journal.journable_type == 'WorkPackage'
handle_work_package_update(journal)
end
end
end
def handle_work_package_create(work_package)
if Setting.notified_events.include?('work_package_added')
notification_receivers(work_package).uniq.each do |user|
job = DeliverWorkPackageCreatedJob.new(user.id, work_package.id, User.current.id)
Delayed::Job.enqueue job
end
end
end
def handle_work_package_update(journal)
if send_update_notification?(journal)
work_package = journal.journable
notification_receivers(work_package).uniq.each do |user|
job = DeliverWorkPackageUpdatedJob.new(user.id, journal.id, User.current.id)
Delayed::Job.enqueue job
end
end
end
def send_update_notification?(journal)
Setting.notified_events.include?('work_package_updated') ||
notify_for_notes?(journal) ||
notify_for_status?(journal) ||
notify_for_priority(journal)
end
def notify_for_notes?(journal)
Setting.notified_events.include?('work_package_note_added') && journal.notes.present?
end
def notify_for_status?(journal)
Setting.notified_events.include?('status_updated') &&
journal.changed_data.has_key?(:status_id)
end
def notify_for_priority(journal)
Setting.notified_events.include?('work_package_priority_updated') &&
journal.changed_data.has_key?(:priority_id)
end
def notification_receivers(work_package)
work_package.recipients + work_package.watcher_recipients
end
end
end

@ -1,67 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class JournalObserver < ActiveRecord::Observer
attr_accessor :send_notification
def after_create(journal)
if journal.journable_type == 'WorkPackage' and !journal.initial? and send_notification
after_create_issue_journal(journal)
end
clear_notification
end
def after_create_issue_journal(journal)
if Setting.notified_events.include?('work_package_updated') ||
(Setting.notified_events.include?('work_package_note_added') && journal.notes.present?) ||
(Setting.notified_events.include?('status_updated') && journal.changed_data.has_key?(:status_id)) ||
(Setting.notified_events.include?('work_package_priority_updated') && journal.changed_data.has_key?(:priority_id))
issue = journal.journable
recipients = issue.recipients + issue.watcher_recipients
users = User.find_all_by_mails(recipients.uniq)
users.each do |user|
job = DeliverWorkPackageUpdatedJob.new(user.id, journal.id, User.current.id)
Delayed::Job.enqueue job
end
end
end
# Wrap send_notification so it defaults to true, when it's nil
def send_notification
return true if @send_notification.nil?
@send_notification
end
private
# Need to clear the notification setting after each usage otherwise it might be cached
def clear_notification
@send_notification = true
end
end

@ -33,8 +33,7 @@ class MessageObserver < ActiveRecord::Observer
recipients = message.recipients
recipients += message.root.watcher_recipients
recipients += message.board.watcher_recipients
users = User.find_all_by_mails(recipients.uniq)
users.each do |user|
recipients.uniq.each do |user|
UserMailer.message_posted(user, message, User.current).deliver
end
end

@ -30,8 +30,7 @@
class NewsObserver < ActiveRecord::Observer
def after_create(news)
if Setting.notified_events.include?('news_added')
users = User.find_all_by_mails(news.recipients)
users.each do |user|
news.recipients.uniq.each do |user|
UserMailer.news_added(user, news, User.current).deliver
end
end

@ -616,9 +616,9 @@ class Project < ActiveRecord::Base
possible_responsible_members.map(&:principal).compact.sort
end
# Returns the mail adresses of users that should be always notified on project events
# Returns users that should be always notified on project events
def recipients
notified_users.map(&:mail)
notified_users
end
# Returns the users that should be notified on project events

@ -87,12 +87,22 @@ class Repository < ActiveRecord::Base
@scm
end
def vendor
self.class.vendor
def self.scm_config
scm_adapter_class.config
end
def supported_types
[]
def self.available_types
supported_types - disabled_types
end
##
# Retrieves the :disabled_types setting from `configuration.yml
def self.disabled_types
scm_config[:disabled_types] || []
end
def vendor
self.class.vendor
end
def supports_cat?
@ -282,7 +292,8 @@ class Repository < ActiveRecord::Base
repository = klass.new(args)
repository.attributes = args
repository.project = project
repository.scm_type = type
set_verified_type!(repository, type) unless type.nil?
repository.configure(type, args)
@ -304,6 +315,20 @@ class Repository < ActiveRecord::Base
end
end
##
# Verifies that the chosen scm type can be selected
def self.set_verified_type!(repository, type)
if repository.class.available_types.include? type
repository.scm_type = type
else
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.errors.disabled_or_unknown_type',
type: type,
vendor: repository.vendor)
)
end
end
##
# Allow global permittible params. May be overridden by plugins
def self.permitted_params(params)

@ -38,7 +38,7 @@ class Repository::Git < Repository
end
def configure(scm_type, _args)
if scm_type == MANAGED_TYPE
if scm_type == self.class.managed_type
unless manageable?
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.managed.error_not_manageable')
@ -54,9 +54,9 @@ class Repository::Git < Repository
super(params)
end
def supported_types
def self.supported_types
types = [:local]
types << MANAGED_TYPE if manageable?
types << managed_type if manageable?
types
end

@ -39,7 +39,7 @@ class Repository::Subversion < Repository
end
def configure(scm_type, _args)
if scm_type == MANAGED_TYPE
if scm_type == self.class.managed_type
unless manageable?
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.managed.error_not_manageable')
@ -55,9 +55,9 @@ class Repository::Subversion < Repository
super(params).merge(params.permit(:login, :password))
end
def supported_types
def self.supported_types
types = [:existing]
types << MANAGED_TYPE if manageable?
types << managed_type if manageable?
types
end

@ -68,8 +68,7 @@ class WikiContent < ActiveRecord::Base
# Returns the mail adresses of users that should be notified
def recipients
notified = project.notified_users
notified.reject! { |user| !visible?(user) }
notified.map(&:mail)
notified.select { |user| visible?(user) }
end
# FIXME: Deprecate
@ -87,57 +86,4 @@ class WikiContent < ActiveRecord::Base
def comments_to_journal_notes
add_journal author, comments
end
# FIXME: This is for backwards compatibility only. Remove once we decide it is not needed anymore
# WikiContentJournal.class_eval do
# attr_protected :data
# after_save :compress_version_text
#
# # Wiki Content might be large and the data should possibly be compressed
# def compress_version_text
# self.text = changed_data["text"].last if changed_data["text"]
# self.text ||= self.journaled.text
# end
#
# def text=(plain)
# case Setting.wiki_compression
# when "gzip"
# begin
# text_hash :text => Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION), :compression => Setting.wiki_compression
# rescue
# text_hash :text => plain, :compression => ''
# end
# else
# text_hash :text => plain, :compression => ''
# end
# plain
# end
#
# def text_hash(hash)
# changed_data.delete("text")
# changed_data["data"] = hash[:text]
# changed_data["compression"] = hash[:compression]
# update_attribute(:changed_data, changed_data)
# end
#
# def text
# @text ||= case changed_data["compression"]
# when "gzip"
# Zlib::Inflate.inflate(changed_data["data"])
# else
# # uncompressed data
# changed_data["data"]
# end
# end
#
# # Returns the previous version or nil
# def previous
# @previous ||= journaled.journals.at(version - 1)
# end
#
# # FIXME: Deprecate
# def versioned
# journaled
# end
# end
end

@ -31,8 +31,7 @@ class WikiContentObserver < ActiveRecord::Observer
def after_create(wiki_content)
if Setting.notified_events.include?('wiki_content_added')
recipients = wiki_content.recipients + wiki_content.page.wiki.watcher_recipients
users = User.find_all_by_mails(recipients.uniq)
users.each do |user|
recipients.uniq.each do |user|
UserMailer.wiki_content_added(user, wiki_content, User.current).deliver
end
end
@ -40,9 +39,10 @@ class WikiContentObserver < ActiveRecord::Observer
def after_update(wiki_content)
if wiki_content.text_changed? && Setting.notified_events.include?('wiki_content_updated')
recipients = wiki_content.recipients + wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients
users = User.find_all_by_mails(recipients.uniq)
users.each do |user|
recipients = wiki_content.recipients
recipients += wiki_content.page.wiki.watcher_recipients
recipients += wiki_content.page.watcher_recipients
recipients.uniq.each do |user|
UserMailer.wiki_content_updated(user, wiki_content, User.current).deliver
end
end

@ -490,7 +490,7 @@ class WorkPackage < ActiveRecord::Base
end
# >>> issues.rb >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Returns the mail addresses of users that should be notified
# Returns users that should be notified
def recipients
notified = project.notified_users
# Author and assignee are always notified unless they have been
@ -505,8 +505,7 @@ class WorkPackage < ActiveRecord::Base
end
notified.uniq!
# Remove users that can not view the issue
notified.reject! { |user| !visible?(user) }
notified.map(&:mail)
notified.select { |user| visible?(user) }
end
def done_ratio

@ -34,7 +34,7 @@ class CreateWorkPackageService
self.user = user
self.project = project
WorkPackageObserver.instance.send_notification = send_notifications
JournalManager.send_notification = send_notifications
end
def create

@ -35,7 +35,7 @@ class UpdateWorkPackageService
self.work_package = work_package
self.permitted_params = permitted_params
JournalObserver.instance.send_notification = send_notifications
JournalManager.send_notification = send_notifications
end
def update

@ -27,7 +27,7 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<% if repository.new_record? %>
<% scm_types = repository.supported_types %>
<% scm_types = repository.class.available_types %>
<div class="attributes-group -toggleable" data-switch="scm_type">
<% scm_types.each do |type|%>
<%= render partial: "/repositories/settings/vendor_attribute_groups",

@ -78,7 +78,7 @@ class Scm::CreateRepositoryJob
end
def config
@config ||= repository.scm.config
@config ||= repository.class.scm_config
end
def repository

@ -94,9 +94,10 @@ module OpenProject
# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
config.active_record.observers = :journal_observer, :message_observer,
:news_observer, :wiki_content_observer,
:comment_observer, :work_package_observer
config.active_record.observers = :message_observer,
:news_observer,
:wiki_content_observer,
:comment_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.

@ -221,17 +221,33 @@ default:
# Enable managed repositories for this vendor. This allows OpenProject to
# take control over the given path to create and delete repositories directly
# when created in the frontend.
# NOTE: Disabling :managed repositories using disabled_types takes precedence over this setting.
# disabled_types:
# Disable specific repository types for this particular vendor. This allows
# to restrict the available choices a project administrator has for creating repositories
# See the example below for available types
#
# Available types for git:
# - :local (Local repositories, registered using a local path)
# - :managed (Managed repositores, available IF :manages path is set below)
# Available types for subversion:
# - :existing (Existing subversion repositories by URL - local using file:/// or remote
# using one of the supported URL schemes (e.g., https://, svn+ssh:// )
# - :managed (Managed repositores, available IF :manages path is set below)
#
# Examplary configuration
#
# scm:
# Git:
# client_command: /usr/local/bin/git
# manages: /tmp/git
# disabled_types:
# - :local
# manages: /opt/repositories/git
# Subversion:
# # Use command below to override the default svn command taken from path.
# client_command: /usr/local/bin/svn
# manages: /tmp/svn
# disabled_types:
# - :existing
# manages: /opt/repositories/svn
# Key used to encrypt sensitive data in the database (SCM and LDAP passwords).
# If you don't want to enable data encryption, just leave it blank.

@ -27,12 +27,6 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
module Notifier
def self.notify?(event)
notified_events.include?(event.to_s)
end
def self.notified_events
Setting.notified_events.to_a
end
OpenProject::Notifications.subscribe('journal_created') do |payload|
JournalNotificationMailer.distinguish_journals(payload[:journal], payload[:send_notification])
end

@ -1267,8 +1267,8 @@ en:
path_permission_failed: "An error occurred trying to create the following path: %{path}. Please ensure that OpenProject may write to that folder."
unauthorized: "You're not authorized to access the repository or the credentials are invalid."
unavailable: "The repository is unavailable."
unlink_failed_unmanageable: "The repository was deleted, but its directory could not be removed, because it is no longer manageable by OpenProject."
exception_title: "Cannot access the repository: %{message}"
disabled_or_unknown_type: "The selected type %{type} is disabled or no longer available for the SCM vendor %{vendor}."
disabled_or_unknown_vendor: "The SCM vendor %{vendor} is disabled or no longer available."
git:
instructions:

@ -30,12 +30,31 @@
# Removes all remaining Repository::Filesystem entries.
#
class RemoveFilesystemRepositories < ActiveRecord::Migration
include Migration::Utils
def up
# Circumvent Repository.where(...) since this tries to load
# the Repository::Filesystem constant and fails.
ActiveRecord::Base.connection.execute <<-SQL
DELETE FROM repositories
WHERE type = #{quote_value("Repository::Filesystem")}
SQL
ActiveRecord::Base.transaction do
# Delete any changesets belonging to filesystem repositories
ActiveRecord::Base.connection.execute <<-SQL
DELETE FROM changesets
WHERE repository_id IN (
SELECT id
FROM repositories
WHERE type = #{filesystem_type}
)
SQL
# Circumvent Repository.where(...) since this tries to load
# the Repository::Filesystem constant and fails.
ActiveRecord::Base.connection.execute <<-SQL
DELETE FROM repositories
WHERE type = #{filesystem_type}
SQL
end
end
def filesystem_type
quote_value('Repository::Filesystem')
end
end

@ -107,6 +107,10 @@ time_entry_activities = []
time_entry_activities << time_entry_activity
end
repository = Repository::Subversion.create!(project: project,
url: 'file:///tmp/foo/bar.svn',
scm_type: 'existing')
print 'Creating objects for...'
user_count.times do |count|
login = "#{Faker::Name.first_name}#{rand(10000)}"

@ -4108,7 +4108,7 @@ Gets a list of revisions that are linked to this work package, e.g., because it
**Required permission:** view changesets of the project this work package is contained in
*Note that you will only receive this error, if you are at least allowed to see the corresponding project.*
*Note that you will only receive this error, if you are at least allowed to see the corresponding work package.*
+ Body
@ -4120,16 +4120,16 @@ Gets a list of revisions that are linked to this work package, e.g., because it
+ Response 404 (application/hal+json)
Returned if the project does not exist or the client does not have sufficient permissions to see it.
Returned if the work package does not exist or the client does not have sufficient permissions to see it.
**Required permission:** view project
**Required permission:** view work package
+ Body
{
"_type": "Error",
"errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
"message": "The specified project does not exist."
"message": "The specified work project does not exist."
}

@ -38,26 +38,20 @@ describe('OpenProject', function() {
});
it('should show work packages title', function() {
page.get();
expect(page.getSelectableTitle().getText()).to.eventually.equal('Work packages');
});
it('should show work packages', function() {
page.get();
page.getTableHeaders().map(function(heading) {
return heading.getText();
}).then(function(headingTexts) {
var expected = ['',
'ID',
'TYPE',
'STATUS',
'SUBJECT',
'ASSIGNEE'];
for (var i = 0; i < expected.length; i++) {
expect(headingTexts[i]).to.equal(expected[i]);
var expected = ['',
'ID',
'TYPE',
'STATUS',
'SUBJECT',
'ASSIGNEE'];
page.getTableHeaders().then(function(headings) {
for (var i = 0; i < headings.length; i++) {
expect(headings[i].getText()).to.eventually.equal(expected[i]);
}
});
});

@ -0,0 +1,77 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Repositories
class RevisionRepresenter < ::API::Decorators::Single
include API::V3::Utilities
self_link path: :revision,
title_getter: -> (*) { nil }
link :project do
{
href: api_v3_paths.project(represented.project.id),
title: represented.project.name
}
end
link :author do
{
href: api_v3_paths.user(represented.user.id),
title: represented.user.name
} unless represented.user.nil?
end
property :id
property :identifier
property :author, as: :authorName
property :message,
exec_context: :decorator,
getter: -> (*) {
::API::Decorators::Formattable.new(represented.comments,
object: represented,
format: 'plain')
},
render_nil: true
property :created_at,
exec_context: :decorator,
getter: -> (*) {
datetime_formatter.format_datetime(represented.committed_on)
}
def _type
'Revision'
end
end
end
end
end

@ -0,0 +1,61 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Repositories
class RevisionsAPI < ::API::OpenProjectAPI
resources :revisions do
params do
requires :id, desc: 'Revision id'
end
route_param :id do
helpers do
attr_reader :revision
def revision_representer
RevisionRepresenter.new(revision)
end
end
before do
@revision = Changeset.find(params[:id])
authorize(:view_changesets, context: revision.project) do
raise API::Errors::NotFound.new
end
end
get do
revision_representer
end
end
end
end
end
end
end

@ -0,0 +1,52 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'api/v3/repositories/revisions_collection_representer'
module API
module V3
module Repositories
class RevisionsByWorkPackageAPI < ::API::OpenProjectAPI
resources :revisions do
before do
authorize(:view_changesets, context: work_package.project)
end
get do
self_path = api_v3_paths.work_package_revisions(work_package.id)
revisions = work_package.changesets
RevisionsCollectionRepresenter.new(revisions,
revisions.count,
self_path)
end
end
end
end
end
end

@ -27,39 +27,12 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
class WorkPackageObserver < ActiveRecord::Observer
attr_accessor :send_notification
def after_create(work_package)
if send_notification
recipients = work_package.recipients + work_package.watcher_recipients
users = User.find_all_by_mails(recipients.uniq)
users.each do |user|
notify(user, work_package)
module API
module V3
module Repositories
class RevisionsCollectionRepresenter < ::API::Decorators::Collection
element_decorator ::API::V3::Repositories::RevisionRepresenter
end
end
clear_notification
end
##
# Notifies the user of the created work package.
def notify(user, work_package)
job = DeliverWorkPackageCreatedJob.new(user.id, work_package.id, User.current.id)
Delayed::Job.enqueue job
end
# Wrap send_notification so it defaults to true, when it's nil
def send_notification
return true if @send_notification.nil?
@send_notification
end
private
# Need to clear the notification setting after each usage otherwise it might be cached
def clear_notification
@send_notification = true
end
end

@ -42,6 +42,7 @@ module API
mount ::API::V3::Projects::ProjectsAPI
mount ::API::V3::Queries::QueriesAPI
mount ::API::V3::Render::RenderAPI
mount ::API::V3::Repositories::RevisionsAPI
mount ::API::V3::Statuses::StatusesAPI
mount ::API::V3::StringObjects::StringObjectsAPI
mount ::API::V3::Types::TypesAPI

@ -114,6 +114,10 @@ module API
"#{root}/relations/#{id}"
end
def self.revision(id)
"#{root}/revisions/#{id}"
end
def self.render_markup(format: nil, link: nil)
format = format || Setting.text_formatting
format = 'plain' if format == '' # Setting will return '' for plain
@ -200,6 +204,10 @@ module API
"#{work_package_relations(work_package_id)}/#{id}"
end
def self.work_package_revisions(id)
"#{work_package(id)}/revisions"
end
def self.work_package_schema(project_id, type_id)
"#{root}/work_packages/schemas/#{project_id}-#{type_id}"
end

@ -128,6 +128,12 @@ module API
} if current_user_allowed_to(:add_work_package_watchers, context: represented.project)
end
link :revisions do
{
href: api_v3_paths.work_package_revisions(represented.id)
} if current_user_allowed_to(:view_changesets, context: represented.project)
end
link :watch do
{
href: api_v3_paths.work_package_watchers(represented.id),

@ -82,6 +82,7 @@ module API
mount ::API::V3::Relations::RelationsAPI
mount ::API::V3::Activities::ActivitiesByWorkPackageAPI
mount ::API::V3::Attachments::AttachmentsByWorkPackageAPI
mount ::API::V3::Repositories::RevisionsByWorkPackageAPI
mount ::API::V3::WorkPackages::UpdateFormAPI
end

@ -35,6 +35,10 @@ module OpenProject
class Base
attr_accessor :url, :root_url
def self.vendor
name.demodulize
end
def initialize(url, root_url = nil)
self.url = url
self.root_url = root_url
@ -57,7 +61,7 @@ module OpenProject
end
def vendor
self.class.name.demodulize
self.class.vendor
end
def info

@ -44,7 +44,7 @@ module OpenProject
end
def client_command
@client_command ||= config[:client_command] || 'git'
@client_command ||= self.class.config[:client_command] || 'git'
end
def client_version

@ -32,6 +32,20 @@ module OpenProject
module Scm
module Adapters
module LocalClient
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
##
# Reads the configuration for this strategy from OpenProject's `configuration.yml`.
def config
['scm', vendor].inject(OpenProject::Configuration) do |acc, key|
HashWithIndifferentAccess.new acc[key]
end
end
end
##
# Determines local capabilities for SCM creation.
# Overridden by including classes when SCM may be remote.
@ -39,15 +53,6 @@ module OpenProject
true
end
##
# Reads the configuration for this strategy from OpenProject's `configuration.yml`.
def config
scm_config = OpenProject::Configuration
['scm', vendor].inject(scm_config) do |acc, key|
HashWithIndifferentAccess.new acc[key]
end
end
##
# client executable command
def client_command

@ -36,11 +36,11 @@ module OpenProject
include LocalClient
def client_command
@client_command ||= config[:client_command] || 'svn'
@client_command ||= self.class.config[:client_command] || 'svn'
end
def svnadmin_command
@svnadmin_command ||= (config[:svnadmin_command] || 'svnadmin')
@svnadmin_command ||= (self.class.config[:svnadmin_command] || 'svnadmin')
end
def client_version

@ -30,32 +30,47 @@
module OpenProject
module Scm
module ManageableRepository
##
# We let SCM vendor implementation define their own
# types (e.g., for differences in the management of
# local vs. remote repositories).
#
# But if they are manageable by OpenProject, they must
# expose this type through +available_types+.
MANAGED_TYPE = :managed
##
# Reads from configuration whether new repositories of this kind
# may be managed from OpenProject.
def manageable?
!managed_root.nil?
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
##
# We let SCM vendor implementation define their own
# types (e.g., for differences in the management of
# local vs. remote repositories).
#
# But if they are manageable by OpenProject, they must
# expose this type through +available_types+.
def managed_type
:managed
end
##
# Reads from configuration whether new repositories of this kind
# may be managed from OpenProject.
def manageable?
! (disabled_types.include?(managed_type) || managed_root.nil?)
end
##
# Returns the managed root for this repository vendor
def managed_root
scm_config[:manages]
end
end
##
# Returns the managed root for this repository vendor
def managed_root
scm.config[:manages]
#
def manageable?
self.class.manageable?
end
##
# Determines whether this repository IS currently managed
# by openproject
def managed?
scm_type.to_sym == MANAGED_TYPE
scm_type.to_sym == self.class.managed_type
end
##
@ -73,7 +88,7 @@ module OpenProject
# Used only in the creation of a repository, at a later point
# in time, it is referred to in the root_url
def managed_repository_path
File.join(managed_root, repository_path)
File.join(self.class.managed_root, repository_path)
end
##

@ -92,12 +92,11 @@ module Redmine
end
end
# Returns the mail adresses of users that should be notified
# Returns users that should be notified
def recipients
notified = []
notified = project.notified_users if project
notified.reject! { |user| !visible?(user) }
notified.map(&:mail)
notified.select { |user| visible?(user) }
end
module ClassMethods

@ -55,8 +55,7 @@ module Redmine::Acts::Journalized
def recipients
notified = []
notified = project.notified_users if project
notified.reject! { |user| !visible?(user) }
notified.map(&:mail)
notified.select { |user| visible?(user) }
end
def current_journal

@ -66,10 +66,19 @@ module Redmine::Acts::Journalized
add_journal = journals.empty? || JournalManager.changed?(self) || !@journal_notes.empty?
JournalManager.add_journal self, @journal_user, @journal_notes if add_journal
journal = JournalManager.add_journal self, @journal_user, @journal_notes if add_journal
journals.select(&:new_record?).each(&:save!)
if add_journal
OpenProject::Notifications.send('journal_created',
journal: journal,
send_notification: JournalManager.send_notification)
end
# Need to clear the notification setting after each usage otherwise it might be cached
JournalManager.reset_notification
@journal_user = nil
@journal_notes = nil
end

@ -146,12 +146,10 @@ module Redmine
watcher_user_ids.any? { |uid| uid == user.id }))
end
# Returns an array of watchers' email addresses
# Returns an array of watchers
def watcher_recipients
notified = watcher_users.active.where(['mail_notification != ?', 'none'])
notified.select! { |user| possible_watcher?(user) }
notified.map(&:mail).compact
notified.select { |user| possible_watcher?(user) }
end
module ClassMethods; end

@ -47,9 +47,7 @@ describe 'messages', type: :feature do
end
before do
visit project_path(topic.board.project)
click_on 'Forums'
click_on topic.subject, match: :first
visit topic_path(topic)
end
describe 'clicking on quote', js: true do

@ -140,6 +140,7 @@ describe 'Create repository', type: :feature, js: true do
find('input[type="radio"][value="managed"]').set(true)
find('button[type="submit"]', text: I18n.t(:button_create)).click
expect(page).to have_selector('div.flash.notice')
expect(page).to have_selector('input[name="scm_type"][value="managed"]:checked')
expect(page).to have_selector('a.icon-delete', text: I18n.t(:button_delete))
end
@ -152,6 +153,7 @@ describe 'Create repository', type: :feature, js: true do
find('button[type="submit"]', text: I18n.t(:button_create)).click
expect(page).to have_selector('div.flash.notice')
expect(page).to have_selector('button[type="submit"]', text: I18n.t(:button_save))
expect(page).to have_selector('a.icon-delete', text: I18n.t(:button_delete))
end

@ -18,20 +18,26 @@ describe 'activity comments', js: true do
row = page.find("#work-package-#{work_package.id}")
row.double_click
expect(find('#add-comment-text')).to be_present
ng_wait
end
it 'should alert user if navigating with unsaved form' do
page.execute_script("jQuery('#add-comment-text').val('Foobar').trigger('change')")
fill_in I18n.t('js.label_add_comment_title'), with: 'Foobar'
visit root_path
page.driver.browser.switch_to.alert.accept
expect(current_path).to eq(root_path)
end
it 'should not alert if comment has been submitted' do
page.execute_script("jQuery('#add-comment-text').val('Foobar').trigger('change')")
page.execute_script("jQuery('#add-comment-text').siblings('button').trigger('click')")
fill_in I18n.t('js.label_add_comment_title'), with: 'Foobar'
click_button I18n.t('js.label_add_comment')
visit root_path
expect(current_path).to eq(root_path)
end
end

@ -87,6 +87,9 @@ class WorkPackagesPage
def ensure_index_page_loaded
if Capybara.current_driver == Capybara.javascript_driver
extend ::Angular::DSL unless singleton_class.included_modules.include?(::Angular::DSL)
ng_wait
expect(page).to have_selector('.advanced-filters--filter', visible: false)
end
end

@ -81,13 +81,13 @@ describe Comment, type: :model do
news = FactoryGirl.create(:news, project: project, author: user)
# with notifications for that event turned on
allow(Notifier).to receive(:notify?).with(:news_comment_added).and_return(true)
allow(Setting).to receive(:notified_events).and_return(['news_comment_added'])
assert_difference 'ActionMailer::Base.deliveries.size', 1 do
Comment.create!(commented: news, author: user, comments: 'more useful stuff')
end
# with notifications for that event turned off
allow(Notifier).to receive(:notify?).with(:news_comment_added).and_return(false)
allow(Setting).to receive(:notified_events).and_return([])
assert_no_difference 'ActionMailer::Base.deliveries.size' do
Comment.create!(commented: news, author: user, comments: 'more useful stuff')
end

@ -1,133 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'legacy_spec_helper'
describe JournalObserver, type: :model do
before do
@type = FactoryGirl.create :type_with_workflow
@project = FactoryGirl.create :project,
types: [@type]
@workflow = @type.workflows.first
@user = FactoryGirl.create :user,
mail_notification: 'all',
member_in_project: @project
@issue = FactoryGirl.create :work_package,
project: @project,
author: @user,
type: @type,
status: @workflow.old_status
@user.members.first.roles << @workflow.role
@user.reload
allow(User).to receive(:current).and_return(@user)
ActionMailer::Base.deliveries.clear
end
context "#after_create for 'work_package_updated'" do
it 'should send a notification when configured as a notification' do
Setting.notified_events = ['work_package_updated']
assert_difference('ActionMailer::Base.deliveries.size', +1) do
@issue.add_journal(@user)
@issue.subject = 'A change to the issue'
assert @issue.save(validate: false)
end
end
it 'should not send a notification with not configured' do
Setting.notified_events = []
assert_no_difference('ActionMailer::Base.deliveries.size') do
@issue.add_journal(@user)
@issue.subject = 'A change to the issue'
assert @issue.save(validate: false)
end
end
end
context "#after_create for 'work_package_note_added'" do
it 'should send a notification when configured as a notification' do
@issue.recreate_initial_journal!
Setting.notified_events = ['work_package_note_added']
assert_difference('ActionMailer::Base.deliveries.size', +1) do
@issue.add_journal(@user, 'This update has a note')
assert @issue.save(validate: false)
end
end
it 'should not send a notification with not configured' do
Setting.notified_events = []
assert_no_difference('ActionMailer::Base.deliveries.size') do
@issue.add_journal(@user, 'This update has a note')
assert @issue.save(validate: false)
end
end
end
context "#after_create for 'status_updated'" do
it 'should send a notification when configured as a notification' do
Setting.notified_events = ['status_updated']
assert_difference('ActionMailer::Base.deliveries.size', +1) do
@issue.add_journal(@user)
@issue.status = @workflow.new_status
assert @issue.save(validate: false)
end
end
it 'should not send a notification with not configured' do
Setting.notified_events = []
assert_no_difference('ActionMailer::Base.deliveries.size') do
@issue.add_journal(@user)
@issue.status = @workflow.new_status
assert @issue.save(validate: false)
end
end
end
context "#after_create for 'work_package_priority_updated'" do
it 'should send a notification when configured as a notification' do
Setting.notified_events = ['work_package_priority_updated']
assert_difference('ActionMailer::Base.deliveries.size', +1) do
@issue.add_journal(@user)
@issue.priority = IssuePriority.generate!
assert @issue.save(validate: false)
end
end
it 'should not send a notification with not configured' do
Setting.notified_events = []
assert_no_difference('ActionMailer::Base.deliveries.size') do
@issue.add_journal(@user)
@issue.priority = IssuePriority.generate!
assert @issue.save(validate: false)
end
end
end
end

@ -56,7 +56,7 @@ describe Journal, type: :model do
issue = WorkPackage.find(:first)
user = User.find(:first)
journal = issue.add_journal(user, 'A note')
JournalObserver.instance.send_notification = false
JournalManager.send_notification = false
assert_difference('Journal.count') do
assert issue.save

@ -364,7 +364,7 @@ describe User, type: :model do
@jsmith.notified_project_ids = []
@jsmith.save
@jsmith.reload
assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
assert @jsmith.projects.first.recipients.include?(@jsmith)
end
it 'should mail notification selected' do
@ -372,7 +372,7 @@ describe User, type: :model do
@jsmith.notified_project_ids = [1]
@jsmith.save
@jsmith.reload
assert Project.find(1).recipients.include?(@jsmith.mail)
assert Project.find(1).recipients.include?(@jsmith)
end
it 'should mail notification only my events' do
@ -380,7 +380,7 @@ describe User, type: :model do
@jsmith.notified_project_ids = []
@jsmith.save
@jsmith.reload
assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
assert !@jsmith.projects.first.recipients.include?(@jsmith)
end
it 'should comments sorting preference' do

@ -92,10 +92,10 @@ describe Watcher do
assert @issue.watcher_recipients.empty?
assert @issue.add_watcher(@user)
assert_contains @issue.watcher_recipients, @user.mail
assert_contains @issue.watcher_recipients, @user
@user.update_attribute :mail_notification, 'none'
assert_does_not_contain @issue.watcher_recipients, @user.mail
assert_does_not_contain @issue.watcher_recipients, @user
end
it 'should unwatch' do

@ -0,0 +1,127 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe ::API::V3::Repositories::RevisionRepresenter do
include ::API::V3::Utilities::PathHelper
let(:representer) { described_class.new(revision) }
let(:project) { FactoryGirl.build :project }
let(:repository) { FactoryGirl.build :repository_subversion, project: project }
let(:revision) {
FactoryGirl.build(:changeset,
id: 42,
revision: '1234',
repository: repository,
comments: commit_message,
committer: 'foo bar <foo@example.org>',
committed_on: DateTime.now,
)
}
let(:commit_message) { 'Some commit message' }
context 'generation' do
subject(:generated) { representer.to_json }
it { is_expected.to be_json_eql('Revision'.to_json).at_path('_type') }
describe 'revision' do
it { is_expected.to have_json_path('id') }
it_behaves_like 'API V3 formattable', 'message' do
let(:format) { 'plain' }
let(:raw) { revision.comments }
let(:html) { '<p>' + revision.comments + '</p>' }
end
describe 'identifier' do
it { is_expected.to have_json_path('identifier') }
it { is_expected.to be_json_eql('1234'.to_json).at_path('identifier') }
end
describe 'createdAt' do
it_behaves_like 'has UTC ISO 8601 date and time' do
let(:date) { revision.committed_on }
let(:json_path) { 'createdAt' }
end
end
describe 'authorName' do
it { is_expected.to have_json_path('authorName') }
it { is_expected.to be_json_eql('foo bar '.to_json).at_path('authorName') }
end
end
context 'with referencing commit message' do
let(:work_package) { FactoryGirl.build_stubbed(:work_package, project: project) }
let(:commit_message) { "Totally references ##{work_package.id}" }
let(:html_reference) {
id = work_package.id
str = "Totally references <a href=\"/work_packages/#{id}\""
str << " class=\"issue work_package status-1 priority-1 parent\""
str << " title=\"#{work_package.subject} (#{work_package.status})\">"
str << "##{id}</a>"
}
before do
allow(WorkPackage).to receive(:find_by_id).and_return(work_package)
end
it_behaves_like 'API V3 formattable', 'message' do
let(:format) { 'plain' }
let(:raw) { revision.comments }
let(:html) { '<p>' + html_reference + '</p>' }
end
end
describe 'author' do
context 'with no linked user' do
it_behaves_like 'has no link' do
let(:link) { 'author' }
end
end
context 'with linked user as author' do
let(:user) { FactoryGirl.build(:user) }
before do
allow(revision).to receive(:user).and_return(user)
end
it_behaves_like 'has a titled link' do
let(:link) { 'author' }
let(:href) { api_v3_paths.user(user.id) }
let(:title) { user.name }
end
end
end
end
end

@ -239,6 +239,16 @@ describe ::API::V3::Utilities::PathHelper do
end
end
describe 'revisions paths' do
describe '#revision' do
subject { helper.revision 1 }
it_behaves_like 'api v3 path'
it { is_expected.to eql('/api/v3/revisions/1') }
end
end
describe 'schemas paths' do
describe '#work_package_schema' do
subject { helper.work_package_schema 1, 2 }
@ -399,6 +409,14 @@ describe ::API::V3::Utilities::PathHelper do
it { is_expected.to match(/^\/api\/v3\/work_packages\/42\/relations\/1/) }
end
describe '#work_package_revisions' do
subject { helper.work_package_revisions 42 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to eql('/api/v3/work_packages/42/revisions') }
end
describe '#work_package_form' do
subject { helper.work_package_form 1 }

@ -325,6 +325,26 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
end
end
describe 'revisions' do
context 'when the user lacks the view_changesets permission' do
it_behaves_like 'has no link' do
let(:link) { 'revisions' }
end
end
context 'when the user has the required permission' do
let(:revision_permissions) { [:view_changesets] }
let(:role) { FactoryGirl.create :role, permissions: permissions + revision_permissions }
it_behaves_like 'has an untitled link' do
let(:link) { 'revisions' }
let(:href) {
api_v3_paths.work_package_revisions(work_package.id)
}
end
end
end
describe 'version' do
let(:embedded_path) { '_embedded/version' }
let(:href_path) { '_links/version/href' }

@ -44,7 +44,7 @@ describe OpenProject::Scm::Adapters::Git do
}
before do
allow(adapter).to receive(:config).and_return(config)
allow(adapter.class).to receive(:config).and_return(config)
end
describe 'client information' do

@ -36,7 +36,7 @@ describe OpenProject::Scm::Adapters::Subversion do
let(:adapter) { OpenProject::Scm::Adapters::Subversion.new url, root_url }
before do
allow(adapter).to receive(:config).and_return(config)
allow(adapter.class).to receive(:config).and_return(config)
end
describe 'client information' do

@ -0,0 +1,156 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe JournalNotificationMailer do
let(:project) { FactoryGirl.create(:project_with_types) }
let(:user) do
FactoryGirl.build(:user,
mail_notification: 'all',
member_in_project: project)
end
let(:work_package) {
FactoryGirl.create(:work_package,
project: project,
author: user,
type: project.types.first)
}
let(:notifications) { [] }
before do
allow(User).to receive(:current).and_return(user)
allow(Setting).to receive(:notified_events).and_return(notifications)
ActionMailer::Base.deliveries.clear
end
shared_examples_for 'handles deliveries' do |notification_setting|
context 'setting enabled' do
let(:notifications) { [notification_setting] }
it 'sends a notification' do
expect(ActionMailer::Base.deliveries.size).to eq(1)
end
context 'insufficient work package changes' do
let!(:another_work_package) {
FactoryGirl.create(:work_package,
project: project,
author: user,
type: project.types.first)
}
before do
ActionMailer::Base.deliveries.clear
another_work_package.add_journal(user)
another_work_package.description = 'needs more changes'
another_work_package.save!(validate: false)
end
it 'sends no notification' do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
end
it 'sends no notification' do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
describe 'journal creation' do
context 'work_package_created' do
before do
FactoryGirl.create(:work_package, project: project)
end
it_behaves_like 'handles deliveries', 'work_package_added'
end
context 'work_package_updated' do
before do
work_package.add_journal(user)
work_package.subject = 'A change to the issue'
work_package.save!(validate: false)
end
context 'setting enabled' do
let(:notifications) { ['work_package_updated'] }
it 'sends a notification' do
expect(ActionMailer::Base.deliveries.size).to eq(1)
end
end
it 'sends no notification' do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
context 'work_package_note_added' do
before do
work_package.add_journal(user, 'This update has a note')
work_package.save!(validate: false)
work_package.recreate_initial_journal!
end
it_behaves_like 'handles deliveries', 'work_package_note_added'
end
context 'status_updated' do
before do
work_package.add_journal(user)
work_package.status = FactoryGirl.build(:status)
work_package.save!(validate: false)
end
it_behaves_like 'handles deliveries', 'status_updated'
end
context 'work_package_priority_updated' do
before do
work_package.add_journal(user)
work_package.priority = IssuePriority.generate!
work_package.save!(validate: false)
end
it_behaves_like 'handles deliveries', 'work_package_priority_updated'
end
context 'send_notification disabled' do
before do
allow(JournalManager).to receive(:send_notification).and_return(false)
FactoryGirl.create(:work_package, project: project) # Provoke notification
end
it 'sends no notification' do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
end
end

@ -37,7 +37,21 @@ describe Repository::Git, type: :model do
before do
allow(Setting).to receive(:enabled_scm).and_return(['Git'])
allow(instance).to receive(:scm).and_return(adapter)
allow(adapter).to receive(:config).and_return(config)
allow(adapter.class).to receive(:config).and_return(config)
end
describe 'available types' do
it 'allow local by default' do
expect(instance.class.available_types).to eq([:local])
end
context 'with disabled typed' do
let(:config) { { disabled_types: [:local, :managed] } }
it 'does not have any types' do
expect(instance.class.available_types).to be_empty
end
end
end
describe 'managed git' do
@ -53,6 +67,16 @@ describe Repository::Git, type: :model do
it 'is manageable' do
expect(instance.manageable?).to be true
expect(instance.class.available_types).to eq([:local, :managed])
end
context 'with disabled managed typed' do
let(:config) { { disabled_types: [:managed] } }
it 'is no longer manageable' do
expect(instance.class.available_types).to eq([:local])
expect(instance.manageable?).to be false
end
end
context 'and associated project' do

@ -36,7 +36,7 @@ describe Repository::Subversion, type: :model do
before do
allow(Setting).to receive(:enabled_scm).and_return(['Subversion'])
allow(instance).to receive(:scm).and_return(adapter)
allow(adapter).to receive(:config).and_return(config)
allow(instance.class).to receive(:scm_config).and_return(config)
end
describe 'default Subversion' do
@ -45,7 +45,15 @@ describe Repository::Subversion, type: :model do
end
it 'has one available type' do
expect(instance.supported_types).to eq [:existing]
expect(instance.class.available_types).to eq [:existing]
end
context 'with disabled typed' do
let(:config) { { disabled_types: [:existing, :managed] } }
it 'does not have any types' do
expect(instance.class.available_types).to be_empty
end
end
end
@ -62,10 +70,16 @@ describe Repository::Subversion, type: :model do
it 'is manageable' do
expect(instance.manageable?).to be true
expect(instance.class.available_types).to eq([:existing, :managed])
end
it 'has two available types' do
expect(instance.supported_types).to eq [:existing, :managed]
context 'with disabled managed typed' do
let(:config) { { disabled_types: [:managed] } }
it 'is no longer manageable' do
expect(instance.class.available_types).to eq([:existing])
expect(instance.manageable?).to be false
end
end
context 'and associated project' do

@ -31,20 +31,23 @@ require 'spec_helper'
describe WorkPackage, type: :model do
describe ActionMailer::Base do
let(:user_1) {
FactoryGirl.create(:user,
mail: 'dlopper@somenet.foo')
FactoryGirl.build(:user,
mail: 'dlopper@somenet.foo',
member_in_project: project)
}
let(:user_2) {
FactoryGirl.create(:user,
mail: 'jsmith@somenet.foo')
FactoryGirl.build(:user,
mail: 'jsmith@somenet.foo',
member_in_project: project)
}
let(:work_package) { FactoryGirl.build(:work_package) }
let(:project) { FactoryGirl.create(:project) }
let(:work_package) { FactoryGirl.build(:work_package, project: project) }
before do
ActionMailer::Base.deliveries.clear
allow(work_package).to receive(:recipients).and_return([user_1.mail])
allow(work_package).to receive(:watcher_recipients).and_return([user_2.mail])
allow(work_package).to receive(:recipients).and_return([user_1])
allow(work_package).to receive(:watcher_recipients).and_return([user_2])
work_package.save
end
@ -73,7 +76,7 @@ describe WorkPackage, type: :model do
before do
ActionMailer::Base.deliveries.clear
WorkPackageObserver.instance.send_notification = false
JournalManager.send_notification = false
work_package.save!
end
@ -91,7 +94,7 @@ describe WorkPackage, type: :model do
subject { work_package.recipients }
it { is_expected.to include(user_1.mail) }
it { is_expected.to include(user_1) }
end
end
end

@ -191,12 +191,12 @@ describe WorkPackage, type: :model do
context 'pre-condition' do
subject { work_package.recipients }
it { is_expected.to include(work_package.author.mail) }
it { is_expected.to include(work_package.author) }
end
subject { copy.recipients }
it { is_expected.not_to include(copy.author.mail) }
it { is_expected.not_to include(copy.author) }
end
describe 'with children' do

@ -1037,7 +1037,7 @@ describe WorkPackage, type: :model do
it { is_expected.not_to be_nil }
end
let(:expected_users) { work_package.author.mail }
let(:expected_users) { work_package.author }
it_behaves_like 'includes expected users'
end
@ -1051,7 +1051,7 @@ describe WorkPackage, type: :model do
it { is_expected.not_to be_nil }
end
let(:expected_users) { work_package.assigned_to.mail }
let(:expected_users) { work_package.assigned_to }
it_behaves_like 'includes expected users'
end

@ -0,0 +1,98 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Revisions by work package resource', type: :request do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
include FileHelpers
let(:current_user) {
FactoryGirl.create(:user,
member_in_project: project,
member_through_role: role)
}
let(:project) { FactoryGirl.create(:project, is_public: false) }
let(:role) { FactoryGirl.create(:role, permissions: permissions) }
let(:permissions) { [:view_work_packages, :view_changesets] }
let(:repository) { FactoryGirl.create(:repository_subversion, project: project) }
let(:work_package) { FactoryGirl.create(:work_package, author: current_user, project: project) }
let(:revisions) { [] }
subject(:response) { last_response }
before do
allow(User).to receive(:current).and_return current_user
end
describe '#get' do
let(:get_path) { api_v3_paths.work_package_revisions work_package.id }
before do
revisions.each do |rev| rev.save! end
get get_path
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
end
it_behaves_like 'API V3 collection response', 0, 0, 'Revision'
context 'with existing revisions' do
let(:revisions) {
FactoryGirl.build_list(:changeset,
5,
comments: "This commit references ##{work_package.id}",
repository: repository
)
}
it_behaves_like 'API V3 collection response', 5, 5, 'Revision'
end
context 'user unauthorized to view work package' do
let(:current_user) { FactoryGirl.create(:user) }
it 'should respond with 404' do
expect(subject.status).to eq(404)
end
end
context 'user unauthorized to view revisions' do
let(:permissions) { [:view_work_packages] }
it 'should respond with 403' do
expect(subject.status).to eq(403)
end
end
end
end

@ -0,0 +1,97 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Revisions resource', type: :request do
include Rack::Test::Methods
include Capybara::RSpecMatchers
include API::V3::Utilities::PathHelper
let(:revision) {
FactoryGirl.create(:changeset,
repository: repository,
comments: 'Some commit message',
committer: 'foo bar <foo@example.org>'
)
}
let(:repository) {
FactoryGirl.create(:repository_subversion, project: project)
}
let(:project) {
FactoryGirl.create(:project, identifier: 'test_project', is_public: false)
}
let(:role) {
FactoryGirl.create(:role,
permissions: [:view_changesets])
}
let(:current_user) {
FactoryGirl.create(:user, member_in_project: project, member_through_role: role)
}
let(:unauthorized_user) { FactoryGirl.create(:user) }
describe '#get' do
let(:get_path) { api_v3_paths.revision revision.id }
context 'when acting as a user with permission to view revisions' do
before(:each) do
allow(User).to receive(:current).and_return current_user
get get_path
end
it 'should respond with 200' do
expect(last_response.status).to eq(200)
end
describe 'response body' do
subject(:response) { last_response.body }
it 'should respond with revision in HAL+JSON format' do
is_expected.to be_json_eql(revision.id.to_json).at_path('id')
end
end
context 'requesting nonexistent revision' do
let(:get_path) { api_v3_paths.revision 909090 }
it_behaves_like 'not found'
end
end
context 'when acting as an user without permission to view work package' do
before(:each) do
allow(User).to receive(:current).and_return unauthorized_user
get get_path
end
it_behaves_like 'not found'
end
end
end

@ -133,7 +133,7 @@ MESSAGE
subject { model_instance.watcher_recipients }
it { is_expected.to match_array([watching_user.mail]) }
it { is_expected.to match_array([watching_user]) }
context 'when the permission to watch has been removed' do
before do

Loading…
Cancel
Save