diff --git a/Gemfile.lock b/Gemfile.lock index c350f861ea..fb7ab7e1da 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -102,10 +102,10 @@ GIT GIT remote: https://github.com/opf/openproject-translations.git - revision: 8444a411240546e5abd36319bf4c627f71c7763c + revision: 04af925b27f647bb45c23244d5d40e5f27d81761 branch: stable/7 specs: - openproject-translations (7.0.2) + openproject-translations (7.0.3) crowdin-api (~> 0.4.1) mixlib-shellout (~> 2.1.0) rails (~> 5.0.0) @@ -140,67 +140,67 @@ GIT PATH remote: vendored-plugins/openproject-auth_plugins specs: - openproject-auth_plugins (7.0.2) + openproject-auth_plugins (7.0.3) omniauth (~> 1.0) rails (~> 5.0) PATH remote: vendored-plugins/openproject-backlogs specs: - openproject-backlogs (7.0.2) + openproject-backlogs (7.0.3) acts_as_silent_list (~> 3.0.0) - openproject-pdf_export (= 7.0.2) + openproject-pdf_export (= 7.0.3) rails (~> 5.0.0) PATH remote: vendored-plugins/openproject-costs specs: - openproject-costs (7.0.2) + openproject-costs (7.0.3) rails (~> 5.0.0) PATH remote: vendored-plugins/openproject-documents specs: - openproject-documents (7.0.2) + openproject-documents (7.0.3) rails (~> 5.0.0) PATH remote: vendored-plugins/openproject-github_integration specs: - openproject-github_integration (7.0.2) - openproject-webhooks (~> 7.0.2) + openproject-github_integration (7.0.3) + openproject-webhooks (~> 7.0.3) rails (~> 5.0) PATH remote: vendored-plugins/openproject-global_roles specs: - openproject-global_roles (7.0.2) + openproject-global_roles (7.0.3) rails (~> 5.0.0) PATH remote: vendored-plugins/openproject-local_avatars specs: - openproject-local_avatars (7.0.2) + openproject-local_avatars (7.0.3) rails (~> 5.0) rmagick (~> 2.15.4) PATH remote: vendored-plugins/openproject-meeting specs: - openproject-meeting (7.0.2) + openproject-meeting (7.0.3) icalendar (~> 2.3.0) rails (~> 5.0.0) PATH remote: vendored-plugins/openproject-my_project_page specs: - openproject-my_project_page (7.0.2) + openproject-my_project_page (7.0.3) rails (~> 5.0.0) PATH remote: vendored-plugins/openproject-openid_connect specs: - openproject-openid_connect (7.0.2) + openproject-openid_connect (7.0.3) lobby_boy (~> 0.1) omniauth-openid_connect-providers (~> 0.1) openproject-auth_plugins (~> 7.0) @@ -209,36 +209,36 @@ PATH PATH remote: vendored-plugins/openproject-pdf_export specs: - openproject-pdf_export (7.0.2) + openproject-pdf_export (7.0.3) prawn (~> 2.2) rails (~> 5.0.0) PATH remote: vendored-plugins/openproject-reporting specs: - openproject-reporting (7.0.2) + openproject-reporting (7.0.3) jquery-tablesorter (~> 1.22.3) - openproject-costs (= 7.0.2) + openproject-costs (= 7.0.3) rails (~> 5.0.0) reporting_engine (>= 1.1.0) PATH remote: vendored-plugins/openproject-webhooks specs: - openproject-webhooks (7.0.2) + openproject-webhooks (7.0.3) rails (~> 5.0) PATH remote: vendored-plugins/openproject-xls_export specs: - openproject-xls_export (7.0.2) + openproject-xls_export (7.0.3) rails (~> 5.0.0) spreadsheet (~> 0.8.9) PATH remote: vendored-plugins/reporting_engine specs: - reporting_engine (7.0.2) + reporting_engine (7.0.3) json rails (~> 5.0.0) diff --git a/app/assets/stylesheets/content/_autocomplete.sass b/app/assets/stylesheets/content/_autocomplete.sass index ea80dba163..5ad35343c9 100644 --- a/app/assets/stylesheets/content/_autocomplete.sass +++ b/app/assets/stylesheets/content/_autocomplete.sass @@ -88,7 +88,7 @@ div.autocomplete &.-inplace position: relative border: none - padding-top: 15px + margin-top: 15px line-height: 1.6 font-size: 1rem diff --git a/app/assets/stylesheets/content/work_packages/single_view/_single_view.sass b/app/assets/stylesheets/content/work_packages/single_view/_single_view.sass index 32a1a2788d..9315362c51 100644 --- a/app/assets/stylesheets/content/work_packages/single_view/_single_view.sass +++ b/app/assets/stylesheets/content/work_packages/single_view/_single_view.sass @@ -159,3 +159,22 @@ i &:before @include varprop(color, $body-font-color) + + +// Implement two column layout for WP full screen view +@media screen and (min-width: 90rem) + .action-show .attributes-group, + .full-create .attributes-group + .-columns-2 + column-count: 2 + column-gap: 4rem + + .attributes-key-value + -webkit-column-break-inside: avoid + page-break-inside: avoid + break-inside: avoid + + // Let some elements still span both columns + &.-span-all-columns + column-span: all + diff --git a/app/assets/stylesheets/layout/_toolbar.sass b/app/assets/stylesheets/layout/_toolbar.sass index de73746771..fb7f52216a 100644 --- a/app/assets/stylesheets/layout/_toolbar.sass +++ b/app/assets/stylesheets/layout/_toolbar.sass @@ -248,6 +248,7 @@ display: block .search-query-wrapper padding: 15px + overflow: hidden .query-select-dropdown--no-results padding: 15px 15px 0 .ui-menu-item @@ -272,6 +273,9 @@ top: 24px right: 22px color: #999999 + .ui-widget-content.-inplace + max-height: 55vh + overflow-x: auto input[type="search"]::-webkit-search-cancel-button display: none diff --git a/app/assets/stylesheets/layout/_work_package_table.sass b/app/assets/stylesheets/layout/_work_package_table.sass index e9ec11a544..d94950faa5 100644 --- a/app/assets/stylesheets/layout/_work_package_table.sass +++ b/app/assets/stylesheets/layout/_work_package_table.sass @@ -241,20 +241,3 @@ overflow: overlay #content overflow-y: auto - -// Implement two column layout for WP full screen view -@media screen and (min-width: 90rem) - .action-show .attributes-group, - .full-create .attributes-group - .-columns-2 - column-count: 2 - column-gap: 4rem - - .attributes-key-value - -webkit-column-break-inside: avoid - page-break-inside: avoid - break-inside: avoid - - - - diff --git a/app/assets/stylesheets/openproject/_plugins.scss.erb b/app/assets/stylesheets/openproject/_plugins.scss.erb index 7c77ed11e0..88eca61524 100644 --- a/app/assets/stylesheets/openproject/_plugins.scss.erb +++ b/app/assets/stylesheets/openproject/_plugins.scss.erb @@ -1,5 +1,5 @@ <% Redmine::Plugin.all.collect do |plugin| %> <% plugin.registered_global_assets[:css].each do |path| %> - @import "<%= path %>" + @import "<%= path %>"; <% end %> <% end %> diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8e24f9a317..4ab1227f90 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -42,6 +42,7 @@ class ApplicationController < ActionController::Base include I18n include Redmine::I18n include HookHelper + include ::OpenProject::Authentication::SessionExpiry layout 'base' @@ -675,13 +676,7 @@ def stop_if_feeds_disabled private def session_expired? - !api_request? && current_user.logged? && - (session_ttl_enabled? && (session[:updated_at].nil? || - (session[:updated_at] + Setting.session_ttl.to_i.minutes) < Time.now)) - end - - def session_ttl_enabled? - Setting.session_ttl_enabled? && Setting.session_ttl.to_i >= 5 + !api_request? && current_user.logged? && session_ttl_expired? end def permitted_params diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 84d6aaeceb..925d53f5de 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -435,6 +435,10 @@ def graph_commits_per_author(repository) ) graph.burn end + + def login_back_url_params + params.permit(:path) + end end class Date diff --git a/app/models/queries/work_packages/filter/watcher_filter.rb b/app/models/queries/work_packages/filter/watcher_filter.rb index d30d64e33e..4ef7d09784 100644 --- a/app/models/queries/work_packages/filter/watcher_filter.rb +++ b/app/models/queries/work_packages/filter/watcher_filter.rb @@ -101,7 +101,7 @@ def where_self_watcher(user_id) (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='WorkPackage' - AND #{::Queries::Operators::Equals.sql_for_field [user_id], db_table, db_field} + AND #{::Queries::Operators::Equals.sql_for_field [user_id], db_table, db_field}) SQL end diff --git a/app/models/work_package.rb b/app/models/work_package.rb index f2c6e2c506..f4665566ec 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -252,7 +252,7 @@ def self.event_url # after the wp is created. # As after_create is run before after_save, and journal creation is triggered by an # after_save hook, we rely on after_save and a specific version here. - after_save :reload_lock_and_timestamps, if: Proc.new { |wp| wp.lock_version == 0 } + after_save :reload_lock_and_timestamps, if: Proc.new { |wp| wp.lock_version.zero? } def self.done_ratio_disabled? Setting.work_package_done_ratio == 'disabled' @@ -286,11 +286,10 @@ def copy_from(arg, options = {}) # attributes don't come from form, so it's safe to force assign self.attributes = work_package.attributes.dup.except(*merged_options[:exclude]) self.parent_id = work_package.parent_id if work_package.parent_id - self.custom_field_values = - work_package.custom_field_values.inject({}) do |h, v| - h[v.custom_field_id] = work_package.custom_value_for(v.custom_field_id) - h - end + self.custom_field_values = work_package + .custom_values + .map { |cv| [cv.custom_field_id, cv.value] } + .to_h self.status = work_package.status work_package.watchers.each do |watcher| diff --git a/app/views/work_packages/bulk/destroy.html.erb b/app/views/work_packages/bulk/destroy.html.erb index 8e7873ed4e..bffc5579dd 100644 --- a/app/views/work_packages/bulk/destroy.html.erb +++ b/app/views/work_packages/bulk/destroy.html.erb @@ -81,11 +81,10 @@ See doc/COPYRIGHT.rdoc for more details. concat content_tag :i, '', class: 'button--icon icon-delete' concat content_tag :span, l(:button_delete), class: 'button--text' end %> - <%= link_to project_work_packages_path(@project), - title: l(:button_cancel), - class: 'button -with-icon icon-cancel' do %> - <%= l(:button_cancel) %> - <% end %> + <%= link_to_function l(:button_cancel), + "history.back()", + title: l(:button_cancel), + class: 'button -with-icon icon-cancel'%> <% end %> diff --git a/config/additional_boot.rb.example b/config/additional_boot.rb.example new file mode 100644 index 0000000000..f63ae1cf69 --- /dev/null +++ b/config/additional_boot.rb.example @@ -0,0 +1,8 @@ +# Copy this file to additional_boot.rb and add any statements +# that should run prior to Rails booting. +# +# Example: +# +# Silence Hashie warnings in OpenProject 7.0.x +# require 'hashie' +# Hashie.logger = Logger.new(nil) diff --git a/config/boot.rb b/config/boot.rb index 6031f1c67b..ea7c852ee7 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -29,4 +29,11 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +# Load any local boot extras that is kept out of source control +# (e.g., silencing of deprecations) +if File.exists?(File.join(File.dirname(__FILE__), 'additional_boot.rb')) + instance_eval File.read(File.join(File.dirname(__FILE__), 'additional_boot.rb')) +end + + require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/config/routes.rb b/config/routes.rb index b569e14f54..a7678b9366 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -348,7 +348,8 @@ get '(/revisions/:rev)(/*path)', action: :show, format: false, - rev: /[a-z0-9\.\-_]+/ + rev: /[a-z0-9\.\-_]+/, + as: 'show_revisions_path' end end diff --git a/docker-compose.pullpreview.yml b/docker-compose.pullpreview.yml index 3455b04380..1883077eb8 100644 --- a/docker-compose.pullpreview.yml +++ b/docker-compose.pullpreview.yml @@ -18,7 +18,7 @@ worker: &ruby - db environment: - "RAILS_CACHE_STORE=file_store" - - "DATABASE_URL=postgresql://app:p4ssw0rd@db:5432/app?encoding=utf8&pool=5&timeout=5000" + - "DATABASE_URL=postgresql://app:p4ssw0rd@db:5432/app?encoding=utf8&pool=5&timeout=5000&reconnect=true" - "SECRET_KEY_BASE=d4e74f017910ac56c6ebad01165b7e1b37f4c9c02e9716836f8670cdc8d65a231e64e4f6416b19c8" - "RAILS_ENV=production" - "HEROKU=true" diff --git a/docs/configuration/incoming-emails.md b/docs/configuration/incoming-emails.md index 43c4501846..84e0bbffa1 100644 --- a/docs/configuration/incoming-emails.md +++ b/docs/configuration/incoming-emails.md @@ -9,7 +9,14 @@ Receiving emails is done via a rake task that fetches emails from an email serve ### IMAP The rake task `redmine:email:receive_imap` fetches emails via IMAP and parses them. -Example: + +**Packaged installation** + +```bash +openproject run bundle exec rake redmine:email:receive_imap host='imap.gmail.com' username='test_user' password='password' port=993 ssl=true allow_override=type,project project=test_project +``` + +**Manual installation** ```bash bundle exec rake redmine:email:receive_imap host='imap.gmail.com' username='test_user' password='password' port=993 ssl=true allow_override=type,project project=test_project @@ -42,7 +49,14 @@ Available arguments that change how the work packages are handled: ### POP3 The rake task `redmine:email:receive_pop3` fetches emails via IMAP and parses them. -Example: +**Packaged installation** + +```bash +openproject run bundle exec rake redmine:email:receive_imap host='imap.gmail.com' username='test_user' password='password' port=993 ssl=true allow_override=type,project project=test_project +``` + +**Manual installation** + ```bash bundle exec rake redmine:email:receive_pop3 host='pop.gmail.com' username='test_user' password='password' port=995 allow_override=priority ``` diff --git a/docs/installation/manual/README.md b/docs/installation/manual/README.md index 9379e4d216..0d94dfaa80 100644 --- a/docs/installation/manual/README.md +++ b/docs/installation/manual/README.md @@ -1,6 +1,9 @@ -# Manual installation of OpenProject 6.1 with Apache on Ubuntu 14.04. LTS +# Manual installation of OpenProject 7.0 with Apache on Ubuntu 14.04. LTS -This tutorial helps you to deploy OpenProject 6.1. Please, aware that: +**IMPORTANT: We strongly recommend to use the [OpenProject installers](https://www.openproject.org/download-and-installation) (packaged installation). There is no real advantage installing OpenProject manually.** + + +This tutorial helps you to deploy OpenProject 7.0. Please, aware that: This guide requires that you have a clean Ubuntu 14.04 x64 installation with administrative rights. We have tested the installation guide on an @@ -144,7 +147,7 @@ with OpenProject. For more information, see https://github.com/opf/openproject-c ```bash [openproject@host] cd ~ -[openproject@host] git clone https://github.com/opf/openproject-ce.git --branch stable/6 --depth 1 +[openproject@host] git clone https://github.com/opf/openproject-ce.git --branch stable/7 --depth 1 [openproject@host] cd openproject-ce [openproject@host] gem install bundler [openproject@host] bundle install --deployment --without postgres sqlite development test therubyracer docker @@ -259,7 +262,7 @@ Then, we prepare apache and passenger: ```bash [root@host] apt-get install -y apache2 libcurl4-gnutls-dev \ - apache2-threaded-dev libapr1-dev \ + apache2-dev libapr1-dev \ libaprutil1-dev [root@ubuntu] chmod o+x "/home/openproject" ``` diff --git a/docs/installation/packaged/4-faq.md b/docs/installation/packaged/4-faq.md new file mode 100644 index 0000000000..790792728a --- /dev/null +++ b/docs/installation/packaged/4-faq.md @@ -0,0 +1,60 @@ +## Frequently asked questions - FAQ + +### How can I install an OpenProject plugin? + +Our [official installation page][install-page] has instructions on how to customize your OpenProject installation. +Please note that customization is not yet supported for Docker-based installations. + +[install-page]: https://www.openproject.org/download-and-installation/ + +### How to migrate from Bitnami to the official OpenProject installation packages? + +Please follow the following steps: +1. Make a dump of your bitnami MySQL database to export your data. You can refer to the [Bitnami documentation][bitnami-mysql]. +1. Make a dump of files your might have uploaded. You can refer to the [Bitnami documentation][bitnami-backup] to perform a full dump. +1. Copy both dumps to the server you want to install OpenProject on. +1. Install OpenProject usign the packaged installation. +1. Import the MySQL dump into your new MySQL database. You can get your MySQL configuration by running `sudo openproject config:get DATABASE_URL` +1. Extract the bitnami backup, and copy your file assets into the relevant directory (e.g. in `/var/db/openproject/files` for uploaded files) +1. Restart OpenProject + +[bitnami-mysql]: https://docs.bitnami.com/installer/components/mysql/ +[bitnami-backup]: https://docs.bitnami.com/installer/apps/openproject/ + +### Can I use NginX instead of Apache webserver? + +Yes, but you will lose the ability to enable Git/SVN repository integration. Note that the OpenProject installer does not support NginX, so you will have to ask to disable the Apache2 integration when running the installer, and then configure NginX yourself so that it forwards traffic to the OpenProject web process (listening by default on 127.0.0.1:6000). + +### Can I use PostgreSQL instead of MySQL? + +Yes, but you will need to setup the database by yourself, and then ask the installer to use an existing database and enter the host and credentials configuration to access it. + +### My favorite linux distribution is not listed. What can I do? + +You can either try the manual installation, or ask in the forum whether this could be added to the list of supported distributions. + +### What is the better option to run OpenProject in production environments: docker or linux packages? + +Linux packages are currently the most stable option, since they are regularly tested and provide an installer to help you configure your OpenProject installation. Docker images do not get the same level of testing. + +### How to upgrade my OpenProject installation? + +Please refer to the documentation at https://www.openproject.org/operations/upgrading/ + +### What skills should I have for the installation? + +If you use the packaged installation, you should have a basic knowledge of Linux and the command-line terminal. + +If you use the docker images, you need to be familiar with Docker and Docker volumes. + +### Why don't you support Windows? + +Ruby support on Windows is notoriously difficult, however you might be able to run the Docker image, or use the unofficial Windows stack provided by [Bitnami](https://bitnami.com/stack/openproject/installer). + +### How to backup and restore my OpenProject instalallation? + +Please refer to the documentation at https://www.openproject.org/operations/backup/ + +### How can I install a free SSL certificate using let's encrypt? + +TODO diff --git a/docs/operations/upgrading/manual/upgrading.md b/docs/operations/upgrading/manual/upgrading.md index ad390901d0..02a82c57b8 100644 --- a/docs/operations/upgrading/manual/upgrading.md +++ b/docs/operations/upgrading/manual/upgrading.md @@ -1,10 +1,14 @@ -# OpenProject 6.0.x to OpenProject 6.1 Debian/Ubuntu Upgrade Guide (Manual installation) +# OpenProject 6.x to OpenProject 7.x Debian/Ubuntu Upgrade Guide (Manual installation) -Please look at the steps in the section about the upgrade to OpenProject 6.0. OpenProject 6.x is being released under the branch `stable/6`. +Please look at the steps in the section about the upgrade to OpenProject 6.0. OpenProject 7.x is being released under the branch `stable/7`. The other steps are identical. + +### Frontend changes, bower is no longer required + +With OpenProject 7.0., we no longer depend on `bower` for some on the frontend assets. Please ensure you remove `/frontend/bower_components` and `/frontend/bower.json`. ### When running with MySQL: Required changes in sql_mode -If you're upgrading to OpenProject 6.1.x with a MySQL installation, you will need to update your database.yml to reflect some necessary changes to MySQL `sql_mode` made as part of the migration to Rails 5. Please see the `config/database.yml.example` file for more information. +If you're upgrading to OpenProject 7.x with a MySQL installation, you will need to update your database.yml to reflect some necessary changes to MySQL `sql_mode` made as part of the migration to Rails 5. Please see the `config/database.yml.example` file for more information. # OpenProject 5.0.x to OpenProject 6.0 Debian/Ubuntu Upgrade Guide diff --git a/features/timelines/timeline_work_package_show_view.feature b/features/timelines/timeline_work_package_show_view.feature deleted file mode 100644 index bc27b32a71..0000000000 --- a/features/timelines/timeline_work_package_show_view.feature +++ /dev/null @@ -1,59 +0,0 @@ -#-- copyright -# OpenProject is a project management system. -# Copyright (C) 2012-2017 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-2017 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. -#++ - -Feature: Timeline Work Package Show View - As a Project Member - I want edit planning elements to open in a new tab - - Background: - Given there is 1 user with: - | login | manager | - And there is a role "manager" - And the role "manager" may have the following rights: - | view_timelines | - | edit_timelines | - | view_work_packages | - And there is a project named "ecookbook" - And I am working in project "ecookbook" - And there is a timeline "Testline" for project "ecookbook" - And the project uses the following modules: - | timelines | - And the user "manager" is a "manager" - And there are the following work packages: - | Start date | Due date | description | responsible | Subject | - | 2012-01-01 | 2012-01-31 | #2 http://google.de | manager | January | - | 2012-02-01 | 2012-02-24 | Avocado Rincon | manager | February | - And I am already logged in as "manager" - - @javascript - Scenario: planning element click should show the plannin element - When I go to the page of the timeline "Testline" of the project called "ecookbook" - And I wait for timeline to load table - And I should see "January" - When I click on the Planning Element with name "January" - Then I should see "January" in the new window diff --git a/frontend/app/components/api/api-v3/hal-resources/error-resource.service.ts b/frontend/app/components/api/api-v3/hal-resources/error-resource.service.ts index 211a606827..cd9e7a2e53 100644 --- a/frontend/app/components/api/api-v3/hal-resources/error-resource.service.ts +++ b/frontend/app/components/api/api-v3/hal-resources/error-resource.service.ts @@ -27,7 +27,10 @@ //++ import {HalResource} from './hal-resource.service'; -import {opApiModule} from "../../../../angular-modules"; +import {opApiModule} from '../../../../angular-modules'; + +export const v3ErrorIdentifierQueryInvalid = 'urn:openproject-org:api:v3:errors:InvalidQuery'; +export const v3ErrorIdentifierMultipleErrors = 'urn:openproject-org:api:v3:errors:MultipleErrors'; export class ErrorResource extends HalResource { public errors:any[]; @@ -44,14 +47,14 @@ export class ErrorResource extends HalResource { } public isMultiErrorMessage() { - return this.errorIdentifier === 'urn:openproject-org:api:v3:errors:MultipleErrors'; + return this.errorIdentifier === v3ErrorIdentifierMultipleErrors; } public getInvolvedAttributes():string[] { var columns = []; if (this.details) { - columns = [{ details: this.details }] + columns = [{ details: this.details }]; } else if (this.errors) { columns = this.errors; @@ -60,7 +63,7 @@ export class ErrorResource extends HalResource { return columns.map(field => field.details.attribute); } - public getMessagesPerAttribute():{ [attribute:string]: string[] } { + public getMessagesPerAttribute():{ [attribute:string]:string[] } { let perAttribute:any = {}; if (this.details) { diff --git a/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.controller.ts b/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.controller.ts index 1c62542a12..e3c6b904ab 100644 --- a/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.controller.ts +++ b/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.controller.ts @@ -60,8 +60,8 @@ function wpContextMenuController($scope:any, }; $scope.triggerContextMenuAction = function (action:any, link:any) { - let table: WorkPackageTable; - let wp: WorkPackageResourceInterface; + let table:WorkPackageTable; + let wp:WorkPackageResourceInterface; switch (action) { case 'delete': @@ -72,6 +72,10 @@ function wpContextMenuController($scope:any, editSelectedWorkPackages(link); break; + case 'copy': + copySelectedWorkPackages(link); + break; + case 'relation-precedes': table = $scope.table; wp = $scope.row.object; @@ -125,6 +129,21 @@ function wpContextMenuController($scope:any, $state.transitionTo('work-packages.show.edit', params); } + function copySelectedWorkPackages(link:any) { + var selected = getSelectedWorkPackages(); + + if (selected.length > 1) { + $window.location.href = link; + return; + } + + var params = { + copiedFromWorkPackageId: selected[0].id + }; + + $state.transitionTo('work-packages.list.copy', params); + } + function getSelectedWorkPackages() { let workPackagefromContext = $scope.row.object; let selectedWorkPackages = wpTableSelection.getSelectedWorkPackages(); diff --git a/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html b/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html index c29cf756a0..ae9125b746 100644 --- a/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html +++ b/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html @@ -18,7 +18,9 @@
-
+
@@ -64,9 +66,9 @@
-
+
{ - this.QueryDm.findDefault({pageSize: 0}, projectIdentifier) - .then((query:QueryResource) => { - this.wpListInvalidQueryService.restoreQuery(query, form); + this.QueryFormDm.loadWithParams(queryProps, queryId, projectIdentifier) + .then(form => { + this.QueryDm.findDefault({pageSize: 0}, projectIdentifier) + .then((query:QueryResource) => { + this.wpListInvalidQueryService.restoreQuery(query, form); - query.results.pageSize = queryProps.pageSize; - query.results.total = 0; + query.results.pageSize = queryProps.pageSize; + query.results.total = 0; - if (queryId) { - query.id = queryId; - } + if (queryId) { + query.id = queryId; + } - this.states.table.context.doAndTransition('Query loaded', () => { - this.updateStatesFromQuery(query); - this.updateStatesFromForm(query, form); - }); + this.states.table.context.doAndTransition('Query loaded', () => { + this.updateStatesFromQuery(query); + this.updateStatesFromForm(query, form); + }); - deferred.resolve(query); - }); - }); + deferred.resolve(query); + }); + }); + } else { + this.NotificationsService.addError(error.message, []); + deferred.resolve(); + } return deferred.promise; } diff --git a/frontend/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.directive.ts b/frontend/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.directive.ts index c94f796ff4..2ff7271922 100644 --- a/frontend/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.directive.ts +++ b/frontend/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.directive.ts @@ -83,7 +83,7 @@ function wpRelationsAutocompleteDirective( function getIdentifier(workPackage:WorkPackageResourceInterface):string { if (workPackage) { - return _.escape(`#${workPackage.id} - ${workPackage.subject}`); + return `#${workPackage.id} - ${workPackage.subject}`; } else { return ''; } diff --git a/lib/api/decorators/link_object.rb b/lib/api/decorators/link_object.rb index b1f113c2e6..b9737ad6a2 100644 --- a/lib/api/decorators/link_object.rb +++ b/lib/api/decorators/link_object.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) diff --git a/lib/api/decorators/linked_resource.rb b/lib/api/decorators/linked_resource.rb new file mode 100644 index 0000000000..9012e18d0b --- /dev/null +++ b/lib/api/decorators/linked_resource.rb @@ -0,0 +1,88 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 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-2017 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 Decorators + module LinkedResource + def self.included(base) + base.extend ClassMethods + end + + def self.prepended(base) + base.extend ClassMethods + end + + def to_hash(*) + super.tap do |hash| + links = {} + representable_attrs.find_all do |dfn| + next unless dfn[:linked_resource] + name = dfn[:as] ? dfn[:as].(nil) : dfn.name + fragment = hash.delete(name) + next unless fragment + links[name] = fragment + end + + hash['_links'].merge!(links) + end + end + + def from_hash(hash, *) + return super unless hash['_links'] + + representable_attrs.find_all do |dfn| + next unless dfn[:linked_resource] + name = dfn[:as] ? dfn[:as].(nil) : dfn.name + fragment = hash['_links'].delete(name) + next unless fragment + + hash[name] = fragment + end + + super + end + + module ClassMethods + def linked_resource(name, + getter:, + setter:, + show_if: ->(*) { true }) + + property name, + exec_context: :decorator, + getter: getter, + setter: setter, + if: show_if, + linked_resource: true + end + end + end + end +end diff --git a/lib/api/v3/queries/filters/query_filter_instance_representer.rb b/lib/api/v3/queries/filters/query_filter_instance_representer.rb index 96831bb0ae..5bb1571870 100644 --- a/lib/api/v3/queries/filters/query_filter_instance_representer.rb +++ b/lib/api/v3/queries/filters/query_filter_instance_representer.rb @@ -33,45 +33,78 @@ module V3 module Queries module Filters class QueryFilterInstanceRepresenter < ::API::Decorators::Single + include API::Decorators::LinkedResource + def initialize(model) super(model, current_user: nil, embed_links: true) end - link :filter do - { - href: api_v3_paths.query_filter(converted_name), - title: name - } - end - - link :operator do - { - href: api_v3_paths.query_operator(CGI.escape(represented.operator)), - title: operator_name - } - end - - links :values do - next unless represented.ar_object_filter? - - represented.value_objects.map do |value_object| - { - href: api_v3_paths.send(value_object.class.name.demodulize.underscore, value_object.id), - title: value_object.name - } + def to_hash(*) + # We need to move the values property back from + # the links section. It got moved there because + # of the equally named linked_resource + super.tap do |hash| + unless represented.ar_object_filter? + hash['values'] = hash['_links'].delete('values') + end end end link :schema do + api_v3_paths.query_filter_instance_schema(converted_name) + end + + link :filter do { - href: api_v3_paths.query_filter_instance_schema(converted_name) + href: api_v3_paths.query_filter(converted_name), + title: name } end + linked_resource :operator, + getter: ->(*) { + hash = { + href: api_v3_paths.query_operator(CGI.escape(represented.operator)) + } + + hash[:title] = represented.operator_class.human_name if represented.operator_class.present? + hash + }, + setter: ->(value, **) { + next unless value + + represented.operator = ::API::Utilities::ResourceLinkParser.parse_id value["href"], + property: 'operator', + expected_version: '3', + expected_namespace: 'queries/operators' + } + + linked_resource :values, + getter: ->(*) { + represented.value_objects.map do |value_object| + { + href: api_v3_paths.send(value_object.class.name.demodulize.underscore, value_object.id), + title: value_object.name + } + end + }, + setter: ->(values, **) { + next unless values + + represented.values = values.map do |value| + ::API::Utilities::ResourceLinkParser.parse(value["href"])[:id] + end + }, + show_if: ->(*) { represented.ar_object_filter? } + property :name, - exec_context: :decorator + exec_context: :decorator, + writeable: false - property :values, + # Need to use property_values instead of values + # to prevent name clashes with the values link + property :property_values, + as: :values, if: ->(*) { !represented.ar_object_filter? }, exec_context: :decorator, show_nil: true @@ -82,7 +115,7 @@ def name represented.human_name end - def values + def property_values if represented.respond_to?(:custom_field) && represented.custom_field.field_format == 'bool' represented.values.map do |value| @@ -97,20 +130,31 @@ def values end end + def property_values=(vals) + represented.values = if represented.respond_to?(:custom_field) && + represented.custom_field.field_format == 'bool' + vals.map do |value| + if value + CustomValue::BoolStrategy::DB_VALUE_TRUE + else + CustomValue::BoolStrategy::DB_VALUE_FALSE + end + end + else + vals + end + end + def _type "#{converted_name.camelize}QueryFilter" end def converted_name - convert_attribute(represented.name) - end - - def operator_name - represented.operator_class.human_name if represented.operator_class.present? + ::API::Utilities::PropertyNameConverter.from_ar_name(represented.name) end - def convert_attribute(attribute) - ::API::Utilities::PropertyNameConverter.from_ar_name(attribute) + def query_filter_instance_links_representer(represented) + ::API::V3::Queries::Filters::QueryFilterInstanceLinksRepresenter.new represented, current_user: current_user end end end diff --git a/lib/api/v3/queries/query_serialization.rb b/lib/api/v3/queries/query_serialization.rb index 7646bdd17f..ab298ce054 100644 --- a/lib/api/v3/queries/query_serialization.rb +++ b/lib/api/v3/queries/query_serialization.rb @@ -58,10 +58,13 @@ def filters=(filters_hash) filters_hash.each do |filter_attributes| name = get_filter_name filter_attributes - operator = get_filter_operator filter_attributes - if name && operator - represented.add_filter name, operator, get_filter_values(filter_attributes) + filter = represented.filter_for name + if filter + filter_representer = ::API::V3::Queries::Filters::QueryFilterInstanceRepresenter.new(filter) + + filter = filter_representer.from_hash filter_attributes + represented.filters << filter else raise API::Errors::InvalidRequestBody, "Could not read filter from: #{filter_attributes}" end @@ -97,19 +100,6 @@ def get_filter_name(filter_attributes) ::API::Utilities::QueryFiltersNameConverter.to_ar_name id, refer_to_ids: true if id end - def get_filter_operator(filter_attributes) - op_href = filter_attributes.dig("_links", "operator", "href") - - id_from_href "queries/operators", op_href - end - - def get_filter_values(filter_attributes) - filter_attributes["values"] || - Array(filter_attributes.dig("_links", "values")) - .map { |value| id_from_href nil, value["href"] } - .compact - end - def initialize_links!(attributes) set_project_id(attributes) set_group_by(attributes) diff --git a/lib/open_project/authentication/session_expiry.rb b/lib/open_project/authentication/session_expiry.rb new file mode 100644 index 0000000000..e985b4c69f --- /dev/null +++ b/lib/open_project/authentication/session_expiry.rb @@ -0,0 +1,20 @@ +module OpenProject + module Authentication + module SessionExpiry + def session_ttl_enabled? + Setting.session_ttl_enabled? && Setting.session_ttl.to_i >= 5 + end + + def session_ttl_minutes + Setting.session_ttl.to_i.minutes + end + + def session_ttl_expired? + # Only when the TTL setting exists + return false unless session_ttl_enabled? + + session[:updated_at].nil? || (session[:updated_at] + session_ttl_minutes) < Time.now + end + end + end +end diff --git a/lib/open_project/authentication/strategies/warden/session.rb b/lib/open_project/authentication/strategies/warden/session.rb index 9cb47db1ba..3929bf1726 100644 --- a/lib/open_project/authentication/strategies/warden/session.rb +++ b/lib/open_project/authentication/strategies/warden/session.rb @@ -1,3 +1,5 @@ +require 'open_project/authentication/session_expiry' + module OpenProject module Authentication module Strategies @@ -7,8 +9,10 @@ module Warden # not been unified in terms of Warden strategies and is only locally # applied to the API v3. class Session < ::Warden::Strategies::Base + include ::OpenProject::Authentication::SessionExpiry + def valid? - session + session && !session_ttl_expired? end def authenticate! diff --git a/lib/open_project/version.rb b/lib/open_project/version.rb index 96768f53ac..c9958bf145 100644 --- a/lib/open_project/version.rb +++ b/lib/open_project/version.rb @@ -34,7 +34,7 @@ module OpenProject module VERSION #:nodoc: MAJOR = 7 MINOR = 0 - PATCH = 2 + PATCH = 3 TINY = PATCH # Redmine compat # Used by semver to define the special version (if any). diff --git a/lib/redmine/i18n.rb b/lib/redmine/i18n.rb index e24691e8f1..d1fba6987d 100644 --- a/lib/redmine/i18n.rb +++ b/lib/redmine/i18n.rb @@ -153,6 +153,7 @@ def all_languages ## # Returns the given language if it is valid or nil otherwise. def find_language(lang) + return nil unless lang =~ /[a-z-]+/i valid_languages.detect { |l| l =~ /#{lang}/i } if lang.present? end diff --git a/lib/tasks/ldap.rake b/lib/tasks/ldap.rake index 0773e2351d..44e60eedc9 100644 --- a/lib/tasks/ldap.rake +++ b/lib/tasks/ldap.rake @@ -103,7 +103,7 @@ namespace :ldap do account: url.user, account_password: url.password, base_dn: url.dn, - onthefly_register: !!args[:onthefly], + onthefly_register: !!ActiveModel::Type::Boolean.new.cast(args[:onthefly]), attr_login: args[:map_login], attr_firstname: args[:map_firstname], attr_lastname: args[:map_lastname], diff --git a/spec/controllers/repositories_controller_spec.rb b/spec/controllers/repositories_controller_spec.rb index 3dbaf1c391..5f3a2ea698 100644 --- a/spec/controllers/repositories_controller_spec.rb +++ b/spec/controllers/repositories_controller_spec.rb @@ -35,10 +35,11 @@ allow(Project).to receive(:find).and_return(project) project end - let(:user) { + let(:user) do FactoryGirl.create(:user, member_in_project: project, member_through_role: role) - } + end + let(:role) { FactoryGirl.create(:role, permissions: []) } let (:url) { 'file:///tmp/something/does/not/exist.svn' } let(:repository) do @@ -167,7 +168,7 @@ end end - describe 'with filesystem repository' do + describe 'with subversion repository' do with_subversion_repository do |repo_dir| let(:root_url) { repo_dir } let(:url) { "file://#{root_url}" } @@ -332,4 +333,22 @@ end end end + + describe 'when not being logged in' do + let(:anonymous) { FactoryGirl.build_stubbed(:anonymous) } + + before do + login_as(anonymous) + end + + describe '#show' do + it 'redirects to login while preserving the path' do + params = { path: 'aDir/within/aDir', rev: '42', project_id: project.id } + get :show, params: params + + expect(response) + .to redirect_to signin_path(back_url: show_revisions_path_project_repository_url(params)) + end + end + end end diff --git a/spec/features/security/session_ttl_spec.rb b/spec/features/security/session_ttl_spec.rb new file mode 100644 index 0000000000..fb6f8c971d --- /dev/null +++ b/spec/features/security/session_ttl_spec.rb @@ -0,0 +1,72 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 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-2017 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 'Session TTL', + with_settings: {session_ttl_enabled?: true, session_ttl: '10'}, + type: :feature do + let!(:user) {FactoryGirl.create :admin} + let!(:work_package) {FactoryGirl.create :work_package} + + before do + login_with(user.login, user.password) + end + + def expire! + page.set_rack_session(updated_at: Time.now - 1.hour) + end + + describe 'outdated TTL on Rails request' do + it 'expires on the next Rails request' do + visit '/my/account' + expect(page).to have_selector('.form--field-container', text: user.login) + + # Expire the session + expire! + + visit '/' + expect(page).to have_selector('.action-login') + end + end + + describe 'outdated TTL on API request' do + it 'expires on the next APIv3 request' do + visit "/api/v3/work_packages/#{work_package.id}" + + body = JSON.parse(page.body) + expect(body['id']).to eq(work_package.id) + + # Expire the session + expire! + visit "/api/v3/work_packages/#{work_package.id}" + + expect(page.body).to eq('unauthorized') + end + end +end diff --git a/spec/features/work_packages/details/inplace_editor/custom_field_spec.rb b/spec/features/work_packages/details/inplace_editor/custom_field_spec.rb index b8ee1a14dc..491a4f6739 100644 --- a/spec/features/work_packages/details/inplace_editor/custom_field_spec.rb +++ b/spec/features/work_packages/details/inplace_editor/custom_field_spec.rb @@ -37,6 +37,7 @@ def expect_update(value, update_args) wp_field.set_value value wp_field.submit_by_enter if wp_field.field_type == 'input' wp_page.expect_notification(update_args) + wp_page.dismiss_notification! end describe 'long text' do @@ -107,7 +108,6 @@ def custom_value(value) wp_page.expect_attributes customField1: 'bar', customField2: 'X' - wp_page.dismiss_notification! end end diff --git a/spec/features/work_packages/table/context_menu_spec.rb b/spec/features/work_packages/table/context_menu_spec.rb index 4faa97266e..4d9d674ef9 100644 --- a/spec/features/work_packages/table/context_menu_spec.rb +++ b/spec/features/work_packages/table/context_menu_spec.rb @@ -57,8 +57,9 @@ def goto_context_menu # Open Copy goto_context_menu menu.choose('Copy') - expect(page).to have_selector('h2', text: I18n.t(:button_copy)) - expect(page).to have_selector('a.issue', text: "##{work_package.id}") + # Split view open in copy state + expect(page).to have_selector('.work-packages--details', text: "New #{work_package.type}") + expect(page).to have_field('wp-new-inline-edit--field-subject', with: work_package.subject) # Open Delete goto_context_menu diff --git a/spec/lib/redmine/i18n_spec.rb b/spec/lib/redmine/i18n_spec.rb index fbfbd79026..6c9a3574b8 100644 --- a/spec/lib/redmine/i18n_spec.rb +++ b/spec/lib/redmine/i18n_spec.rb @@ -150,6 +150,12 @@ module OpenProject it 'can be found by uppercase if it is active' do expect(find_language(:DE)).to eql :de end + + it 'is nil if non valid string is passed' do + expect(find_language('*')).to be_nil + expect(find_language('78445')).to be_nil + expect(find_language('/)(')).to be_nil + end end describe 'link_translation' do diff --git a/spec/models/work_package_spec.rb b/spec/models/work_package_spec.rb index cb1dd1eaa9..e1fad146bd 100644 --- a/spec/models/work_package_spec.rb +++ b/spec/models/work_package_spec.rb @@ -462,34 +462,32 @@ def stub_shared_versions(v = nil) describe '#copy_from' do let(:type) { FactoryGirl.create(:type_standard) } let(:project) { FactoryGirl.create(:project, types: [type]) } - let(:custom_field) { + let(:custom_field) do FactoryGirl.create(:work_package_custom_field, name: 'Database', field_format: 'list', possible_values: ['MySQL', 'PostgreSQL', 'Oracle'], is_required: true) - } + end + let(:bool_custom_field) do + FactoryGirl.create(:bool_wp_custom_field) + end let(:source) { FactoryGirl.build(:work_package) } let(:sink) { FactoryGirl.build(:work_package) } - before do - def self.change_custom_field_value(work_package, value) - work_package.custom_field_values = { custom_field.id => value } unless value.nil? - work_package.save - end - end - before do project.work_package_custom_fields << custom_field type.custom_fields << custom_field - source.save - end + project.work_package_custom_fields << bool_custom_field + type.custom_fields << bool_custom_field - before do source.project_id = project.id - change_custom_field_value(source, 'MySQL') + + source.custom_field_values = { custom_field.id => 'MySQL', + bool_custom_field.id => 'f' } + source.save end shared_examples_for 'work package copy' do @@ -530,18 +528,23 @@ def self.change_custom_field_value(work_package, value) shared_examples_for 'work package copy with custom field' do it_behaves_like 'work package copy' - context 'custom_field' do + context 'list custom_field' do subject { sink.custom_value_for(custom_field.id).value } it { is_expected.to eq('MySQL') } end + + context 'bool custom_field' do + subject { sink.custom_value_for(bool_custom_field.id).value } + + it { is_expected.to eq('f') } + end end context 'with project' do let(:project_id) { source.project_id } describe 'should copy project' do - before { sink.copy_from(source) } it_behaves_like 'work package copy with custom field' diff --git a/spec/requests/api/v3/queries/create_form_api_spec.rb b/spec/requests/api/v3/queries/create_form_api_spec.rb index d24fa68982..2a4604e4ba 100644 --- a/spec/requests/api/v3/queries/create_form_api_spec.rb +++ b/spec/requests/api/v3/queries/create_form_api_spec.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2015 the OpenProject Foundation (OPF) @@ -181,10 +182,18 @@ filters = [ { "_links" => { - "filter" => { "href" => "/api/v3/queries/filters/status" }, - "operator" => { "href" => "/api/v3/queries/operators/%3D" }, + "filter" => { + "href" => "/api/v3/queries/filters/status" + }, + "operator" => { + "href" => "/api/v3/queries/operators/%3D", + "title" => 'is' + }, "values" => [ - { "href" => "/api/v3/statuses/#{status.id}" } + { + "href" => "/api/v3/statuses/#{status.id}", + "title" => status.name + } ] } } diff --git a/spec/requests/api/v3/queries/update_form_api_spec.rb b/spec/requests/api/v3/queries/update_form_api_spec.rb index 39c5bbda0d..0dca721c4c 100644 --- a/spec/requests/api/v3/queries/update_form_api_spec.rb +++ b/spec/requests/api/v3/queries/update_form_api_spec.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2015 the OpenProject Foundation (OPF) @@ -35,7 +36,7 @@ let(:path) { api_v3_paths.query_form(query.id) } let(:user) { FactoryGirl.create(:admin) } let(:role) { FactoryGirl.create :existing_role, permissions: permissions } - let(:permissions) { [:view_work_packages, :manage_public_queries] } + let(:permissions) { %i(view_work_packages manage_public_queries) } let!(:project) { FactoryGirl.create(:project_with_types) } @@ -188,10 +189,18 @@ filters = [ { "_links" => { - "filter" => { "href" => "/api/v3/queries/filters/status" }, - "operator" => { "href" => "/api/v3/queries/operators/%3D" }, + "filter" => { + "href" => "/api/v3/queries/filters/status" + }, + "operator" => { + "href" => "/api/v3/queries/operators/%3D", + "title" => 'is' + }, "values" => [ - { "href" => "/api/v3/statuses/#{status.id}" } + { + "href" => "/api/v3/statuses/#{status.id}", + "title" => status.name + } ] } } diff --git a/spec/services/set_localization_service_spec.rb b/spec/services/set_localization_service_spec.rb index c5d5eda54b..f011d0cd24 100644 --- a/spec/services/set_localization_service_spec.rb +++ b/spec/services/set_localization_service_spec.rb @@ -106,6 +106,12 @@ def expect_locale(locale) it_behaves_like "falls back to the instane's default language" end + + context 'with wildcard header set' do + let(:http_accept_language) { '*' } + + it_behaves_like "falls back to the instane's default language" + end end end @@ -119,5 +125,11 @@ def expect_locale(locale) it_behaves_like "falls back to the instane's default language" end + + context 'with a wildcard header set' do + let(:http_accept_language) { '*' } + + it_behaves_like "falls back to the instane's default language" + end end end diff --git a/spec/support/work_packages/work_package_field.rb b/spec/support/work_packages/work_package_field.rb index ea3d475204..1480795056 100644 --- a/spec/support/work_packages/work_package_field.rb +++ b/spec/support/work_packages/work_package_field.rb @@ -36,8 +36,10 @@ def element ## # Activate the field and check it opened correctly def activate! - element.find(trigger_link_selector).click - expect_active! + retry_block do + element.find(trigger_link_selector).click + expect_active! + end end def expect_state!(open:) diff --git a/vendored-plugins/openproject-auth_plugins/lib/open_project/auth_plugins/version.rb b/vendored-plugins/openproject-auth_plugins/lib/open_project/auth_plugins/version.rb index a4b4f838da..f129aecd57 100644 --- a/vendored-plugins/openproject-auth_plugins/lib/open_project/auth_plugins/version.rb +++ b/vendored-plugins/openproject-auth_plugins/lib/open_project/auth_plugins/version.rb @@ -29,6 +29,6 @@ module OpenProject module AuthPlugins - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-backlogs/lib/open_project/backlogs/version.rb b/vendored-plugins/openproject-backlogs/lib/open_project/backlogs/version.rb index bc3b38b683..627f262bac 100644 --- a/vendored-plugins/openproject-backlogs/lib/open_project/backlogs/version.rb +++ b/vendored-plugins/openproject-backlogs/lib/open_project/backlogs/version.rb @@ -35,6 +35,6 @@ module OpenProject module Backlogs - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-costs/lib/open_project/costs/version.rb b/vendored-plugins/openproject-costs/lib/open_project/costs/version.rb index 82b0742dfd..c5329e3236 100644 --- a/vendored-plugins/openproject-costs/lib/open_project/costs/version.rb +++ b/vendored-plugins/openproject-costs/lib/open_project/costs/version.rb @@ -19,6 +19,6 @@ module OpenProject module Costs - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-documents/lib/open_project/documents/version.rb b/vendored-plugins/openproject-documents/lib/open_project/documents/version.rb index 5f8c9ca051..ce3a7ee22f 100644 --- a/vendored-plugins/openproject-documents/lib/open_project/documents/version.rb +++ b/vendored-plugins/openproject-documents/lib/open_project/documents/version.rb @@ -31,6 +31,6 @@ module OpenProject module Documents - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-github_integration/lib/open_project/github_integration/version.rb b/vendored-plugins/openproject-github_integration/lib/open_project/github_integration/version.rb index 7c91b2accd..fe788a0584 100644 --- a/vendored-plugins/openproject-github_integration/lib/open_project/github_integration/version.rb +++ b/vendored-plugins/openproject-github_integration/lib/open_project/github_integration/version.rb @@ -14,6 +14,6 @@ module OpenProject module GithubIntegration - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-global_roles/lib/open_project/global_roles/version.rb b/vendored-plugins/openproject-global_roles/lib/open_project/global_roles/version.rb index 8eb7033d14..a4fd76567d 100644 --- a/vendored-plugins/openproject-global_roles/lib/open_project/global_roles/version.rb +++ b/vendored-plugins/openproject-global_roles/lib/open_project/global_roles/version.rb @@ -19,6 +19,6 @@ module OpenProject module GlobalRoles - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-local_avatars/lib/open_project/local_avatars/version.rb b/vendored-plugins/openproject-local_avatars/lib/open_project/local_avatars/version.rb index 04f49b20b1..9e6b7db80a 100644 --- a/vendored-plugins/openproject-local_avatars/lib/open_project/local_avatars/version.rb +++ b/vendored-plugins/openproject-local_avatars/lib/open_project/local_avatars/version.rb @@ -1,5 +1,5 @@ module OpenProject module LocalAvatars - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-meeting/lib/open_project/meeting/version.rb b/vendored-plugins/openproject-meeting/lib/open_project/meeting/version.rb index a375551db1..f6478c310e 100644 --- a/vendored-plugins/openproject-meeting/lib/open_project/meeting/version.rb +++ b/vendored-plugins/openproject-meeting/lib/open_project/meeting/version.rb @@ -20,6 +20,6 @@ module OpenProject module Meeting - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-my_project_page/lib/open_project/my_project_page/version.rb b/vendored-plugins/openproject-my_project_page/lib/open_project/my_project_page/version.rb index df82c5a77d..eb491eab71 100644 --- a/vendored-plugins/openproject-my_project_page/lib/open_project/my_project_page/version.rb +++ b/vendored-plugins/openproject-my_project_page/lib/open_project/my_project_page/version.rb @@ -20,6 +20,6 @@ module OpenProject module MyProjectPage - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-openid_connect/lib/open_project/openid_connect/version.rb b/vendored-plugins/openproject-openid_connect/lib/open_project/openid_connect/version.rb index 4d1984e50b..745ee98c38 100644 --- a/vendored-plugins/openproject-openid_connect/lib/open_project/openid_connect/version.rb +++ b/vendored-plugins/openproject-openid_connect/lib/open_project/openid_connect/version.rb @@ -1,5 +1,5 @@ module OpenProject module OpenIDConnect - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-pdf_export/lib/open_project/pdf_export/version.rb b/vendored-plugins/openproject-pdf_export/lib/open_project/pdf_export/version.rb index c80b3644db..2eeaa3fa31 100644 --- a/vendored-plugins/openproject-pdf_export/lib/open_project/pdf_export/version.rb +++ b/vendored-plugins/openproject-pdf_export/lib/open_project/pdf_export/version.rb @@ -25,6 +25,6 @@ module OpenProject module PdfExport - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-reporting/lib/open_project/reporting/version.rb b/vendored-plugins/openproject-reporting/lib/open_project/reporting/version.rb index bcb77e9bb2..846e5b9d2a 100644 --- a/vendored-plugins/openproject-reporting/lib/open_project/reporting/version.rb +++ b/vendored-plugins/openproject-reporting/lib/open_project/reporting/version.rb @@ -19,6 +19,6 @@ module OpenProject module Reporting - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-webhooks/lib/open_project/webhooks/version.rb b/vendored-plugins/openproject-webhooks/lib/open_project/webhooks/version.rb index a852e2600f..2451ccc68e 100644 --- a/vendored-plugins/openproject-webhooks/lib/open_project/webhooks/version.rb +++ b/vendored-plugins/openproject-webhooks/lib/open_project/webhooks/version.rb @@ -14,6 +14,6 @@ module OpenProject module Webhooks - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/openproject-xls_export/lib/open_project/xls_export/version.rb b/vendored-plugins/openproject-xls_export/lib/open_project/xls_export/version.rb index 12e77f4f08..90bdb74bcb 100644 --- a/vendored-plugins/openproject-xls_export/lib/open_project/xls_export/version.rb +++ b/vendored-plugins/openproject-xls_export/lib/open_project/xls_export/version.rb @@ -1,5 +1,5 @@ module OpenProject module XlsExport - VERSION = "7.0.2" + VERSION = "7.0.3" end end diff --git a/vendored-plugins/reporting_engine/lib/reporting_engine/version.rb b/vendored-plugins/reporting_engine/lib/reporting_engine/version.rb index e474dfbb77..afb244f3f9 100644 --- a/vendored-plugins/reporting_engine/lib/reporting_engine/version.rb +++ b/vendored-plugins/reporting_engine/lib/reporting_engine/version.rb @@ -18,5 +18,5 @@ #++ module ReportingEngine - VERSION = "7.0.2" + VERSION = "7.0.3" end