Merge branch 'release/4.3' into dev

pull/3257/head
Jens Ulferts 9 years ago
commit 98678183c1
  1. 13
      CONTRIBUTING.md
  2. 2
      Gemfile.lock
  3. 2
      app/controllers/api/v2/authentication_controller.rb
  4. 2
      app/models/work_package.rb
  5. 14
      app/views/settings/_authentication.html.erb
  6. 7
      app/views/settings/_display.html.erb
  7. 15
      config/locales/en.yml
  8. 3
      config/settings.yml
  9. 32
      frontend/app/helpers/path-helper.js
  10. 2
      frontend/app/services/priority-service.js
  11. 14
      frontend/app/services/status-service.js
  12. 2
      frontend/app/services/type-service.js
  13. 4
      frontend/package.json
  14. 3
      lib/api/v3/work_packages/schema/work_package_schema_representer.rb
  15. 2
      lib/open_project/authentication.rb
  16. 15
      spec/controllers/api/v2/authentication_spec.rb
  17. 9
      spec/features/work_packages/details/activity_comments_spec.rb
  18. 2
      spec/legacy/support/legacy_assertions.rb
  19. 20
      spec/lib/api/v3/work_packages/work_package_schema_representer_spec.rb
  20. 22
      spec/models/work_package_spec.rb
  21. 2
      spec/requests/api/v3/authentication_spec.rb

@ -59,6 +59,19 @@ We will then review your pull request.
Please note that you can add commits after the pull request has been created by pushing
to the branch in your fork.
## Translations
Beginning with OpenProject 4.2.0 the OpenProject core only holds the
english locales and all other locales are stored in
OpenProject-Translations. But since this plugin is hardwired in the
Gemfile, german and other locales are available again.
If you want to contribute to the localization of OpenProject and its
plugins you can do so on [Crowdin](https://crowdin.com/projects/opf).
Once a day we will fetch those locales and upload them to GitHub.
More on this topic can be found [in our blog](https://www.openproject.org/2015/07/10/help-translate-openproject-into-your-language/).
## Important notes
To ensure a smooth workflow for everyone, please take note of the following:

@ -45,7 +45,7 @@ GIT
revision: c42bac8f2bca7850a89b67e753368f5f52d13c25
branch: dev
specs:
openproject-translations (4.3.0.pre.alpha)
openproject-translations (4.3.0)
crowdin-api (~> 0.2.4)
mixlib-shellout (~> 2.1.0)
rails (~> 3.2.14)

@ -35,7 +35,7 @@ module Api
unloadable
AuthorizationData = Struct.new(:authorized, :authenticated_user_id)
skip_before_filter :require_login
skip_before_filter :require_login, :check_if_login_required
before_filter :api_allows_login, :require_login
def index

@ -444,7 +444,7 @@ class WorkPackage < ActiveRecord::Base
end
def update_by(user, attributes)
add_journal(user, attributes.delete(:notes)) if attributes[:notes]
add_journal(user, attributes.delete(:notes) || '')
add_time_entry_for(user, attributes.delete(:time_entry))
attributes.delete(:attachments)

@ -50,7 +50,11 @@ See doc/COPYRIGHT.rdoc for more details.
[l("label_password_rule_#{r}"), r]
end %></div>
<div class="form--field"><%= setting_text_field :password_min_adhered_rules, :size => 6 %></div>
<div class="form--field"><%= setting_text_field :password_days_valid, :size => 6 %></div>
<div class="form--field"><%= setting_text_field :password_days_valid, :size => 6 %>
<span class="form--field-instructions">
<%= l(:text_hint_disable_with_0) %>
</span>
</div>
<div class="form--field"><%= setting_text_field :password_count_former_banned, :size => 6 %></div>
<div class="form--field"><%= setting_check_box :lost_password, :label => :label_password_lost %></div>
<% else %>
@ -71,8 +75,12 @@ See doc/COPYRIGHT.rdoc for more details.
<% unless OpenProject::Configuration.disable_password_login? %>
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend"><%= I18n.t(:brute_force_prevention, :scope => [:settings]) %></legend>
<div class="form--field"><%= setting_text_field :brute_force_block_after_failed_logins %></div>
<div class="form--field"><%= setting_text_field :brute_force_block_minutes %></div>
<div class="form--field"><%= setting_text_field :brute_force_block_after_failed_logins %>
<span class="form--field-instructions">
<%= l(:text_hint_disable_with_0) %>
</span>
</div>
<div class="form--field"><%= setting_text_field :brute_force_block_minutes, unit: l(:label_minute_plural) %></div>
</fieldset>
<% end %>

@ -50,6 +50,13 @@ See doc/COPYRIGHT.rdoc for more details.
<div class="form--field"><%= setting_check_box :gravatar_enabled %></div>
<div class="form--field"><%= setting_select :gravatar_default, [["Wavatars", 'wavatar'], ["Identicons", 'identicon'], ["Monster ids", 'monsterid'], ["Retro", 'retro'], ["Mystery man", 'mm']], :blank => :label_none %></div>
<div class="form--field"><%= setting_text_field :journal_aggregation_time_minutes, unit: l(:label_minute_plural) %>
<span class="form--field-instructions">
<%= l(:text_journal_aggregation_time_explanation) %><br/>
<%= l(:text_hint_disable_with_0) %>
</span>
</div>
</section>
<%= styled_button_tag l(:button_save), class: '-highlight -with-icon icon-yes' %>

@ -578,8 +578,8 @@ en:
error_scm_annotate: "The entry does not exist or cannot be annotated."
error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
error_scm_not_found: "The entry or revision was not found in the repository."
error_unable_delete_status: "Unable to delete work package status because it contains work packages."
error_unable_delete_default_status: "Unable to delete default work package status. Please select another default work package status before deleting the current one."
error_unable_delete_status: "The work package status cannot be deleted since it is used by at least one work package."
error_unable_delete_default_status: "Unable to delete the default work package status. Please select another default work package status before deleting the current one."
error_unable_to_connect: "Unable to connect (%{value})"
error_workflow_copy_source: "Please select a source type or role"
error_workflow_copy_target: "Please select target type(s) and role(s)"
@ -816,6 +816,7 @@ en:
label_message_plural: "Messages"
label_message_posted: "Message added"
label_min_max_length: "Min - Max length"
label_minute_plural: "minutes"
label_missing_api_access_key: "Missing an API access key"
label_missing_feeds_access_key: "Missing a RSS access key"
label_modification: "%{count} change"
@ -1234,6 +1235,7 @@ en:
project_module_news: "News"
project_module_repository: "Repository"
project_module_time_tracking: "Time tracking"
project_module_timelines: "Timelines"
project_module_wiki: "Wiki"
# possible query parameters (e.g. issue queries),
@ -1253,8 +1255,8 @@ en:
setting_autologin: "Autologin"
setting_available_languages: "Available languages"
setting_bcc_recipients: "Blind carbon copy recipients (bcc)"
setting_brute_force_block_after_failed_logins: "Block user after this number of failed login attempts (disable with 0)"
setting_brute_force_block_minutes: "Time the user is blocked for (minutes)"
setting_brute_force_block_after_failed_logins: "Block user after this number of failed login attempts"
setting_brute_force_block_minutes: "Time the user is blocked for"
setting_cache_formatted_text: "Cache formatted text"
setting_column_options: "Customize the appearance of the work package lists"
setting_commit_fix_keywords: "Fixing keywords"
@ -1288,6 +1290,7 @@ en:
setting_work_package_properties: "Work package properties"
setting_work_package_startdate_is_adddate: "Use current date as start date for new work packages"
setting_work_packages_export_limit: "Work packages export limit"
setting_journal_aggregation_time_minutes: "Display journals as aggregated within"
setting_log_requesting_user: "Log user login, name, and mail address for all requests"
setting_login_required: "Authentication required"
setting_mail_from: "Emission email address"
@ -1297,7 +1300,7 @@ en:
setting_new_project_user_role_id: "Role given to a non-admin user who creates a project"
setting_password_active_rules: "Active character classes"
setting_password_count_former_banned: "Number of most recently used passwords banned for reuse"
setting_password_days_valid: "Number of days, after which to enforce a password change (disable with 0)"
setting_password_days_valid: "Number of days, after which to enforce a password change"
setting_password_min_length: "Minimum length"
setting_password_min_adhered_rules: "Minimum number of required classes"
setting_per_page_options: "Objects per page options"
@ -1362,6 +1365,7 @@ en:
text_enumeration_destroy_question: "%{count} objects are assigned to this value."
text_file_repository_writable: "Attachments directory writable"
text_git_repo_example: "a bare and local repository (e.g. /gitrepo, c:\\gitrepo)"
text_hint_disable_with_0: "Note: Disable with 0"
text_work_package_added: "Work package %{id} has been reported by %{author}."
text_work_package_category_destroy_assignments: "Remove category assignments"
text_work_package_category_destroy_question: "Some work packages (%{count}) are assigned to this category. What do you want to do?"
@ -1370,6 +1374,7 @@ en:
text_work_packages_destroy_confirmation: "Are you sure you want to delete the selected work package(s)?"
text_work_packages_ref_in_commit_messages: "Referencing and fixing work packages in commit messages"
text_journal_added: "%{label} %{value} added"
text_journal_aggregation_time_explanation: "Combine journals for display if their age difference is less than the specified timespan. This will also delay mail notifications by the same amount of time."
text_journal_changed: "%{label} changed from %{old} to %{new}"
text_journal_changed_no_detail: "%{label} updated"
text_journal_changed_with_diff: "%{label} changed (%{link})"

@ -261,3 +261,6 @@ users_deletable_by_admins:
default: 0
users_deletable_by_self:
default: 0
journal_aggregation_time_minutes:
default: 5
format: int

@ -217,26 +217,6 @@ module.exports = function() {
return PathHelper.apiWorkPackagesPath() + '/column_sums';
},
// API V2
apiPrioritiesPath: function() {
return PathHelper.apiV2 + '/planning_element_priorities';
},
apiProjectStatusesPath: function(projectIdentifier) {
return PathHelper.apiV2ProjectPath(projectIdentifier) + '/statuses';
},
apiProjectWorkPackageTypesPath: function(projectIdentifier) {
return PathHelper.apiV2ProjectPath(projectIdentifier) + '/planning_element_types';
},
apiStatusesPath: function() {
return PathHelper.apiV2 + '/statuses';
},
apiV2ProjectPath: function(projectIdentifier) {
return PathHelper.apiV2 + PathHelper.projectPath(projectIdentifier);
},
apiWorkPackageTypesPath: function() {
return PathHelper.apiV2 + '/planning_element_types';
},
// API V3
apiQueryStarPath: function(queryId) {
return PathHelper.apiV3QueryPath(queryId) + '/star';
@ -250,6 +230,9 @@ module.exports = function() {
apiV3WorkPackagePath: function(workPackageId) {
return PathHelper.apiV3 + '/work_packages/' + workPackageId;
},
apiPrioritiesPath: function() {
return PathHelper.apiV3 + '/priorities';
},
apiV3ProjectsPath: function(projectIdentifier) {
return PathHelper.apiV3 + PathHelper.projectsPath() + '/' + projectIdentifier;
},
@ -259,6 +242,15 @@ module.exports = function() {
apiV3TypePath: function(typeId) {
return PathHelper.apiV3 + '/types/' + typeId;
},
apiStatusesPath: function() {
return PathHelper.apiV3 + '/statuses';
},
apiProjectWorkPackageTypesPath: function(projectIdentifier) {
return PathHelper.apiV3ProjectsPath(projectIdentifier) + '/types';
},
apiWorkPackageTypesPath: function() {
return PathHelper.apiV3 + '/types';
},
// Static
staticUserPath: function(userId) {
return PathHelper.userPath(userId);

@ -38,7 +38,7 @@ module.exports = function($http, PathHelper) {
doQuery: function(url, params) {
return $http.get(url, { params: params })
.then(function(response){
return response.data.planning_element_priorities;
return response.data._embedded.elements;
});
}
};

@ -29,22 +29,14 @@
module.exports = function($http, PathHelper) {
var StatusService = {
getStatuses: function(projectIdentifier) {
var url;
if(projectIdentifier) {
url = PathHelper.apiProjectStatusesPath(projectIdentifier);
} else {
url = PathHelper.apiStatusesPath();
}
return StatusService.doQuery(url);
getStatuses: function() {
return StatusService.doQuery(PathHelper.apiStatusesPath());
},
doQuery: function(url, params) {
return $http.get(url, { params: params })
.then(function(response){
return response.data.statuses;
return response.data._embedded.elements;
});
}
};

@ -45,7 +45,7 @@ module.exports = function($http, PathHelper) {
doQuery: function(url, params) {
return $http.get(url, { params: params })
.then(function(response){
return response.data.planning_element_types;
return response.data._embedded.elements;
});
}
};

@ -13,7 +13,7 @@
"gulp-filter": "^2.0.2",
"gulp-jshint": "^1.8.5",
"gulp-livingstyleguide": "0.1.5",
"gulp-protractor": "0.0.11",
"gulp-protractor": "1.0.0",
"gulp-replace": "^0.5.3",
"gulp-ruby-sass": "^0.7.1",
"gulp-util": "^3.0.4",
@ -34,7 +34,7 @@
"mocha": "~1.18.2",
"mocha-jenkins-reporter": "^0.1.2",
"phantomjs": "~1.9.2",
"protractor": "^2.0.0",
"protractor": "^2.1.0",
"sinon": "~1.9.1",
"sinon-chai": "~2.5.0",
"sorted-object": "^1.0.0",

@ -52,9 +52,8 @@ module API
end
link :self do
path = api_v3_paths.work_package_schema(represented.project.id, represented.type.id)
unless form_embedded
path = api_v3_paths.work_package_schema(represented.project.id, represented.type.id)
{ href: path }
end
end

@ -83,7 +83,7 @@ module OpenProject
module_function
def pick_auth_scheme(supported_schemes, default_scheme, request_headers = {})
req_scheme = request_headers['X-Authentication-Scheme']
req_scheme = request_headers['HTTP_X_AUTHENTICATION_SCHEME']
if supported_schemes.include? req_scheme
req_scheme

@ -39,15 +39,24 @@ describe Api::V2::AuthenticationController, type: :controller do
it_should_behave_like 'a controller action with require_login'
describe 'REST API disabled' do
before do
before { allow(Setting).to receive(:rest_api_enabled?).and_return false }
context 'without login_required' do
before { fetch }
it { expect(response.status).to eq(403) }
end
allow(Setting).to receive(:rest_api_enabled?).and_return false
context 'with login_required' do
before do
allow(Setting).to receive(:login_required?).and_return true
fetch
end
it { expect(response.status).to eq(403) }
end
end
describe 'authorization data' do
let(:user) { FactoryGirl.create(:user) }
@ -120,7 +129,7 @@ describe Api::V2::AuthenticationController, type: :controller do
context 'with Session auth scheme requested' do
before do
request.env['X-Authentication-Scheme'] = 'Session'
request.env['HTTP_X_AUTHENTICATION_SCHEME'] = 'Session'
end
it 'has Session auth scheme' do

@ -1,16 +1,23 @@
require 'spec_helper'
require 'features/work_packages/details/inplace_editor/shared_contexts'
describe 'activity comments', js: true do
let(:project) { FactoryGirl.create :project_with_types, is_public: true }
let!(:work_package) { FactoryGirl.create(:work_package, project: project) }
let(:user) { FactoryGirl.create :admin }
include_context 'maximized window'
before do
allow(User).to receive(:current).and_return(user)
visit project_work_packages_path(project)
current_window.resize_to(1440, 800)
ensure_wp_table_loaded
row = page.find("#work-package-#{work_package.id}")
row.double_click
expect(find('#add-comment-text')).to be_present
end

@ -294,7 +294,7 @@ module LegacyAssertionsAndHelpers
context "should not send www authenticate when header accept auth is session #{http_method} #{url}" do
context 'without credentials' do
before do
send(http_method, url, parameters, 'X-Authentication-Scheme' => 'Session')
send(http_method, url, parameters, 'HTTP_X_AUTHENTICATION_SCHEME' => 'Session')
end
it { should respond_with failure_code }
it { should_respond_with_content_type_based_on_url(url) }

@ -81,6 +81,26 @@ describe ::API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do
end
end
describe 'self link' do
it_behaves_like 'has an untitled link' do
let(:link) { 'self' }
let(:href) {
api_v3_paths.work_package_schema(work_package.project.id, work_package.type.id)
}
end
context 'embedded in a form' do
let(:embedded) { true }
# In a form there is no guarantee that the current state contains a valid WP
let(:work_package) { FactoryGirl.build(:work_package, type: nil) }
it_behaves_like 'has no link' do
let(:link) { 'self' }
end
end
end
describe '_type' do
it 'is indicated as Schema' do
is_expected.to be_json_eql('Schema'.to_json).at_path('_type')

@ -1245,12 +1245,30 @@ describe WorkPackage, type: :model do
expect(instance.subject).to eq('New subject')
end
it "should create a journal with the journal's 'notes' attribute set to the supplied" do
describe 'creates a journal entry' do
it 'with the supplied notes' do
instance.update_by!(user, notes: 'blubs')
expect(instance.journals.last.notes).to eq('blubs')
end
it 'by the given user' do
instance.update_by!(user, notes: 'blubs')
expect(instance.journals.last.user).to eq(user)
end
context 'without supplying journal notes' do
it 'creates an entry by the given user' do
instance.update_by!(user, subject: 'blubs')
expect(instance.journals.last.user).to eq(user)
end
it 'has empty journal notes' do
instance.update_by!(user, subject: 'blubs')
expect(instance.journals.last.notes).to eq('')
end
end
end
it 'should attach an attachment' do
raw_attachments = [double('attachment')]
attachment = FactoryGirl.build(:attachment)

@ -101,7 +101,7 @@ describe API::V3, type: :request do
let(:headers) do
auth = basic_auth(username, password.reverse)
auth.merge('X-Authentication-Scheme' => 'Session')
auth.merge('HTTP_X_AUTHENTICATION_SCHEME' => 'Session')
end
before do

Loading…
Cancel
Save