diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a6a9847264..64a87d0f36 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,14 +1,3 @@ -# Frontend rules -*.js @opf/frontend -*.ts @opf/frontend - -# Backend rules -/app/ @opf/backend -/config/ @opf/backend -/lib/ @opf/backend -/lib_static/ @opf/backend -/modules/ @opf/backend - # docs rules /docs/ @opf/doc-writers @@ -16,4 +5,4 @@ /docs/development @opf/tech-writers /docs/installation-and-operations @opf/tech-writers /docs/system-admin-guide @opf/tech-writers -/docs/api @opf/tech-writers @opf/backend +/docs/api @opf/tech-writers diff --git a/Gemfile b/Gemfile index 324cd153bf..8b4059f734 100644 --- a/Gemfile +++ b/Gemfile @@ -88,7 +88,7 @@ gem 'deckar01-task_list', '~> 2.3.1' # Requires escape-utils for faster escaping gem 'escape_utils', '~> 1.3' # Syntax highlighting used in html-pipeline with rouge -gem 'rouge', '~> 3.30.0' +gem 'rouge', '~> 4.0.0' # HTML sanitization used for html-pipeline gem 'sanitize', '~> 6.0.0' # HTML autolinking for mails and urls (replaces autolink) @@ -120,7 +120,7 @@ gem 'daemons' gem 'delayed_cron_job', '~> 0.9.0' gem 'delayed_job_active_record', '~> 4.1.5' -gem 'rack-protection', '~> 2.2.0' +gem 'rack-protection', '~> 3.0.0' # Rack::Attack is a rack middleware to protect your web app from bad clients. # It allows whitelisting, blacklisting, throttling, and tracking based @@ -157,7 +157,7 @@ gem 'matrix', '~> 0.4.2' gem 'cells-erb', '~> 0.1.0' gem 'cells-rails', '~> 0.1.4' -gem 'meta-tags', '~> 2.17.0' +gem 'meta-tags', '~> 2.18.0' gem "paper_trail", "~> 12.3" @@ -216,7 +216,7 @@ group :test do # and other niceties gem 'test-prof', '~> 1.0.0' - gem 'database_cleaner', '~> 2.0' + gem 'database_cleaner', '~> 2.0' # only useful for legacy_spec gem 'rack_session_access' gem 'rspec', '~> 3.11.0' # also add to development group, so "spec" rake task gets loaded @@ -235,7 +235,7 @@ group :test do gem 'capybara', '~> 3.37.0' gem 'capybara-screenshot', '~> 1.0.17' gem 'selenium-webdriver', '~> 4.0' - gem 'webdrivers', '~> 5.0.0' + gem 'webdrivers', '~> 5.2.0' gem 'fuubar', '~> 2.5.0' gem 'timecop', '~> 0.9.0' diff --git a/Gemfile.lock b/Gemfile.lock index eacc96fc6a..5c5f640440 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -291,8 +291,8 @@ GEM awesome_nested_set (3.5.0) activerecord (>= 4.0.0, < 7.1) aws-eventstream (1.2.0) - aws-partitions (1.640.0) - aws-sdk-core (3.158.0) + aws-partitions (1.644.0) + aws-sdk-core (3.159.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -310,7 +310,7 @@ GEM aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.18) - bindata (2.4.10) + bindata (2.4.12) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) bootsnap (1.13.0) @@ -409,13 +409,12 @@ GEM declarative-option (0.1.0) delayed_cron_job (0.9.0) fugit (>= 1.5) - delayed_job (4.1.10) + delayed_job (4.1.11) activesupport (>= 3.0, < 8.0) delayed_job_active_record (4.1.7) activerecord (>= 3.0, < 8.0) delayed_job (>= 3.0, < 5) diff-lcs (1.5.0) - digest (3.1.0) disposable (0.6.3) declarative (>= 0.0.9, < 1.0.0) representable (>= 3.1.1, < 4) @@ -427,7 +426,7 @@ GEM dotenv-rails (2.8.1) dotenv (= 2.8.1) railties (>= 3.2) - dry-container (0.10.1) + dry-container (0.11.0) concurrent-ruby (~> 1.0) dry-core (0.8.1) concurrent-ruby (~> 1.0) @@ -465,7 +464,7 @@ GEM tzinfo eventmachine (1.2.7) eventmachine_httpserver (0.2.1) - excon (0.92.4) + excon (0.93.0) factory_bot (6.2.1) activesupport (>= 5.0.0) factory_bot_rails (6.2.0) @@ -519,7 +518,7 @@ GEM formatador (1.1.0) friendly_id (5.4.2) activerecord (>= 4.0.0) - fugit (1.6.0) + fugit (1.7.1) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) fuubar (2.5.1) @@ -591,7 +590,7 @@ GEM open4 (~> 1.0) launchy (2.5.0) addressable (~> 2.7) - lefthook (1.1.1) + lefthook (1.1.2) letter_opener (1.8.1) launchy (>= 2.2, < 3) listen (3.7.1) @@ -612,7 +611,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.18.0) + loofah (2.19.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -620,7 +619,7 @@ GEM marcel (1.0.2) matrix (0.4.2) messagebird-rest (1.4.2) - meta-tags (2.17.0) + meta-tags (2.18.0) actionpack (>= 3.2.0, < 7.1) method_source (1.0.0) mime-types (3.4.1) @@ -631,7 +630,7 @@ GEM mini_portile2 (2.8.0) minisyntax (0.2.5) minitest (5.16.3) - msgpack (1.5.6) + msgpack (1.6.0) multi_json (1.15.0) multipart-post (2.2.3) mustermann (3.0.0) @@ -639,28 +638,22 @@ GEM mustermann-grape (1.0.2) mustermann (>= 1.0.0) nap (1.1.0) - net-imap (0.2.3) - digest + net-imap (0.3.1) net-protocol - strscan net-ldap (0.17.1) - net-pop (0.1.1) - digest + net-pop (0.1.2) net-protocol - timeout net-protocol (0.1.3) timeout - net-smtp (0.3.1) - digest + net-smtp (0.3.2) net-protocol - timeout netrc (0.11.0) nio4r (2.5.8) no_proxy_fix (0.1.2) nokogiri (1.13.8) mini_portile2 (~> 2.8.0) racc (~> 1.4) - octokit (5.5.0) + octokit (5.6.1) faraday (>= 1, < 3) sawyer (~> 0.9) oj (3.13.21) @@ -698,7 +691,7 @@ GEM hashery (~> 2.0) ruby-rc4 ttfunk - pg (1.4.3) + pg (1.4.4) plaintext (0.3.4) activesupport (> 2.2.1) nokogiri (~> 1.10, >= 1.10.4) @@ -759,7 +752,7 @@ GEM httpclient json-jwt (>= 1.11.0) rack (>= 2.1.0) - rack-protection (2.2.2) + rack-protection (3.0.2) rack rack-test (2.0.2) rack (>= 1.3) @@ -812,7 +805,7 @@ GEM recaptcha (5.12.3) json redcarpet (3.5.1) - regexp_parser (2.5.0) + regexp_parser (2.6.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -833,7 +826,7 @@ GEM roar (1.1.1) representable (~> 3.0) rotp (6.2.0) - rouge (3.30.0) + rouge (4.0.0) rspec (3.11.0) rspec-core (~> 3.11.0) rspec-expectations (~> 3.11.0) @@ -938,7 +931,6 @@ GEM stackprof (0.2.21) stringex (2.8.5) stringio (3.0.2) - strscan (3.0.4) structured_warnings (0.4.0) svg-graph (2.2.1) swd (1.3.0) @@ -980,7 +972,7 @@ GEM rack (>= 2.0.9) warden-basic_auth (0.2.1) warden (~> 1.2) - webdrivers (5.0.0) + webdrivers (5.2.0) nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (~> 4.0) @@ -1000,7 +992,7 @@ GEM activerecord (>= 4.2) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.0) + zeitwerk (2.6.1) PLATFORMS ruby @@ -1075,7 +1067,7 @@ DEPENDENCIES livingstyleguide (~> 2.1.0) lograge (~> 0.12.0) matrix (~> 0.4.2) - meta-tags (~> 2.17.0) + meta-tags (~> 2.18.0) mini_magick (~> 4.11.0) multi_json (~> 1.15.0) my_page! @@ -1127,7 +1119,7 @@ DEPENDENCIES rack-attack (~> 6.6.0) rack-cors (~> 1.1.1) rack-mini-profiler - rack-protection (~> 2.2.0) + rack-protection (~> 3.0.0) rack-test (~> 2.0.0) rack-timeout (~> 0.6.3) rack_session_access @@ -1141,7 +1133,7 @@ DEPENDENCIES retriable (~> 3.1.1) rinku (~> 2.0.4) roar (~> 1.1.0) - rouge (~> 3.30.0) + rouge (~> 4.0.0) rspec (~> 3.11.0) rspec-rails (= 6.0.0.rc1) rspec-retry (~> 0.6.1) @@ -1179,7 +1171,7 @@ DEPENDENCIES validate_url warden (~> 1.2) warden-basic_auth (~> 0.2.1) - webdrivers (~> 5.0.0) + webdrivers (~> 5.2.0) webmock (~> 3.12) will_paginate (~> 3.3.0) with_advisory_lock (~> 4.6.0) diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 6352bb46f8..dcff185856 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -69,12 +69,12 @@ class WorkflowsController < ApplicationController @source_type = if params[:source_type_id].blank? || params[:source_type_id] == 'any' nil else - ::Type.find_by(id: params[:source_type_id].to_i) + ::Type.find(params[:source_type_id]) end @source_role = if params[:source_role_id].blank? || params[:source_role_id] == 'any' nil else - Role.find_by(id: params[:source_role_id].to_i) + Role.find(params[:source_role_id]) end @target_types = params[:target_type_ids].blank? ? nil : ::Type.where(id: params[:target_type_ids]) diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index a1e8b515d2..c22de82448 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,6 +1,7 @@ class NotificationSetting < ApplicationRecord WATCHED = :watched - INVOLVED = :involved + ASSIGNEE = :assignee + RESPONSIBLE = :responsible MENTIONED = :mentioned WORK_PACKAGE_CREATED = :work_package_created WORK_PACKAGE_COMMENTED = :work_package_commented @@ -19,7 +20,8 @@ class NotificationSetting < ApplicationRecord def self.all_settings [ WATCHED, - INVOLVED, + ASSIGNEE, + RESPONSIBLE, MENTIONED, WORK_PACKAGE_CREATED, WORK_PACKAGE_COMMENTED, diff --git a/app/models/project.rb b/app/models/project.rb index d70609ad99..b06ce748d1 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -258,17 +258,6 @@ class Project < ApplicationRecord @assignable_versions ||= shared_versions.references(:project).with_status_open.order_by_semver_name.to_a end - # Returns a hash of project users grouped by role - def users_by_role - members.includes(:principal, :roles).inject({}) do |h, m| - m.roles.each do |r| - h[r] ||= [] - h[r] << m.principal - end - h - end - end - # Returns an AR scope of all custom fields enabled for project's work packages # (explicitly associated custom fields and custom fields enabled for all projects) def all_work_package_custom_fields diff --git a/app/models/user.rb b/app/models/user.rb index c344e00bd0..e6eef901c2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -164,7 +164,6 @@ class User < Principal def reload(*args) @name = nil - @projects_by_role = nil @user_allowed_service = nil @project_role_cache = nil @@ -480,37 +479,6 @@ class User < Principal roles_for_project(project).any?(&:member?) end - # Returns a hash of user's projects grouped by roles - def projects_by_role - return @projects_by_role if @projects_by_role - - @projects_by_role = Hash.new { |h, k| h[k] = [] } - memberships.each do |membership| - membership.roles.each do |role| - @projects_by_role[role] << membership.project if membership.project - end - end - @projects_by_role.each do |_role, projects| - projects.uniq! - end - - @projects_by_role - end - - # Returns true if user is arg or belongs to arg - # rubocop:disable Naming/PredicateName - def is_or_belongs_to?(arg) - case arg - when User - self == arg - when Group - arg.users.include?(self) - else - false - end - end - # rubocop:enable Naming/PredicateName - def self.allowed(action, project) Authorization.users(action, project) end @@ -638,7 +606,7 @@ class User < Principal separators = Regexp.escape(Setting.mail_suffix_separators) recipient, domain = mail.split('@').map { |part| Regexp.escape(part) } skip_suffix_check = recipient.nil? || Setting.mail_suffix_separators.empty? || recipient.match?(/.+[#{separators}].+/) - regexp = "#{recipient}([#{separators}][^@]+)*@#{domain}" + regexp = "^#{recipient}([#{separators}][^@]+)*@#{domain}$" [skip_suffix_check, regexp] end diff --git a/app/seeders/admin_user_seeder.rb b/app/seeders/admin_user_seeder.rb index 29470a0049..c531b4f8bf 100644 --- a/app/seeders/admin_user_seeder.rb +++ b/app/seeders/admin_user_seeder.rb @@ -55,7 +55,7 @@ class AdminUserSeeder < Seeder user.language = I18n.locale.to_s user.status = User.statuses[:active] user.force_password_change = force_password_change? - user.notification_settings.build(involved: true, mentioned: true, watched: true) + user.notification_settings.build(assignee: true, responsible: true, mentioned: true, watched: true) end end diff --git a/app/seeders/development_data/projects_seeder.rb b/app/seeders/development_data/projects_seeder.rb index 42029ce490..bcfed8e63a 100644 --- a/app/seeders/development_data/projects_seeder.rb +++ b/app/seeders/development_data/projects_seeder.rb @@ -97,13 +97,18 @@ module DevelopmentData def project_data(identifier) { - name: identifier.humanize, + name: project_name(identifier), identifier:, enabled_module_names: project_modules, types: Type.all } end + def project_name(identifier) + _dev, *parts = identifier.split('-') + "[dev] #{parts.join(' ').capitalize}" + end + def project_modules Setting.default_projects_modules - %w(news wiki meetings calendar) end diff --git a/app/seeders/development_data/users_seeder.rb b/app/seeders/development_data/users_seeder.rb index 8cac0c2d16..d40985a6fa 100644 --- a/app/seeders/development_data/users_seeder.rb +++ b/app/seeders/development_data/users_seeder.rb @@ -103,7 +103,7 @@ module DevelopmentData user.status = User.statuses[:active] user.language = I18n.locale user.force_password_change = false - user.notification_settings.build(involved: true, mentioned: true, watched: true) + user.notification_settings.build(assignee: true, responsible: true, mentioned: true, watched: true) end end diff --git a/app/seeders/random_data/forum_seeder.rb b/app/seeders/random_data/forum_seeder.rb deleted file mode 100644 index 13a260c43c..0000000000 --- a/app/seeders/random_data/forum_seeder.rb +++ /dev/null @@ -1,57 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-2020 the OpenProject GmbH -# -# 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 COPYRIGHT and LICENSE files for more details. -module RandomData - class ForumSeeder - def self.seed!(project) - user = User.admin.first - - puts '' - print_status ' ↳ Creating forum with posts' - - forum = Forum.create! project: project, - name: I18n.t("seeders.#{OpenProject::Configuration['edition']}.demo_data.board.name"), - description: I18n.t("seeders.#{OpenProject::Configuration['edition']}.demo_data.board.description") - - rand(30).times do - print_status '.' - message = Message.create forum: forum, - author: user, - subject: Faker::Lorem.words(5).join(' '), - content: Faker::Lorem.paragraph(5, true, 3) - - rand(5).times do - print_status '.' - Message.create forum:, - author: user, - subject: message.subject, - content: Faker::Lorem.paragraph(5, true, 3), - parent: message - end - end - end - end -end diff --git a/app/seeders/random_data/news_seeder.rb b/app/seeders/random_data/news_seeder.rb deleted file mode 100644 index 1e3a965687..0000000000 --- a/app/seeders/random_data/news_seeder.rb +++ /dev/null @@ -1,56 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-2020 the OpenProject GmbH -# -# 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 COPYRIGHT and LICENSE files for more details. -module RandomData - class NewsSeeder - def self.seed!(project) - user = User.admin.first - - puts '' - print_status ' ↳ Creating news' - - rand(30).times do - print_status '.' - news = News.create project: project, - author: user, - title: Faker::Lorem.characters(60), - summary: Faker::Lorem.paragraph(1, true, 3), - description: Faker::Lorem.paragraph(5, true, 3) - - ## create some journal entries - rand(5).times do - news.reload - - news.title = Faker::Lorem.words(5).join(' ').slice(0, 60) if rand(99).even? - news.summary = Faker::Lorem.paragraph(1, true, 3) if rand(99).even? - news.description = Faker::Lorem.paragraph(5, true, 3) if rand(99).even? - - news.save! - end - end - end - end -end diff --git a/app/seeders/random_data/wiki_seeder.rb b/app/seeders/random_data/wiki_seeder.rb deleted file mode 100644 index 673f50d4eb..0000000000 --- a/app/seeders/random_data/wiki_seeder.rb +++ /dev/null @@ -1,61 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-2020 the OpenProject GmbH -# -# 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 COPYRIGHT and LICENSE files for more details. -module RandomData - class WikiSeeder - def self.seed!(project) - user = User.admin.first - - puts '' - print_status ' ↳ Creating wikis' - - rand(5).times do - print_status '.' - wiki_page = WikiPage.create( - wiki: project.wiki, - title: Faker::Lorem.words(5).join(' ') - ) - - ## create some wiki contents - rand(5).times do - print_status '.' - wiki_content = WikiContent.create( - page: wiki_page, - author: user, - text: Faker::Lorem.paragraph(5, true, 3) - ) - - ## create some journal entries - rand(5).times do - wiki_content.reload - wiki_content.text = Faker::Lorem.paragraph(5, true, 3) if rand(99).even? - wiki_content.save! - end - end - end - end - end -end diff --git a/app/seeders/random_data/work_package_seeder.rb b/app/seeders/random_data/work_package_seeder.rb deleted file mode 100644 index dbfcf03579..0000000000 --- a/app/seeders/random_data/work_package_seeder.rb +++ /dev/null @@ -1,188 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-2020 the OpenProject GmbH -# -# 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 COPYRIGHT and LICENSE files for more details. -module RandomData - class WorkPackageSeeder - attr_accessor :project, :user, :statuses, :repository, :types - - def initialize(project) - self.project = project - self.user = User.admin.first - self.statuses = Status.all - self.repository = Repository.first - self.types = project.types.all.reject(&:is_milestone?) - end - - def seed!(random: true) - puts '' - print_status ' ↳ Creating work_packages' - - seed_random_work_packages - end - - private - - def seed_random_work_packages - rand(50).times do - print_status '.' - work_package = WorkPackage.create!( - project:, - author: user, - subject: Faker::Lorem.words(8).join(' '), - status: statuses.sample, - type: types.sample, - start_date: s = Date.today - rand(-24..25).days, - due_date: s + rand(1..120).days - ) - work_package.priority = IssuePriority.all.sample - work_package.description = Faker::Lorem.paragraph(5, true, 3) - work_package.save! - end - - work_package = WorkPackage.first - - if repository - add_changeset(work_package) - end - - add_time_entries(work_package) - add_attachments(work_package) - add_custom_values(work_package) - make_changes(work_package) - end - - def add_changeset(work_package) - 2.times do |changeset_count| - print_status '.' - changeset = Changeset.create( - repository:, - user:, - revision: (work_package.id * 10) + changeset_count, - scmid: (work_package.id * 10) + changeset_count, - work_packages: [work_package], - committer: Faker::Name.name, - committed_on: Date.today, - comments: Faker::Lorem.words(8).join(' ') - ) - - 5.times do - print_status '.' - change = Change.create( - action: Faker::Lorem.characters(1), - path: Faker::Internet.url - ) - - changeset.file_changes << change - end - - repository.changesets << changeset - - changeset.save! - - rand(5).times do - print_status '.' - changeset.reload - - changeset.committer = Faker::Name.name if rand(99).even? - changeset.committed_on = Date.today + rand(999) if rand(99).even? - changeset.comments = Faker::Lorem.words(8).join(' ') if rand(99).even? - - changeset.save! - end - end - end - - def add_time_entries(work_package) - 5.times do |time_entry_count| - time_entry = TimeEntry.create( - project:, - user:, - work_package:, - spent_on: Date.today + time_entry_count, - activity: time_entry_activities.sample, - hours: time_entry_count - ) - work_package.time_entries << time_entry - end - end - - def add_attachments(work_package) - 3.times do |_attachment_count| - file = OpenProject::Files.create_uploaded_file(name: Faker::Lorem.words(8).join(' ')) - attachment = Attachment.new( - container: work_package, - author: user, - file: - ) - attachment.save! - - work_package.attachments << attachment - end - end - - def add_custom_values(work_package) - project.work_package_custom_fields.each do |custom_field| - work_package.type.custom_fields << custom_field if !work_package.type.custom_fields.include?(custom_field) - work_package.custom_values << CustomValue.new(custom_field:, - value: Faker::Lorem.words(8).join(' ')) - end - - work_package.type.save! - work_package.save! - end - - def make_changes(work_package) - 20.times do - print_status '.' - work_package.reload - - work_package.status = statuses.sample if rand(99).even? - work_package.subject = Faker::Lorem.words(8).join(' ') if rand(99).even? - work_package.description = Faker::Lorem.paragraph(5, true, 3) if rand(99).even? - work_package.type = types.sample if rand(99).even? - - work_package.time_entries.each do |t| - t.spent_on = Date.today + rand(100) if rand(99).even? - t.activity = time_entry_activities.sample if rand(99).even? - t.hours = rand(10) if rand(99).even? - end - - work_package.reload - - attachments = work_package.attachments.select { |_a| rand(999) < 10 } - work_package.attachments = work_package.attachments - attachments - - work_package.reload - - work_package.custom_values.each do |cv| - cv.value = Faker::Code.isbn if rand(99).even? - end - - work_package.save! - end - end - end -end diff --git a/app/seeders/random_data_seeder.rb b/app/seeders/random_data_seeder.rb deleted file mode 100644 index 523a8c34e3..0000000000 --- a/app/seeders/random_data_seeder.rb +++ /dev/null @@ -1,55 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-2020 the OpenProject GmbH -# -# 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 COPYRIGHT and LICENSE files for more details. -class RandomDataSeeder - def self.seed! - puts ' ########################################################' - puts ' # WARNING: THIS DELETES ANY DEMO DATA THAT WAS SEEDED #' - puts ' ########################################################' - - project = DemoData::ProjectSeeder.seed! - - DemoData::CustomFieldSeeder.seed!(project) - RandomData::ForumSeeder.seed!(project) - RandomData::NewsSeeder.seed!(project) - RandomData::WikiSeeder.seed!(project) - RandomData::WorkPackageSeeder.new(project).seed! - - puts "\n\n" - puts ' ################################' - puts ' # Random seeding....done #' - puts ' ################################' - puts " # %02d %-23s #" % [WorkPackage.where(project_id: project.id).count, 'issues created.'] - puts " # %02d %-23s #" % [Message.joins(:board).where(boards: { project_id: project.id }).count, 'messages created.'] - puts " # %02d %-23s #" % [News.where(project_id: project.id).count, 'news created.'] - puts " # %02d %-23s #" % [WikiContent.joins(page: [:wiki]).where('wikis.project_id = ?', project.id).count, - 'wiki contents created.'] - puts " # %02d %-23s #" % [TimeEntry.where(project_id: project.id).count, 'time entries created.'] - puts " # %02d %-23s #" % [Changeset.joins(:repository).where(repositories: { project_id: project.id }).count, - 'changesets created.'] - puts " ################################\n\n" - end -end diff --git a/app/seeders/root_seeder.rb b/app/seeders/root_seeder.rb index 5eef75c95f..7611a6916b 100644 --- a/app/seeders/root_seeder.rb +++ b/app/seeders/root_seeder.rb @@ -88,7 +88,10 @@ class RootSeeder < Seeder ## # Clears some schema caches and column information. def reset_active_record! - ActiveRecord::Base.descendants.each do |klass| + ActiveRecord::Base + .descendants + .reject(&:abstract_class?) + .each do |klass| klass.connection.schema_cache.clear! klass.reset_column_information end diff --git a/app/services/base_services/copy.rb b/app/services/base_services/copy.rb index a7bad66006..aebef7cec8 100644 --- a/app/services/base_services/copy.rb +++ b/app/services/base_services/copy.rb @@ -27,7 +27,7 @@ #++ module BaseServices - class Copy < ::BaseServices::BaseContracted + class Copy < ::BaseServices::Write alias_attribute(:source, :model) ## @@ -39,7 +39,9 @@ module BaseServices ## # collect copyable associated modules def self.copyable_dependencies - copy_dependencies.map do |service_cls| + copy_dependencies + .flat_map { |dependency| [dependency] + dependency.copy_dependencies } + .map do |service_cls| { identifier: service_cls.identifier, name_source: -> { service_cls.human_name }, @@ -61,33 +63,30 @@ module BaseServices end def call(params) - User.execute_as(user) do - prepare(params) - perform(params) - end - end + prepare_state(params) - def after_validate(params, _call) - # Initialize the target resource to copy into - call = initialize_copy(source, params) + super + end + def persist(call) # Return only the unsaved copy return call if params[:attributes_only] - # Try to save the result or return its errors - copy_instance = call.result - unless copy_instance.save - return ServiceResult.failure(result: copy_instance, errors: copy_instance.errors) - end - - self.class.copy_dependencies.each do |service_cls| - next if skip_dependency?(params, service_cls) + super.tap do |super_call| + copy_instance = super_call.result + self.class.copy_dependencies.each do |service_cls| + next if skip_dependency?(params, service_cls) - call.merge! call_dependent_service(service_cls, target: copy_instance, params:), - without_success: true + super_call.merge! call_dependent_service(service_cls, target: copy_instance, params:), + without_success: true + end end + end + + def after_perform(call) + return call if params[:attributes_only] - call + super end protected @@ -110,7 +109,7 @@ module BaseServices # # Note that for dependent copy services to be called # this will already be present. - def prepare(_params) + def prepare_state(_params) # Retain the source project itself state.source = source end @@ -124,8 +123,8 @@ module BaseServices .call(params:) end - def initialize_copy(source, params) - raise NotImplementedError + def instance(_params) + source.class.new end def default_contract_class diff --git a/app/services/copy/dependency.rb b/app/services/copy/dependency.rb index f4cc8a350b..acf994c3d9 100644 --- a/app/services/copy/dependency.rb +++ b/app/services/copy/dependency.rb @@ -47,6 +47,13 @@ module Copy identifier.capitalize end + ## + # Dependencies the current dependency itself supports. + # The most common case for this are attachment services. + def self.copy_dependencies + [] + end + def initialize(source:, target:, user:) @source = source @target = target diff --git a/app/services/grids/copy_service.rb b/app/services/grids/copy_service.rb index d2f6c9680d..357707a91e 100644 --- a/app/services/grids/copy_service.rb +++ b/app/services/grids/copy_service.rb @@ -51,14 +51,8 @@ module Grids protected - def initialize_copy(source, params) - grid = source.dup - - initialize_new_grid! grid, source, params - - ServiceResult.new success: grid.save, result: grid + def set_attributes_params(_params) + source.dup.attributes end - - def initialize_new_grid!(_new_grid, _original_grid, _params); end end end diff --git a/app/services/notifications/create_from_model_service.rb b/app/services/notifications/create_from_model_service.rb index b471f7ecc7..12398b7f96 100644 --- a/app/services/notifications/create_from_model_service.rb +++ b/app/services/notifications/create_from_model_service.rb @@ -141,13 +141,13 @@ class Notifications::CreateFromModelService def settings_of_assigned project_applicable_settings(User.where(id: group_or_user_ids(journal.data.assigned_to)), project, - NotificationSetting::INVOLVED) + NotificationSetting::ASSIGNEE) end def settings_of_responsible project_applicable_settings(User.where(id: group_or_user_ids(journal.data.responsible)), project, - NotificationSetting::INVOLVED) + NotificationSetting::RESPONSIBLE) end def settings_of_subscribed diff --git a/app/services/projects/copy/attachment_copier.rb b/app/services/projects/copy/attachment_copier.rb new file mode 100644 index 0000000000..14baccfeb4 --- /dev/null +++ b/app/services/projects/copy/attachment_copier.rb @@ -0,0 +1,57 @@ +# OpenProject is an open source project management software. +# Copyright (C) 2010-2022 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +module Projects::Copy + # Can be included in case the dependent service needs to be listed in the copy_dependency section for: + # * Being an option in the UI + # * Calculating the count + # but does not really need to do anything beyond that. + # + # This is currently employed to turn the attachment dependent services into NoCreate services as attachment + # handling has been moved into the create services e.g. of WorkPackage and WikiPage. + module AttachmentCopier + extend ActiveSupport::Concern + + class_methods do + def attachment_dependent_service(service_const = nil) + @attachment_dependent_service = service_const if service_const + + @attachment_dependent_service + end + + def copy_dependencies + super + [attachment_dependent_service] + end + end + + protected + + def copy_attachments? + (params.dig(:params, :only) || []) + .any? { |k| k.to_s == self.class.attachment_dependent_service.identifier } + end + end +end diff --git a/app/services/projects/copy/categories_dependent_service.rb b/app/services/projects/copy/categories_dependent_service.rb index c655709eb7..fdc45fea42 100644 --- a/app/services/projects/copy/categories_dependent_service.rb +++ b/app/services/projects/copy/categories_dependent_service.rb @@ -29,7 +29,7 @@ module Projects::Copy class CategoriesDependentService < Dependency def self.human_name - I18n.t(:label_work_package_category_plural) + I18n.t(:'projects.copy.work_package_categories') end def source_count diff --git a/app/services/projects/copy/dependency.rb b/app/services/projects/copy/dependency.rb index 54740b2ffb..be50c7b4be 100644 --- a/app/services/projects/copy/dependency.rb +++ b/app/services/projects/copy/dependency.rb @@ -41,7 +41,7 @@ module Projects::Copy # Check whether this dependency should be copied # as it was selected def self.should_copy?(params, check) - return true unless params[:only].present? + return true if params[:only].blank? params[:only].any? { |key| key.to_sym == check } end diff --git a/app/services/projects/copy/members_dependent_service.rb b/app/services/projects/copy/members_dependent_service.rb index 1eeafbc58b..483849512f 100644 --- a/app/services/projects/copy/members_dependent_service.rb +++ b/app/services/projects/copy/members_dependent_service.rb @@ -29,7 +29,7 @@ module Projects::Copy class MembersDependentService < Dependency def self.human_name - I18n.t(:label_member_plural) + I18n.t(:'projects.copy.members') end def source_count diff --git a/app/services/projects/copy/no_copier.rb b/app/services/projects/copy/no_copier.rb new file mode 100644 index 0000000000..be64abd95a --- /dev/null +++ b/app/services/projects/copy/no_copier.rb @@ -0,0 +1,42 @@ +# OpenProject is an open source project management software. +# Copyright (C) 2010-2022 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +module Projects::Copy + # Can be included in case the dependent service needs to be listed in the copy_dependency section for: + # * Being an option in the UI + # * Calculating the count + # but does not really need to do anything beyond that. + # + # This is currently employed to turn the attachment dependent services into NoCreate services as attachment + # handling has been moved into the create services e.g. of WorkPackage and WikiPage. + module NoCopier + protected + + def copy_dependency(params:) + # Not actually doing any copying. + end + end +end diff --git a/app/services/projects/copy/overview_dependent_service.rb b/app/services/projects/copy/overview_dependent_service.rb index 3f4d0d19c8..9d32df8369 100644 --- a/app/services/projects/copy/overview_dependent_service.rb +++ b/app/services/projects/copy/overview_dependent_service.rb @@ -29,7 +29,7 @@ module Projects::Copy class OverviewDependentService < Dependency def self.human_name - I18n.t(:'overviews.label') + I18n.t(:'projects.copy.overviews') end protected diff --git a/app/services/projects/copy/queries_dependent_service.rb b/app/services/projects/copy/queries_dependent_service.rb index ca1f180f5e..853fe85a62 100644 --- a/app/services/projects/copy/queries_dependent_service.rb +++ b/app/services/projects/copy/queries_dependent_service.rb @@ -29,7 +29,7 @@ module Projects::Copy class QueriesDependentService < Dependency def self.human_name - I18n.t(:label_query_plural) + I18n.t(:'projects.copy.queries') end def source_count diff --git a/app/services/projects/copy/wiki_dependent_service.rb b/app/services/projects/copy/wiki_dependent_service.rb index bc71ed2383..62e46f23d7 100644 --- a/app/services/projects/copy/wiki_dependent_service.rb +++ b/app/services/projects/copy/wiki_dependent_service.rb @@ -28,6 +28,10 @@ module Projects::Copy class WikiDependentService < Dependency + include AttachmentCopier + + attachment_dependent_service ::Projects::Copy::WikiPageAttachmentsDependentService + def self.human_name I18n.t(:label_wiki_page_plural) end @@ -74,7 +78,8 @@ module Projects::Copy .new(user:, model: source_page, contract_class: WikiPages::CopyContract) .call(wiki: target.wiki, parent_id: new_parent_id, - send_notifications: ActionMailer::Base.perform_deliveries) + send_notifications: ActionMailer::Base.perform_deliveries, + copy_attachments: copy_attachments?) if service_call.success? service_call.result diff --git a/app/services/projects/copy/wiki_page_attachments_dependent_service.rb b/app/services/projects/copy/wiki_page_attachments_dependent_service.rb index d4f18c75bd..4ed0489ef8 100644 --- a/app/services/projects/copy/wiki_page_attachments_dependent_service.rb +++ b/app/services/projects/copy/wiki_page_attachments_dependent_service.rb @@ -28,25 +28,14 @@ module Projects::Copy class WikiPageAttachmentsDependentService < Dependency - include ::Copy::Concerns::CopyAttachments + include ::Projects::Copy::NoCopier def self.human_name - I18n.t(:label_wiki_page_attachments) + I18n.t(:'projects.copy.wiki_page_attachments') end def source_count source.wiki && source.wiki.pages.joins(:attachments).count('attachments.id') end - - protected - - def copy_dependency(params:) - # If no wiki pages copied, we cannot copy their attachments - return unless state.wiki_page_id_lookup - - state.wiki_page_id_lookup.each do |old_id, new_id| - copy_attachments('WikiPage', from_id: old_id, to_id: new_id) - end - end end end diff --git a/app/services/projects/copy/work_package_attachments_dependent_service.rb b/app/services/projects/copy/work_package_attachments_dependent_service.rb index 15379744ee..f9d2ce27b4 100644 --- a/app/services/projects/copy/work_package_attachments_dependent_service.rb +++ b/app/services/projects/copy/work_package_attachments_dependent_service.rb @@ -1,6 +1,6 @@ -#-- copyright +#--copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2022 the OpenProject GmbH +# Copyright (C) 2010-2022 the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. @@ -28,25 +28,14 @@ module Projects::Copy class WorkPackageAttachmentsDependentService < Dependency - include ::Copy::Concerns::CopyAttachments + include ::Projects::Copy::NoCopier def self.human_name - I18n.t(:label_work_package_attachments) + I18n.t(:'projects.copy.work_package_attachments') end def source_count source.work_packages.joins(:attachments).count('attachments.id') end - - protected - - def copy_dependency(params:) - # If no work packages were copied, we cannot copy their attachments - return unless state.work_package_id_lookup - - state.work_package_id_lookup.each do |old_wp_id, new_wp_id| - copy_attachments('WorkPackage', from_id: old_wp_id, to_id: new_wp_id) - end - end end end diff --git a/app/services/projects/copy/work_packages_dependent_service.rb b/app/services/projects/copy/work_packages_dependent_service.rb index 8da626e3eb..b022fdac41 100644 --- a/app/services/projects/copy/work_packages_dependent_service.rb +++ b/app/services/projects/copy/work_packages_dependent_service.rb @@ -28,6 +28,10 @@ module Projects::Copy class WorkPackagesDependentService < Dependency + include AttachmentCopier + + attachment_dependent_service ::Projects::Copy::WorkPackageAttachmentsDependentService + def self.human_name I18n.t(:label_work_package_plural) end @@ -39,37 +43,39 @@ module Projects::Copy protected def copy_dependency(params:) + to_copy = source_work_packages + # Stores the source work_package id as a key and the copied work package ID as the - # value. Used to map the two together for work_package relations. - work_packages_map = {} + # value. Used to map the two together for work_package relations. + work_packages_map = copy_work_packages(to_copy) + # Relations after in case copied work_packages are related to each other. + copy_work_packages_relations(to_copy, work_packages_map) + + state.work_package_id_lookup = work_packages_map + end - # Get work_packages sorted by their depth in the hierarchy tree - # so that parents get copied before their children. - to_copy = source + # Get work_packages sorted by their depth in the hierarchy tree + # so that parents get copied before their children. + def source_work_packages + source .work_packages .includes(:custom_values, :version, :assigned_to, :responsible) .order_by_ancestors('asc') .order('id ASC') + end + def copy_work_packages(to_copy) user_cf_ids = WorkPackageCustomField.where(field_format: 'user').pluck(:id) - to_copy.each do |wp| + to_copy.inject({}) do |work_packages_map, wp| parent_id = work_packages_map[wp.parent_id] || wp.parent_id new_wp = copy_work_package(wp, parent_id, user_cf_ids) work_packages_map[wp.id] = new_wp.id if new_wp - end - - # Relations and attachments after in case work_packages related each other - to_copy.each do |wp| - new_wp_id = work_packages_map[wp.id] - next unless new_wp_id - copy_relations(wp, new_wp_id, work_packages_map) + work_packages_map end - - state.work_package_id_lookup = work_packages_map end def copy_work_package(source_work_package, parent_id, user_cf_ids) @@ -79,7 +85,7 @@ module Projects::Copy .new(user:, work_package: source_work_package, contract_class: WorkPackages::CopyProjectContract) - .call(**overrides) + .call(copy_attachments: copy_attachments?, **overrides) if service_call.success? service_call.result @@ -94,6 +100,15 @@ module Projects::Copy end end + def copy_work_packages_relations(to_copy, work_packages_map) + to_copy.each do |wp| + new_wp_id = work_packages_map[wp.id] + next unless new_wp_id + + copy_relations(wp, new_wp_id, work_packages_map) + end + end + def copy_relations(source_wp, new_wp_id, work_packages_map) Relation.of_work_package(source_wp).each do |source_relation| from_id, to_id = relations_from_to(source_relation, source_wp, new_wp_id, work_packages_map) diff --git a/app/services/projects/copy_service.rb b/app/services/projects/copy_service.rb index c688c58220..320d5b2900 100644 --- a/app/services/projects/copy_service.rb +++ b/app/services/projects/copy_service.rb @@ -36,9 +36,7 @@ module Projects ::Projects::Copy::VersionsDependentService, ::Projects::Copy::CategoriesDependentService, ::Projects::Copy::WorkPackagesDependentService, - ::Projects::Copy::WorkPackageAttachmentsDependentService, ::Projects::Copy::WikiDependentService, - ::Projects::Copy::WikiPageAttachmentsDependentService, ::Projects::Copy::ForumsDependentService, ::Projects::Copy::QueriesDependentService, ::Projects::Copy::BoardsDependentService, @@ -57,70 +55,86 @@ module Projects !Copy::Dependency.should_copy?(params, dependency_cls.identifier.to_sym) end - def initialize_copy(source, params) - target = Project.new + def set_attributes_params(_params) + attributes = source_attributes.merge( + # Clear enabled modules + enabled_module_names: source_enabled_modules, + types: source_types, + work_package_custom_fields: source_custom_fields, - target.attributes = source.attributes.dup.except(*skipped_attributes) - # Clear enabled modules - target.enabled_modules = [] - target.enabled_module_names = source.enabled_module_names - %w[repository] - target.types = source.types - target.work_package_custom_fields = source.work_package_custom_fields - - # Copy status object - target.status = source.status&.dup - - # Take over the CF values for attributes - target.custom_field_values = source.custom_value_attributes - - # Additional input target params - target_project_params = params[:target_project_params].with_indifferent_access - - cleanup_target_project_params(source, target, target_project_params) - cleanup_target_project_attributes(source, target, target_project_params) + # Copy status object + status: source_status + ) - # Assign additional params from user - call = Projects::SetAttributesService - .new(user:, - model: target, - contract_class: Projects::CopyContract, - contract_options: { copy_source: source, validate_model: true }) - .with_state(state) - .call(target_project_params) + only_allowed_parent_id(attributes) + .merge(source_custom_field_attributes) + .merge(target_project_params) + end - # Retain values after the set attributes service - retain_attributes(source, target, target_project_params) + def before_perform(params, service_call) + super.tap do |super_call| + # Retain values after the set attributes service + retain_attributes(source, super_call.result) - # Retain the project in the state for other dependent - # copy services to use - state.project = target + # Retain the project in the state for other dependent + # copy services to use + state.project = super_call.result + end + end - call + def contract_options + { copy_source: source, validate_model: true } end - def retain_attributes(source, target, target_project_params) + def retain_attributes(source, target) # Ensure we keep the public value of the source project # which might get overridden by the SetAttributesService # unless the user provided a different value target.public = source.public unless target_project_params.key?(:public) end - def cleanup_target_project_params(_source, _target, target_project_params) - if (parent_id = target_project_params[:parent_id]) && (parent = Project.find_by(id: parent_id)) && !user.allowed_to?( - :add_subprojects, parent - ) - target_project_params.delete(:parent_id) - end + def skipped_attributes + %w[id created_at updated_at name identifier active templated lft rgt] end - def cleanup_target_project_attributes(_source, target, _target_project_params) - if target.parent && !user.allowed_to?(:add_subprojects, target.parent) - target.parent = nil - end + def source_attributes + source.attributes.dup.except(*skipped_attributes).with_indifferent_access end - def skipped_attributes - %w[id created_at updated_at name identifier active templated lft rgt] + def source_enabled_modules + source.enabled_module_names - %w[repository] + end + + def source_status + source.status&.attributes + end + + def source_types + source.types + end + + def source_custom_fields + source.work_package_custom_fields + end + + def source_custom_field_attributes + source + .custom_value_attributes + .transform_keys { |key| "custom_field_#{key}" } + end + + # Additional input target params + def target_project_params + params[:target_project_params].with_indifferent_access + end + + def only_allowed_parent_id(attributes) + if (parent_id = attributes[:parent_id]) && (parent = Project.find_by(id: parent_id)) && + !user.allowed_to?(:add_subprojects, parent) + attributes.except(:parent_id) + else + attributes + end end end end diff --git a/app/services/queries/copy_service.rb b/app/services/queries/copy_service.rb index 105f3cdb0e..bef9f19335 100644 --- a/app/services/queries/copy_service.rb +++ b/app/services/queries/copy_service.rb @@ -37,16 +37,19 @@ module Queries protected - def initialize_copy(source, _params) - new_query = ::Query.new source.attributes.dup.except(*skipped_attributes) + def set_attributes(_params) + new_query = copied_query new_query.sort_criteria = source.sort_criteria if source.sort_criteria - new_query.project = state.project || source.project ::Queries::Copy::FiltersMapper .new(state, new_query.filters) .map_filters! - ServiceResult.new(success: new_query.save, result: new_query) + ServiceResult.new(success: new_query.valid?, result: new_query) + end + + def copied_query + ::Query.new source.attributes.dup.except(*skipped_attributes).merge(project: state.project || source.project) end def skipped_attributes diff --git a/app/services/user_preferences/update_service.rb b/app/services/user_preferences/update_service.rb index 23f5701bde..65f4e3040c 100644 --- a/app/services/user_preferences/update_service.rb +++ b/app/services/user_preferences/update_service.rb @@ -90,7 +90,8 @@ module UserPreferences conflict_target:, index_predicate:, columns: %i[watched - involved + assignee + responsible mentioned work_package_commented work_package_created diff --git a/app/services/users/set_attributes_service.rb b/app/services/users/set_attributes_service.rb index 75ff215f43..9c2dc80bba 100644 --- a/app/services/users/set_attributes_service.rb +++ b/app/services/users/set_attributes_service.rb @@ -62,7 +62,7 @@ module Users end def initialize_notification_settings - model.notification_settings.build(involved: true, mentioned: true, watched: true) + model.notification_settings.build(assignee: true, responsible: true, mentioned: true, watched: true) end # rubocop:disable Metrics/AbcSize diff --git a/app/services/wiki_pages/copy_service.rb b/app/services/wiki_pages/copy_service.rb index 4730801629..dbd2dbb711 100644 --- a/app/services/wiki_pages/copy_service.rb +++ b/app/services/wiki_pages/copy_service.rb @@ -29,6 +29,7 @@ class WikiPages::CopyService include ::Shared::ServiceContext include Contracted + include ::Copy::Concerns::CopyAttachments attr_accessor :user, :model, @@ -40,18 +41,21 @@ class WikiPages::CopyService self.contract_class = contract_class end - def call(send_notifications: true, **attributes) + def call(send_notifications: true, copy_attachments: true, **attributes) in_context(model, send_notifications) do - copy(attributes) + copy(attributes, copy_attachments) end end protected - def copy(attribute_override) + def copy(attribute_override, copy_attachments) attributes = copied_attributes(attribute_override) create(attributes) + .on_success do |call| + copy_wiki_page_attachments(call.result) if copy_attachments + end end def create(attributes) @@ -74,4 +78,8 @@ class WikiPages::CopyService instantiate_contract(model, user) .writable_attributes end + + def copy_wiki_page_attachments(copy) + copy_attachments('WikiPage', from_id: model.id, to_id: copy.id) + end end diff --git a/app/services/work_packages/copy_service.rb b/app/services/work_packages/copy_service.rb index 171b09c666..ff5cba6cf2 100644 --- a/app/services/work_packages/copy_service.rb +++ b/app/services/work_packages/copy_service.rb @@ -29,6 +29,7 @@ class WorkPackages::CopyService include ::Shared::ServiceContext include Contracted + include ::Copy::Concerns::CopyAttachments attr_accessor :user, :work_package, @@ -40,34 +41,34 @@ class WorkPackages::CopyService self.contract_class = contract_class end - def call(send_notifications: true, **attributes) + def call(send_notifications: true, copy_attachments: true, **attributes) in_context(work_package, send_notifications) do - copy(attributes, send_notifications) + copy(attributes, copy_attachments, send_notifications) end end protected - def copy(attribute_override, send_notifications) - attributes = copied_attributes(work_package, attribute_override) - - copied = create(attributes, send_notifications) - - if copied.success? - remove_author_watcher(copied.result) - copy_watchers(copied.result) - end + def copy(attribute_override, copy_attachments, send_notifications) + copied = create(work_package, + attribute_override, + send_notifications) + .on_success do |copy_call| + remove_author_watcher(copy_call.result) + copy_watchers(copy_call.result) + copy_work_package_attachments(copy_call.result) if copy_attachments + end copied.state.copied_from_work_package_id = work_package&.id copied end - def create(attributes, send_notifications) + def create(work_package, attribute_overrides, send_notifications) WorkPackages::CreateService .new(user:, contract_class:) - .call(**attributes.merge(send_notifications:).symbolize_keys) + .call(**copied_attributes(work_package, attribute_overrides).merge(send_notifications:).symbolize_keys) end def copied_attributes(work_package, override) @@ -102,4 +103,8 @@ class WorkPackages::CopyService copied.add_watcher(user) if user.active? end end + + def copy_work_package_attachments(copy) + copy_attachments('WorkPackage', from_id: work_package.id, to_id: copy.id) + end end diff --git a/app/services/work_packages/schedule_dependency.rb b/app/services/work_packages/schedule_dependency.rb index 7cbe83bb64..33eb3bd595 100644 --- a/app/services/work_packages/schedule_dependency.rb +++ b/app/services/work_packages/schedule_dependency.rb @@ -93,6 +93,8 @@ class WorkPackages::ScheduleDependency end def descendants(work_package) + # Avoid using WorkPackage.with_ancestors to save database requests. + # All needed data is already loaded. @descendants ||= {} @descendants[work_package] ||= begin children = children_by_parent_id(work_package.id) @@ -101,15 +103,14 @@ class WorkPackages::ScheduleDependency end end + # Get relations of type follows for which the given work package is a direct + # follower, or an indirect follower (through parent and/or children). + # + # Used by +Dependency#dependent_ids+ to get work packages that must be + # scheduled prior to the given work package. def follows_relations(work_package) @follows_relations ||= {} - @follows_relations[work_package] ||= begin - line = [work_package] + ancestors(work_package) + descendants(work_package) - @follows_relations_by_from_id ||= known_follows_relations.group_by(&:from_id) - @follows_relations_by_from_id - .fetch_values(*line.map(&:id)) { [] } - .flatten - end + @follows_relations[work_package] ||= all_direct_and_indirect_follows_relations_for(work_package) end private @@ -117,6 +118,18 @@ class WorkPackages::ScheduleDependency attr_accessor :known_follows_relations, :moved_work_packages + def all_direct_and_indirect_follows_relations_for(work_package) + family = ancestors(work_package) + [work_package] + descendants(work_package) + follows_relations_by_follower_id + .fetch_values(*family.pluck(:id)) { [] } + .flatten + .uniq + end + + def follows_relations_by_follower_id + @follows_relations_by_follower_id ||= known_follows_relations.group_by(&:from_id) + end + def create_dependencies moving_work_packages.index_with { |work_package| Dependency.new(work_package, self) } end @@ -168,6 +181,7 @@ class WorkPackages::ScheduleDependency WorkPackage .with_ancestor(known_work_packages) .where.not(id: known_work_packages.map(&:id)) + .distinct end # Load all the predecessors of follows relations that are not already loaded. diff --git a/app/views/onboarding/_onboarding_video_modal.html.erb b/app/views/onboarding/_onboarding_video_modal.html.erb index 6f5569d40b..091b08eb93 100644 --- a/app/views/onboarding/_onboarding_video_modal.html.erb +++ b/app/views/onboarding/_onboarding_video_modal.html.erb @@ -34,7 +34,6 @@ See COPYRIGHT and LICENSE files for more details.
-

<%= I18n.t('onboarding.heading_getting_started') %>

<%= I18n.t('onboarding.text_getting_started_description') %> diff --git a/app/views/projects/_project_export_modal.html.erb b/app/views/projects/_project_export_modal.html.erb index 5c559cbd00..c9386bbab3 100644 --- a/app/views/projects/_project_export_modal.html.erb +++ b/app/views/projects/_project_export_modal.html.erb @@ -46,4 +46,13 @@ See COPYRIGHT and LICENSE files for more details. <% end %>
+
+
+ +
+
diff --git a/app/workers/backup_job.rb b/app/workers/backup_job.rb index c5040584a8..be73860970 100644 --- a/app/workers/backup_job.rb +++ b/app/workers/backup_job.rb @@ -249,23 +249,30 @@ class BackupJob < ::ApplicationJob end def pg_env - config = ActiveRecord::Base.connection_db_config.configuration_hash entries = pg_env_to_connection_config.map do |key, config_key| - value = config[config_key].to_s + possible_keys = Array(config_key) + value = possible_keys + .lazy + .filter_map { |key| database_config[key] } + .first - [key.to_s, value] if value.present? + [key.to_s, value.to_s] if value.present? end entries.compact.to_h end + def database_config + @database_config ||= ActiveRecord::Base.connection_db_config.configuration_hash + end + ## # Maps the PG env variable name to the key in the AR connection config. def pg_env_to_connection_config { PGHOST: :host, PGPORT: :port, - PGUSER: :username, + PGUSER: %i[username user], PGPASSWORD: :password, PGDATABASE: :database } diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index 4fb84c13e1..ec7d3af277 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -210,6 +210,15 @@ af: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ af: label_wiki_start: "Begin bladsy" label_work_package: "Werkspakket" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Nuwe kategorie" label_work_package_category_plural: "Werkspakket kategorieë" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index 836d8d5906..1ce0286fdb 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -210,6 +210,15 @@ ar: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1986,7 +1995,6 @@ ar: label_wiki_start: "صفحة البداية" label_work_package: "مجموعة العمل" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "فئة جديدة" label_work_package_category_plural: "فئات مجموعة العمل" label_work_package_hierarchy: "التسلسل الهرمي لمجموعة العمل" diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index f0b89d0e2b..9383d96fd6 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -210,6 +210,15 @@ az: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ az: label_wiki_start: "Start page" label_work_package: "Work package" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "New category" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index 07d603f079..3e0222fd60 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -210,6 +210,15 @@ bg: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Изтриването е планирано и се извършва във фонов режим. Ще бъдете уведомени за резултата." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ bg: label_wiki_start: "Начална страница" label_work_package: "Работен пакет" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Нова категория" label_work_package_category_plural: "Категории работни пакети" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index f2b0b2029c..c9481954c7 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -207,6 +207,15 @@ ca: ignore_filenames: > Especifica una llista de noms per a ignorar quan es processin adjuncions per a correus electrònics entrants (p.e. signatures o icones). Introduïu un nom de fitxer per línia. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Membres del projecte' + overviews: 'Visió general del projecte' + queries: 'Paquets de treball: vistes guardades' + wiki_page_attachments: 'Pàgines wiki: fitxers adjunts' + work_package_attachments: 'Paquets de treball: fitxers adjunts' + work_package_categories: 'Paquets de treball: categories' + work_package_file_links: 'Paquets de treball: enllaços de fitxers' delete: scheduled: "L'eliminació s'ha planificat i s'efectuarà de fons. Sereu notificats del resultat." schedule_failed: "El projecte no es pot eliminar: %{errors}" @@ -1915,7 +1924,6 @@ ca: label_wiki_start: "Pàgina d'inici" label_work_package: "Paquet de treball" label_work_package_attachments: "Fitxers adjunts del paquet de treball" - label_work_package_file_link_plural: "Enllaços de fitxer del paquet de treball" label_work_package_category_new: "Nova categoria" label_work_package_category_plural: "Categories de paquet de treball" label_work_package_hierarchy: "Jerarquia del paquet de treball" diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index 1c77f9806e..d90b0d0f87 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -210,6 +210,15 @@ ckb-IR: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ ckb-IR: label_wiki_start: "Start page" label_work_package: "Work package" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "New category" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 9dc08448b3..b77ba1d638 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -210,6 +210,15 @@ cs: ignore_filenames: > Zadejte seznam názvů, které budou ignorovány při zpracování příloh pro příchozí poštu (např. podpisy nebo ikony). Zadejte jeden název souboru na řádek. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Odstranění bylo naplánováno a je dokončeno na pozadí. Výsledek vám bude oznámen." schedule_failed: "Projekt nelze odstranit: %{errors}" @@ -1953,7 +1962,6 @@ cs: label_wiki_start: "Úvodní stránka" label_work_package: "Pracovní balíček" label_work_package_attachments: "Přílohy pracovních balíčků" - label_work_package_file_link_plural: "Odkazy na souborů pracovních balíčků" label_work_package_category_new: "Nová kategorie" label_work_package_category_plural: "Kategorie pracovních balíčků" label_work_package_hierarchy: "Hierarchie pracovních balíčků" diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index 09fedc2920..b240a08ccc 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -208,6 +208,15 @@ da: ignore_filenames: > Angiv en liste over navne der skal ignoreres ved behandling af vedhæftede filer for indgående breve (f.eks. signaturer eller ikoner). Indtast et filnavn pr. linje. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Sletning er skemalagt og udføres i baggrunden. Du får besked om resultatet." schedule_failed: "Projektet kan ikke slettes: %{errors}" @@ -1917,7 +1926,6 @@ da: label_wiki_start: "Startside" label_work_package: "Arbejdspakke" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Ny kategori" label_work_package_category_plural: "Kategorier for arbejdspakker" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index 6a4a11d636..7db684a295 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -207,6 +207,15 @@ de: ignore_filenames: > Geben Sie eine Liste von Dateinamen an, die beim Verarbeiten von Anhängen für eingehende Mails (z.B. Signaturen oder Symbole) ignoriert werden sollen. Geben Sie einen Dateinamen pro Zeile ein. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Die Löschung wurde geplant und wird im Hintergrund durchgeführt. Sie werden über das Ergebnis informiert." schedule_failed: "Projekt kann nicht gelöscht werden: %{errors}" @@ -1914,7 +1923,6 @@ de: label_wiki_start: "Hauptseite" label_work_package: "Arbeitspaket" label_work_package_attachments: "Anhänge des Arbeitspakets" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Neue Kategorie" label_work_package_category_plural: "Arbeitspaket-Kategorien" label_work_package_hierarchy: "Arbeitspakethierarchie" @@ -1930,7 +1938,7 @@ de: label_workflow: "Workflow" label_workflow_plural: "Workflows" label_workflow_summary: "Zusammenfassung" - label_working_days: "Working days" + label_working_days: "Werktage" label_x_closed_work_packages_abbr: one: "1 geschlossen" other: "%{count} geschlossen" diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index e15f1f126e..5230d6c826 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -206,6 +206,15 @@ el: ignore_filenames: > Καθορίστε μια λίστα με τα ονόματα που πρέπει να αγνοήσετε όταν επεξεργάζεστε συνημμένα για εισερχόμενα email (π.χ. υπογραφές ή εικονίδια). Εισάγετε ένα όνομα αρχείου ανά γραμμή. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Η διαγραφή έχει προγραμματιστεί και εκτελείται στο παρασκήνιο. Θα ειδοποιηθείτε για το αποτέλεσμα." schedule_failed: "Το έργο δεν μπορεί να διαγραφεί: %{errors}" @@ -1915,7 +1924,6 @@ el: label_wiki_start: "Σελίδα έναρξης" label_work_package: "Εργασία" label_work_package_attachments: "Συνημμένα πακέτου εργασίας" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Νέα κατηγορία" label_work_package_category_plural: "Κατηγορίες πακέτου εργασίας" label_work_package_hierarchy: "Ιεραρχία πακέτου εργασίας" diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index 51d2d9b3f0..36e70a598b 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -210,6 +210,15 @@ eo: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "La projekto ne povas esti forigita: %{errors}" @@ -1919,7 +1928,6 @@ eo: label_wiki_start: "Start page" label_work_package: "Laborpakaĵo" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "New category" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index 52598e2d2c..59da4f0287 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -207,6 +207,15 @@ es: ignore_filenames: > Especifica una lista de nombres a ignorar al procesar archivos adjuntos de emails entrantes (por ejemplo, firmas o iconos). Introduce un nombre de archivo por línea. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Miembros del proyecto' + overviews: 'Visión general del proyecto' + queries: 'Paquetes de trabajo: vistas guardadas' + wiki_page_attachments: 'Páginas wiki: archivos adjuntos' + work_package_attachments: 'Paquetes de trabajo: archivos adjuntos' + work_package_categories: 'Paquetes de trabajo: categorías' + work_package_file_links: 'Paquetes de trabajo: enlaces de archivos' delete: scheduled: "Se ha programado la eliminación y se ejecutará en segundo plano. Recibirá una notificación con el resultado." schedule_failed: "El proyecto no puede ser eliminado: %{errors}" @@ -1916,7 +1925,6 @@ es: label_wiki_start: "Página de inicio" label_work_package: "Paquete de trabajo" label_work_package_attachments: "Datos adjuntos de paquete de trabajo" - label_work_package_file_link_plural: "Enlaces de archivo de paquete de trabajo" label_work_package_category_new: "Nueva categoría" label_work_package_category_plural: "Categorías de paquete de trabajo" label_work_package_hierarchy: "Jerarquía del paquete de trabajo" diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index adbbb80195..323351b6b1 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -210,6 +210,15 @@ et: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ et: label_wiki_start: "Esileht" label_work_package: "Teema" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Uus kategooria" label_work_package_category_plural: "Teema kategooriad" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index a4d73316bc..8766739868 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -210,6 +210,15 @@ fa: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ fa: label_wiki_start: "صفحه‌ی شروع" label_work_package: "Work package" label_work_package_attachments: "پیوست بسته کاری" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "New category" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index c68e689dc0..a8fbfc0db9 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -210,6 +210,15 @@ fi: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ fi: label_wiki_start: "Aloitussivu" label_work_package: "Työpaketti" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Uusi luokka" label_work_package_category_plural: "Kategoriat" label_work_package_hierarchy: "Tehtävähierarkia" diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index 070e9dcfdc..f6047422f7 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -210,6 +210,15 @@ fil: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ fil: label_wiki_start: "Simulan ang pahina" label_work_package: "Work package" label_work_package_attachments: "Nakakalakip na pahinag trabaho" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Bagong kategorya" label_work_package_category_plural: "Ang mga katergorya ng work package" label_work_package_hierarchy: "Hierarchy ng work package" diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index 5e89aea9b9..1f302f10b4 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -210,6 +210,15 @@ fr: ignore_filenames: > Spécifiez une liste de noms à ignorer lors du traitement des pièces jointes aux messages entrants (par exemple, signatures ou icônes). Entrez un nom de fichier par ligne. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "La suppression a été planifiée et est effectuée en arrière-plan. Vous serez notifié du résultat." schedule_failed: "Le projet ne peut pas être supprimé : %{errors}" @@ -532,9 +541,9 @@ fr: end_deletion: "Fin de la suppression" ignore_non_working_days: "Ignorer les jours non ouvrables" include_non_working_days: - title: 'Journées de travail' + title: 'Jours ouvrés' false: 'jours ouvrés seulement' - true: 'Inclure les jours non travaillés' + true: 'Inclure les jours non ouvrés' parent: "Parent" parent_issue: "Parent" parent_work_package: "Parent" @@ -1919,7 +1928,6 @@ fr: label_wiki_start: "Page d'accueil" label_work_package: "Lot de travaux" label_work_package_attachments: "Pièces jointes du lot de travaux" - label_work_package_file_link_plural: "Liens des fichiers du lot de travaux" label_work_package_category_new: "Nouvelle catégorie" label_work_package_category_plural: "Catégories de Lots de Travaux" label_work_package_hierarchy: "Hiérarchie du lot de travaux" diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index cb8a3569ce..1a0abbafc7 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -210,6 +210,15 @@ he: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1953,7 +1962,6 @@ he: label_wiki_start: "דף פתיחה" label_work_package: "חבילת עבודה" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "קטגוריה חדשה" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index faac509926..7bc4fa3b35 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -210,6 +210,15 @@ hi: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1917,7 +1926,6 @@ hi: label_wiki_start: "प्रारंभ पृष्ठ" label_work_package: "कार्य पैकेज" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "नई श्रेणी" label_work_package_category_plural: "कार्य पैकेज श्रेणियां" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index dcd60cf108..5b39662521 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -210,6 +210,15 @@ hr: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1936,7 +1945,6 @@ hr: label_wiki_start: "Početna stranica" label_work_package: "Radni paket" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Nova kategorija" label_work_package_category_plural: "Kategorije radnih paketa" label_work_package_hierarchy: "Hijerarhija radnih paketa" diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index 90015e2f0a..022ff62c85 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -209,6 +209,15 @@ hu: ignore_filenames: > Adja meg azoknak a neveknek a listáját, amelyeket figyelmen kívül kell hagyni a bejövő levelek mellékleteinek feldolgozásakor (pl. Aláírások vagy ikonok). Írjon be egy fájlnevet soronként. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "A törlés ütemezésre került, és a háttérben történik. Értesítést kap az eredményről.\n" schedule_failed: "A projektet nem lehet törölni: %{errors}" @@ -1916,7 +1925,6 @@ hu: label_wiki_start: "Kezdő oldal" label_work_package: "Feladatcsoport" label_work_package_attachments: "Munkacsomag mellékletek" - label_work_package_file_link_plural: "Munkacsomag fájlhivatkozások" label_work_package_category_new: "Új kategória" label_work_package_category_plural: "Munkacsomag kategóriák" label_work_package_hierarchy: "Feladatcsoport hierarchia" diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index d193b0b833..ede14dd90e 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -207,6 +207,15 @@ id: ignore_filenames: > Tentukan daftar nama yang harus diabaikan ketika memproses lampiran untuk surat masuk (mis., Tanda tangan atau ikon). Masukkan satu nama file per baris. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Proyek tidak dapat dihapus: %{errors}" @@ -1899,7 +1908,6 @@ id: label_wiki_start: "Homepage" label_work_package: "Paket-Penugasan" label_work_package_attachments: "Lampiran paket kerja" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Kategori Baru" label_work_package_category_plural: "Kategori Paket-Penugasan" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 2db7380991..de615e10b8 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -207,6 +207,15 @@ it: ignore_filenames: > Specifica un elenco di nomi da ignorare durante l'elaborazione di allegati per posta in arrivo (ad esempio firme o icone). Immettere un nome file per riga. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "L'eliminazione è stata programmata e verrà eseguita in background. Verrai avvisato del risultato." schedule_failed: "Il progetto non può essere eliminato: %{errors}" @@ -1916,7 +1925,6 @@ it: label_wiki_start: "Pagina iniziale" label_work_package: "Macro-attività" label_work_package_attachments: "Allegati macro-attività" - label_work_package_file_link_plural: "Collegamenti file macro-attività" label_work_package_category_new: "Nuova categoria" label_work_package_category_plural: "Categorie della macro-attività" label_work_package_hierarchy: "Gerarchia Macro-attività" diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 2f1ccee7c3..b1d535c640 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -208,6 +208,15 @@ ja: ignore_filenames: > 受信メールの添付ファイルを処理するとき、無視する名前の一覧を指定します (例えば、署名またはアイコン)。1 行につき 1 つのファイル名を入力してください。 projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "削除がスケジュールされ、バックグラウンドで実行されます。結果は通知されます。" schedule_failed: "プロジェクトを削除できません: %{errors}" @@ -1898,7 +1907,6 @@ ja: label_wiki_start: "開始ページ" label_work_package: "ワーク パッケージ" label_work_package_attachments: "ワークパッケージの添付ファイル" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "新規カテゴリ" label_work_package_category_plural: "ワークパッケージのカテゴリ" label_work_package_hierarchy: "ワークパッケージの階層" diff --git a/config/locales/crowdin/js-af.yml b/config/locales/crowdin/js-af.yml index 5b4d27caa4..ba9ea3a7ba 100644 --- a/config/locales/crowdin/js-af.yml +++ b/config/locales/crowdin/js-af.yml @@ -551,6 +551,7 @@ af: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ af: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-ar.yml b/config/locales/crowdin/js-ar.yml index dd90d0cb09..445e39a490 100644 --- a/config/locales/crowdin/js-ar.yml +++ b/config/locales/crowdin/js-ar.yml @@ -551,6 +551,7 @@ ar: all: 'الجميع' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: zero: 'and %{count} others' one: 'and 1 other' @@ -588,9 +589,8 @@ ar: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-az.yml b/config/locales/crowdin/js-az.yml index 5a9b352d51..bfde3eef68 100644 --- a/config/locales/crowdin/js-az.yml +++ b/config/locales/crowdin/js-az.yml @@ -551,6 +551,7 @@ az: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ az: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-bg.yml b/config/locales/crowdin/js-bg.yml index 216512b5c1..0f90fed5e0 100644 --- a/config/locales/crowdin/js-bg.yml +++ b/config/locales/crowdin/js-bg.yml @@ -551,6 +551,7 @@ bg: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ bg: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-ca.yml b/config/locales/crowdin/js-ca.yml index 2bb137119d..74ff4a9f66 100644 --- a/config/locales/crowdin/js-ca.yml +++ b/config/locales/crowdin/js-ca.yml @@ -551,6 +551,7 @@ ca: all: 'Totes' all_title: 'Mostrar tots' center: + label_actor_and: 'and' and_more_users: one: ' i 1 altre' other: ' i %{count} altres' @@ -584,9 +585,8 @@ ca: mentioned: title: 'He estat @mencionat' description: 'Rep una notificació cada vegada que algú em menciona a qualsevol lloc' - involved: - title: 'Assignat a mi o responsable' - description: 'Rep notificacions per a totes les activitats en paquets de treball pels quals soc assignat o responsable.' + assignee: 'Assignat a' + responsible: 'Responsable' watched: 'Actualitzacions en elements observats' work_package_commented: 'Tots els nous comentaris' work_package_created: 'Paquets de treball nou' diff --git a/config/locales/crowdin/js-ckb-IR.yml b/config/locales/crowdin/js-ckb-IR.yml index bd7238c43c..23a8a1c796 100644 --- a/config/locales/crowdin/js-ckb-IR.yml +++ b/config/locales/crowdin/js-ckb-IR.yml @@ -551,6 +551,7 @@ ckb-IR: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ ckb-IR: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-cs.yml b/config/locales/crowdin/js-cs.yml index 29658e0b19..04662ce2c3 100644 --- a/config/locales/crowdin/js-cs.yml +++ b/config/locales/crowdin/js-cs.yml @@ -551,6 +551,7 @@ cs: all: 'Vše' all_title: 'Zobrazit vše' center: + label_actor_and: 'and' and_more_users: one: 'a 1 další' few: 'a %{count} dalšich' @@ -586,9 +587,8 @@ cs: mentioned: title: 'Jsem @zmíněn' description: 'Dostat upozornění, kdykoli mě někdo kdekoli zmíní' - involved: - title: 'Přiřazeno mně nebo jsem Odpovědný' - description: 'Přijímat oznámení pro všechny aktivity v pracovních balíčcích, pro které jsem ŘeŠitel nebo Zodpovědný' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Aktualizace u sledovaných položek' work_package_commented: 'Všechny nové komentáře' work_package_created: 'Nový pracovní balíček' diff --git a/config/locales/crowdin/js-da.yml b/config/locales/crowdin/js-da.yml index a686cc29b8..1033fd1f31 100644 --- a/config/locales/crowdin/js-da.yml +++ b/config/locales/crowdin/js-da.yml @@ -550,6 +550,7 @@ da: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -583,9 +584,8 @@ da: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml index 43d814af41..2fe114456b 100644 --- a/config/locales/crowdin/js-de.yml +++ b/config/locales/crowdin/js-de.yml @@ -550,6 +550,7 @@ de: all: 'Alle' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'und ein weiterer' other: 'und %{count} weitere' @@ -583,9 +584,8 @@ de: mentioned: title: 'Wenn ich @erwähnt werde' description: 'Jedes Mal eine Benachrichtigung erhalten, wenn ich irgendwo erwähnt werde' - involved: - title: 'Mir zugewiesen oder verantwortlich' - description: 'Benachrichtigungen für Aktivitäten auf Arbeitspaketen erhalten, wenn ich zugewiesen oder verantwortlich bin' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Aktualisierungen auf beobachteten Objekten' work_package_commented: 'Alle neuen Kommentare' work_package_created: 'Neue Arbeitspakete' diff --git a/config/locales/crowdin/js-el.yml b/config/locales/crowdin/js-el.yml index f93ba0ccd9..8a627112cc 100644 --- a/config/locales/crowdin/js-el.yml +++ b/config/locales/crowdin/js-el.yml @@ -550,6 +550,7 @@ el: all: 'Όλα' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -583,9 +584,8 @@ el: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-eo.yml b/config/locales/crowdin/js-eo.yml index 39000ce5dc..106615349b 100644 --- a/config/locales/crowdin/js-eo.yml +++ b/config/locales/crowdin/js-eo.yml @@ -551,6 +551,7 @@ eo: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ eo: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-es.yml b/config/locales/crowdin/js-es.yml index 87b6753fe8..163aabbe21 100644 --- a/config/locales/crowdin/js-es.yml +++ b/config/locales/crowdin/js-es.yml @@ -551,6 +551,7 @@ es: all: 'Todos' all_title: 'Mostrar todos' center: + label_actor_and: 'and' and_more_users: one: 'y 1 más' other: 'y %{count} más' @@ -584,9 +585,8 @@ es: mentioned: title: 'Estoy @mencionado' description: 'Recibir una notificación cada vez que un usuario me mencione' - involved: - title: 'Asignado a mi usuario o responsable' - description: 'Recibe notificaciones para todas las actividades relacionadas con paquetes de trabajo en los cuales mi rol es asignado/a o responsable' + assignee: 'Asignado a' + responsible: 'Responsable' watched: 'Actualizaciones en elementos observados' work_package_commented: 'Todos los nuevos comentarios' work_package_created: 'Nuevos paquetes de trabajo' diff --git a/config/locales/crowdin/js-et.yml b/config/locales/crowdin/js-et.yml index fa3a4aeb60..2cdb7ce6de 100644 --- a/config/locales/crowdin/js-et.yml +++ b/config/locales/crowdin/js-et.yml @@ -551,6 +551,7 @@ et: all: 'Kõik' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ et: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-fa.yml b/config/locales/crowdin/js-fa.yml index 72f3b00e14..2488d017c0 100644 --- a/config/locales/crowdin/js-fa.yml +++ b/config/locales/crowdin/js-fa.yml @@ -551,6 +551,7 @@ fa: all: 'همه' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ fa: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-fi.yml b/config/locales/crowdin/js-fi.yml index b4c424ae6d..b8c3255f0b 100644 --- a/config/locales/crowdin/js-fi.yml +++ b/config/locales/crowdin/js-fi.yml @@ -551,6 +551,7 @@ fi: all: 'Kaikki' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ fi: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-fil.yml b/config/locales/crowdin/js-fil.yml index 2b253916e5..b34c761901 100644 --- a/config/locales/crowdin/js-fil.yml +++ b/config/locales/crowdin/js-fil.yml @@ -551,6 +551,7 @@ fil: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ fil: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-fr.yml b/config/locales/crowdin/js-fr.yml index 0ef094694d..903fec2aa7 100644 --- a/config/locales/crowdin/js-fr.yml +++ b/config/locales/crowdin/js-fr.yml @@ -551,6 +551,7 @@ fr: all: 'Toutes' all_title: 'Afficher tout' center: + label_actor_and: 'and' and_more_users: one: 'et 1 autre' other: 'et %{count} autres' @@ -584,9 +585,8 @@ fr: mentioned: title: 'Je suis @mentionné' description: 'Recevoir une notification chaque fois que quelqu''un me mentionne n''importe où' - involved: - title: 'Assigné à moi ou responsable' - description: 'Recevez des notifications pour toutes les activités sur les lots de travaux qui vous ont été assignés' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Mises à jour sur les éléments surveillés' work_package_commented: 'Tous les nouveaux commentaires' work_package_created: 'Nouveaux lots de travaux' diff --git a/config/locales/crowdin/js-he.yml b/config/locales/crowdin/js-he.yml index 0e3d3603d6..123255c513 100644 --- a/config/locales/crowdin/js-he.yml +++ b/config/locales/crowdin/js-he.yml @@ -551,6 +551,7 @@ he: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' two: 'and %{count} others' @@ -586,9 +587,8 @@ he: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-hi.yml b/config/locales/crowdin/js-hi.yml index 4fe72916e9..7393071ab7 100644 --- a/config/locales/crowdin/js-hi.yml +++ b/config/locales/crowdin/js-hi.yml @@ -551,6 +551,7 @@ hi: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ hi: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-hr.yml b/config/locales/crowdin/js-hr.yml index f06a179090..8c3acc053f 100644 --- a/config/locales/crowdin/js-hr.yml +++ b/config/locales/crowdin/js-hr.yml @@ -551,6 +551,7 @@ hr: all: 'Svi' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' few: 'and %{count} others' @@ -585,9 +586,8 @@ hr: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-hu.yml b/config/locales/crowdin/js-hu.yml index b6073de3aa..0c18211273 100644 --- a/config/locales/crowdin/js-hu.yml +++ b/config/locales/crowdin/js-hu.yml @@ -551,6 +551,7 @@ hu: all: 'Mind' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: "és 1 másik\n" other: 'és %{count} egyéb' @@ -584,9 +585,8 @@ hu: mentioned: title: 'Megemlítettek' description: 'Értesítés fogadása minden alkalommal, amikor bárki bárhol megemlít' - involved: - title: 'Hozzám vagy a felelőshöz rendelve' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'Új megjegyzések' work_package_created: 'Új munkacsomagok' diff --git a/config/locales/crowdin/js-id.yml b/config/locales/crowdin/js-id.yml index 40980d540c..abc172b16b 100644 --- a/config/locales/crowdin/js-id.yml +++ b/config/locales/crowdin/js-id.yml @@ -551,6 +551,7 @@ id: all: 'Semua' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: other: 'and %{count} others' no_results: @@ -583,9 +584,8 @@ id: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-it.yml b/config/locales/crowdin/js-it.yml index db19c3c621..a64a6a711b 100644 --- a/config/locales/crowdin/js-it.yml +++ b/config/locales/crowdin/js-it.yml @@ -551,6 +551,7 @@ it: all: 'Tutti' all_title: 'Mostra tutto' center: + label_actor_and: 'and' and_more_users: one: 'e 1 altro' other: 'e altri %{count}' @@ -584,9 +585,8 @@ it: mentioned: title: 'Ricevo una @menzione' description: 'Ricevi una notifica ogni volta che qualcuno ti menziona ovunque' - involved: - title: 'Assegnato a me o responsabile' - description: 'Ricevi notifiche per tutte le attività sui pacchetti di lavoro di cui sei assegnatario o responsabile.' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Aggiornamenti sugli elementi osservati' work_package_commented: 'Tutti i nuovi commenti' work_package_created: 'Nuove macro-attività' diff --git a/config/locales/crowdin/js-ja.yml b/config/locales/crowdin/js-ja.yml index 4456c8ba22..e6e96c4b22 100644 --- a/config/locales/crowdin/js-ja.yml +++ b/config/locales/crowdin/js-ja.yml @@ -552,6 +552,7 @@ ja: all: '全て' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: other: 'and %{count} others' no_results: @@ -584,9 +585,8 @@ ja: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-ko.yml b/config/locales/crowdin/js-ko.yml index 9b90d4f607..fa94315224 100644 --- a/config/locales/crowdin/js-ko.yml +++ b/config/locales/crowdin/js-ko.yml @@ -551,6 +551,7 @@ ko: all: '모두' all_title: '모두 표시' center: + label_actor_and: 'and' and_more_users: other: '외 %{count}건' no_results: @@ -583,9 +584,8 @@ ko: mentioned: title: '내가 @멘션되었습니다' description: '어디에서든 누군가가 나를 멘션할 때마다 알림 받기' - involved: - title: '내게 할당되거나 내가 담당함' - description: '내게 할당되거나 내가 담당하는 작업 패키지의 모든 활동에 대한 알림을 받습니다.' + assignee: 'Assignee' + responsible: 'Accountable' watched: '지켜보는 항목에 대한 업데이트' work_package_commented: '모든 새로운 코멘트' work_package_created: '새 작업 패키지' diff --git a/config/locales/crowdin/js-lol.yml b/config/locales/crowdin/js-lol.yml index bf318a9725..4bd3ec8622 100644 --- a/config/locales/crowdin/js-lol.yml +++ b/config/locales/crowdin/js-lol.yml @@ -550,6 +550,7 @@ lol: all: 'crwdns787696:0crwdne787696:0' all_title: 'crwdns833312:0crwdne833312:0' center: + label_actor_and: 'crwdns835484:0crwdne835484:0' and_more_users: one: 'crwdns787698:1crwdne787698:1' other: 'crwdns787698:5%{count}crwdne787698:5' @@ -583,9 +584,8 @@ lol: mentioned: title: 'crwdns787742:0crwdne787742:0' description: 'crwdns787744:0crwdne787744:0' - involved: - title: 'crwdns787746:0crwdne787746:0' - description: 'crwdns787748:0crwdne787748:0' + assignee: 'crwdns834614:0crwdne834614:0' + responsible: 'crwdns834616:0crwdne834616:0' watched: 'crwdns787750:0crwdne787750:0' work_package_commented: 'crwdns787752:0crwdne787752:0' work_package_created: 'crwdns787754:0crwdne787754:0' diff --git a/config/locales/crowdin/js-lt.yml b/config/locales/crowdin/js-lt.yml index a4c60912c1..05aaf6aefe 100644 --- a/config/locales/crowdin/js-lt.yml +++ b/config/locales/crowdin/js-lt.yml @@ -551,6 +551,7 @@ lt: all: 'Visi' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'ir dar 1' few: 'ir dar %{count}' @@ -586,9 +587,8 @@ lt: mentioned: title: 'Aš @paminėtas' description: 'Gauti pranešimą kiekvieną kartą, kai kas nors mane bet kur pamini' - involved: - title: 'Priskirta man arba atsakingas' - description: 'Gauti pranešimus apie visus veiksmus su darbų paketais, kuriuose aš arba paskirtasis, arba atsakingas' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Stebimų elementų pakeitimai' work_package_commented: 'Visi nauji komentarai' work_package_created: 'Nauji darbų paketai' diff --git a/config/locales/crowdin/js-lv.yml b/config/locales/crowdin/js-lv.yml index e5e6c32900..446b5a7be8 100644 --- a/config/locales/crowdin/js-lv.yml +++ b/config/locales/crowdin/js-lv.yml @@ -551,6 +551,7 @@ lv: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: zero: 'and %{count} others' one: 'and 1 other' @@ -585,9 +586,8 @@ lv: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-ne.yml b/config/locales/crowdin/js-ne.yml index d0d51833d4..8496e0abe8 100644 --- a/config/locales/crowdin/js-ne.yml +++ b/config/locales/crowdin/js-ne.yml @@ -551,6 +551,7 @@ ne: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ ne: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-nl.yml b/config/locales/crowdin/js-nl.yml index 059e04eb99..24ac9b4a09 100644 --- a/config/locales/crowdin/js-nl.yml +++ b/config/locales/crowdin/js-nl.yml @@ -26,9 +26,9 @@ nl: loading: "Laden…" updating: "Bijwerken…" attachments: - delete: "Delete attachment" + delete: "Bijlage verwijderen" delete_confirmation: | - Are you sure you want to delete this file? This action is not reversible. + Weet je zeker dat je dit bestand wilt verwijderen? Deze actie is niet omkeerbaar. draggable_hint: | Sleep op het invoerveld naar in line afbeelding of verwijzing bijlage. Gesloten invoervelden wordt geopend terwijl u blijft slepen. autocomplete_select: @@ -85,10 +85,10 @@ nl: button_open_fullscreen: "Open volledig scherm" button_show_cards: "Toon kaartweergave" button_show_list: "Toon lijstweergave" - button_show_table: "Show table view" - button_show_gantt: "Show Gantt view" - button_show_fullscreen: "Show fullscreen view" - button_more_actions: "More actions" + button_show_table: "Tabel weergave tonen" + button_show_gantt: "Toon Gantt-weergave" + button_show_fullscreen: "Volledig scherm weergeven" + button_more_actions: "Meer acties" button_quote: "Citeer" button_save: "Opslaan" button_settings: "Instellingen" @@ -301,11 +301,11 @@ nl: standard: learn_about_link: https://www.openproject.org/blog/openproject-12-3-release new_features_html: > - The release contains various new features and improvements:
+ De release bevat verschillende nieuwe functies en verbeteringen:
bim: learn_about_link: https://www.openproject.org/blog/openproject-12-3-release new_features_html: > - The release contains various new features and improvements:
+ De release bevat verschillende nieuwe functies en verbeteringen:
label_activate: "Activeren" label_assignee: 'Toegewezene' label_add_column_after: "Kolom toevoegen" @@ -547,46 +547,46 @@ nl: prioritized: 'prioriteren' facets: unread: 'Ongelezen' - unread_title: 'Show unread' + unread_title: 'Toon ongelezen' all: 'Alle' - all_title: 'Show all' + all_title: 'Alles weergeven' center: + label_actor_and: 'and' and_more_users: one: 'en 1 ander' other: 'en %{count} anderen' no_results: - at_all: 'New notifications will appear here when there is activity that concerns you, in the mean time you can also view and modify your notification settings to configure when to be notified.' - with_current_filter: 'There are no notifications in this view at the moment' + at_all: 'Nieuwe meldingen verschijnen hier wanneer er activiteit is die je aangaat, in de tussentijd kunt u ook uw meldingsinstellingen bekijken en wijzigen zodat u kunt configureren wanneer u een melding moet ontvangen.' + with_current_filter: 'Er zijn geen meldingen in deze weergave op dit moment' mark_all_read: 'Alles als gelezen markeren' mark_as_read: 'Als gelezen markeren' text_update_date: "%{date} door" total_count_warning: "De %{newest_count} meest recente meldingen weergeven. %{more_count} meer worden niet weergegeven." empty_state: no_notification: "Het lijkt erop dat u helemaal bij bent." - no_notification_with_current_project_filter: "Looks like you're all caught up with the selected project." - no_notification_with_current_filter: "Looks like you're all caught up for %{filter} filter." + no_notification_with_current_project_filter: "Het lijkt erop dat je allemaal bij bent met het geselecteerde project." + no_notification_with_current_filter: "Het lijkt erop dat je allemaal bij bent met %{filter} filter." no_selection: "Klik op een melding om alle activiteit details te bekijken." new_notifications: - message: 'There are new notifications.' - link_text: 'Click here to load them' + message: 'Er zijn nieuwe meldingen.' + link_text: 'Klik hier om ze te laden' menu: accountable: 'Verantwoording afleggen' - by_project: 'Unread by project' - by_reason: 'Involvement' + by_project: 'Ongelezen per project' + by_reason: 'Betrokkenheid' inbox: 'Inkomend' mentioned: '@vermeld' watching: 'Bekijken' settings: - change_notification_settings: 'To view and change your notification settings, click here' + change_notification_settings: 'Om je meldingen instellingen te bekijken en te wijzigen, klik hier' title: "Instellingen voor meldingen" notify_me: "Waarschuw mij" reasons: mentioned: title: 'Ik ben @vermeld' description: 'Ontvang een melding elke keer dat iemand mij ergens noemt' - involved: - title: 'Toegewezen aan mij of verantwoordelijk' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Toegewezene' + responsible: 'Accountable' watched: 'Updates van bekeken artikelen' work_package_commented: 'Alle nieuwe reacties' work_package_created: 'Nieuwe werkpakketten' diff --git a/config/locales/crowdin/js-no.yml b/config/locales/crowdin/js-no.yml index a21d015e99..a880742760 100644 --- a/config/locales/crowdin/js-no.yml +++ b/config/locales/crowdin/js-no.yml @@ -551,6 +551,7 @@ all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-pl.yml b/config/locales/crowdin/js-pl.yml index 0528d88a41..869235574b 100644 --- a/config/locales/crowdin/js-pl.yml +++ b/config/locales/crowdin/js-pl.yml @@ -551,6 +551,7 @@ pl: all: 'Wszystko' all_title: 'Pokaż wszystkie' center: + label_actor_and: 'and' and_more_users: one: 'i 1 inny' few: 'i %{count} inne' @@ -586,9 +587,8 @@ pl: mentioned: title: '@wzmianka o mnie' description: 'Otrzymuj powiadomienie za każdym razem, gdy pojawi się o tobie wzmianka gdziekolwiek' - involved: - title: 'Przypisane do mnie lub moja odpowiedzialność' - description: 'Otrzymuj powiadomienia o wszystkich czynnościach w pakietach roboczych, do których jesteś przypisany lub za które ponosisz odpowiedzialność' + assignee: 'Przypisana osoba' + responsible: 'Osoba odpowiedzialna' watched: 'Aktualizacje obserwowanych pozycji' work_package_commented: 'Wszystkie nowe komentarze' work_package_created: 'Nowe pakiety robocze' diff --git a/config/locales/crowdin/js-pt.yml b/config/locales/crowdin/js-pt.yml index f7a5ca70da..0d36e10e7a 100644 --- a/config/locales/crowdin/js-pt.yml +++ b/config/locales/crowdin/js-pt.yml @@ -550,6 +550,7 @@ pt: all: 'Todos' all_title: 'Mostrar todas' center: + label_actor_and: 'and' and_more_users: one: 'e 1 outro' other: 'e %{count} outros' @@ -583,9 +584,8 @@ pt: mentioned: title: 'Eu sou @mencionado' description: 'Receber uma notificação sempre que alguém me mencionar' - involved: - title: 'Atribuído a mim ou responsável' - description: 'Receba notificações para todas as atividades nos pacotes de trabalho atribuídos a mim ou de minha responsabilidade' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Atualizações em itens observados' work_package_commented: 'Todos os novos comentários' work_package_created: 'Novos pacotes de trabalho' diff --git a/config/locales/crowdin/js-ro.yml b/config/locales/crowdin/js-ro.yml index 9134768f07..cbf4c80c18 100644 --- a/config/locales/crowdin/js-ro.yml +++ b/config/locales/crowdin/js-ro.yml @@ -550,6 +550,7 @@ ro: all: 'Toate' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'și încă unul' few: 'și %{count} alții' @@ -584,9 +585,8 @@ ro: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-ru.yml b/config/locales/crowdin/js-ru.yml index 879017f874..ea8138b601 100644 --- a/config/locales/crowdin/js-ru.yml +++ b/config/locales/crowdin/js-ru.yml @@ -550,6 +550,7 @@ ru: all: 'Bсе' all_title: 'Показать все' center: + label_actor_and: 'and' and_more_users: one: 'и еще 1' few: 'и %{count} других' @@ -585,9 +586,8 @@ ru: mentioned: title: 'Я @mentioned' description: 'Получать уведомления каждый раз, когда кто-то упоминает меня в любом месте' - involved: - title: 'Назначен мне или подотчетен' - description: 'Получать уведомления о всех видах деятельности в пакетах работ, к которым я причастен.' + assignee: 'Назначенный' + responsible: 'Подотчетный' watched: 'Обновления просмотренных элементов' work_package_commented: 'Все новые комментарии' work_package_created: 'Новые пакеты работ' @@ -1187,7 +1187,7 @@ ru: next_button: 'Следующие' message: label: 'Сообщение о приглашении' - description: 'We will send an email to the user, to which you can add a personal message here. An explanation for the invitation could be useful, or prehaps a bit of information regarding the project to help them get started.' + description: 'Мы отправим пользователю электронное письмо, к которому вы можете что-нибудь добавить. Приглашение облегчит им начало работы, если у них будет немного информации о проекте.' next_button: 'Далее' summary: next_button: 'Отправить приглашение' diff --git a/config/locales/crowdin/js-rw.yml b/config/locales/crowdin/js-rw.yml index afa6edf26e..413faf2d4d 100644 --- a/config/locales/crowdin/js-rw.yml +++ b/config/locales/crowdin/js-rw.yml @@ -551,6 +551,7 @@ rw: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ rw: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-si.yml b/config/locales/crowdin/js-si.yml index a62ba8fb34..fe1d1c6a40 100644 --- a/config/locales/crowdin/js-si.yml +++ b/config/locales/crowdin/js-si.yml @@ -551,6 +551,7 @@ si: all: 'සියලු' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -584,9 +585,8 @@ si: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-sk.yml b/config/locales/crowdin/js-sk.yml index ce4e7601e4..3a5c9091df 100644 --- a/config/locales/crowdin/js-sk.yml +++ b/config/locales/crowdin/js-sk.yml @@ -551,6 +551,7 @@ sk: all: 'Všetky' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' few: 'and %{count} others' @@ -586,9 +587,8 @@ sk: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-sl.yml b/config/locales/crowdin/js-sl.yml index 189060f20e..f94c49427e 100644 --- a/config/locales/crowdin/js-sl.yml +++ b/config/locales/crowdin/js-sl.yml @@ -550,6 +550,7 @@ sl: all: 'Vse' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' two: 'and %{count} others' @@ -585,9 +586,8 @@ sl: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-sv.yml b/config/locales/crowdin/js-sv.yml index 0c1ed712cc..38ff110a57 100644 --- a/config/locales/crowdin/js-sv.yml +++ b/config/locales/crowdin/js-sv.yml @@ -550,6 +550,7 @@ sv: all: 'Alla' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -583,9 +584,8 @@ sv: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-th.yml b/config/locales/crowdin/js-th.yml index 4f4223c839..bb9b15db36 100644 --- a/config/locales/crowdin/js-th.yml +++ b/config/locales/crowdin/js-th.yml @@ -551,6 +551,7 @@ th: all: 'ทั้งหมด' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: other: 'and %{count} others' no_results: @@ -583,9 +584,8 @@ th: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-tr.yml b/config/locales/crowdin/js-tr.yml index 0e5f1cb198..fe25c9c3e1 100644 --- a/config/locales/crowdin/js-tr.yml +++ b/config/locales/crowdin/js-tr.yml @@ -551,6 +551,7 @@ tr: all: 'Hepsi' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 've 1 diğer' other: 've 1 diğer' @@ -584,9 +585,8 @@ tr: mentioned: title: 'benden @bahsedildi' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'Yeni iş paketleri' diff --git a/config/locales/crowdin/js-uk.yml b/config/locales/crowdin/js-uk.yml index 738167da86..947fdc2e09 100644 --- a/config/locales/crowdin/js-uk.yml +++ b/config/locales/crowdin/js-uk.yml @@ -551,6 +551,7 @@ uk: all: 'Всі' all_title: 'Показати всі' center: + label_actor_and: 'and' and_more_users: one: 'і ще 1' few: 'і ще %{count}' @@ -586,9 +587,8 @@ uk: mentioned: title: 'Мене @згадано' description: 'Отримувати сповіщення щоразу, коли хтось згадує мене де завгодно' - involved: - title: 'Призначено мені або відповідальній особі' - description: 'Отримувати сповіщення про всі дії щодо пакетів робіт, які я виконую або за які несу відповідальність' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Оновлення щодо відстежуваних елементів' work_package_commented: 'Усі нові коментарі' work_package_created: 'Нові пакети робіт' diff --git a/config/locales/crowdin/js-vi.yml b/config/locales/crowdin/js-vi.yml index f5104f3cd0..9635911237 100644 --- a/config/locales/crowdin/js-vi.yml +++ b/config/locales/crowdin/js-vi.yml @@ -550,6 +550,7 @@ vi: all: 'Toàn bộ' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: other: 'and %{count} others' no_results: @@ -582,9 +583,8 @@ vi: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/js-zh-TW.yml b/config/locales/crowdin/js-zh-TW.yml index c6734aa7dd..5fab7b02e6 100644 --- a/config/locales/crowdin/js-zh-TW.yml +++ b/config/locales/crowdin/js-zh-TW.yml @@ -550,6 +550,7 @@ zh-TW: all: '全部' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: other: 'and %{count} others' no_results: @@ -582,9 +583,8 @@ zh-TW: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: '分配給我或由我負責' - description: '對所有分配給我或由我負責的工作包接收所有活動通知' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index 30575a21dc..d771fed9e7 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -210,6 +210,15 @@ ko: ignore_filenames: > 받는 메일의 첨부 파일을 처리할 때 무시할 이름 목록을 지정하십시오(예: 서명 또는 아이콘). 라인 하나에 파일 이름 하나를 입력하십시오. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "삭제가 예약되었으며 백그라운드에서 수행됩니다. 결과 알림이 제공될 것입니다." schedule_failed: "프로젝트를 삭제할 수 없습니다: %{errors}" @@ -1901,7 +1910,6 @@ ko: label_wiki_start: "시작 페이지" label_work_package: "작업 패키지" label_work_package_attachments: "작업 패키지 첨부 파일" - label_work_package_file_link_plural: "작업 패키지 파일 링크" label_work_package_category_new: "새 카테고리" label_work_package_category_plural: "작업 패키지 카테고리" label_work_package_hierarchy: "작업 패키지 계층 구조" diff --git a/config/locales/crowdin/lol.yml b/config/locales/crowdin/lol.yml index 8b702b27bf..189a539a41 100644 --- a/config/locales/crowdin/lol.yml +++ b/config/locales/crowdin/lol.yml @@ -198,6 +198,15 @@ lol: ignore_filenames: > crwdns494927:0crwdne494927:0 projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'crwdns835410:0crwdne835410:0' + overviews: 'crwdns835412:0crwdne835412:0' + queries: 'crwdns835414:0crwdne835414:0' + wiki_page_attachments: 'crwdns835416:0crwdne835416:0' + work_package_attachments: 'crwdns835418:0crwdne835418:0' + work_package_categories: 'crwdns835420:0crwdne835420:0' + work_package_file_links: 'crwdns835422:0crwdne835422:0' delete: scheduled: "crwdns494929:0crwdne494929:0" schedule_failed: "crwdns494931:0%{errors}crwdne494931:0" @@ -1896,7 +1905,6 @@ lol: label_wiki_start: "crwdns497855:0crwdne497855:0" label_work_package: "crwdns497857:0crwdne497857:0" label_work_package_attachments: "crwdns497859:0crwdne497859:0" - label_work_package_file_link_plural: "crwdns830182:0crwdne830182:0" label_work_package_category_new: "crwdns497861:0crwdne497861:0" label_work_package_category_plural: "crwdns497863:0crwdne497863:0" label_work_package_hierarchy: "crwdns497865:0crwdne497865:0" diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index 8cc6d5548c..6b3853e40d 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -207,6 +207,15 @@ lt: ignore_filenames: > Nurodykite sąrašą, kurį reikia ignoruoti apdorojant įeinančių laiškų priedus (pvz. parašus ar piešinius). Įveskite po vieną failo pavadinimą eilutėje. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Naikinimas suplanuotas ir vykdomas fone. Jums bus pranešta apie rezultatą." schedule_failed: "Projekto negalima ištrinti: %{errors}" @@ -1950,7 +1959,6 @@ lt: label_wiki_start: "Pradžios puslapis" label_work_package: "Darbų paketas" label_work_package_attachments: "Darbų paketo prisegti failai" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Nauja kategorija" label_work_package_category_plural: "Darbų paketo kategorijos" label_work_package_hierarchy: "Darbų paketo hierarchija" diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index fd4a6840c3..b24b9958db 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -210,6 +210,15 @@ lv: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Projektu nav iespējams dzēst: %{errors}" @@ -1935,7 +1944,6 @@ lv: label_wiki_start: "Sākuma lapa" label_work_package: "Work package" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "New category" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index 9eaa8faff0..6c937e27d2 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -210,6 +210,15 @@ ne: ignore_filenames: > आगामी मेलहरूका लागि अनुलग्नकहरू प्रशोधन गर्दा उपेक्षा गर्न नामहरूको सूची निर्दिष्ट गर्नुहोस् (जस्तै, हस्ताक्षरहरू वा प्रतिमाहरू)। प्रति रेखा एक फाइलको नाम प्रविष्ट गर्नुहोस्। projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "परियोजना मेटिने छैन: %{errors}" @@ -1919,7 +1928,6 @@ ne: label_wiki_start: "Start page" label_work_package: "Work package" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "New category" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index 913054f520..16267dee20 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -67,8 +67,8 @@ nl: upgrade_to_ee: "Upgrade naar de Enterprise Edition" add_token: "Uploadeneen Enterprise Edition ondersteuningstoken" delete_token_modal: - text: "Are you sure you want to remove the current Enterprise Edition token used?" - title: "Delete token" + text: "Weet u zeker dat u de huidige gebruikte Enterprise Edition token wilt verwijderen?" + title: "Verwijder token" replace_token: "Uw huidige ondersteuningstoken vervangen" order: "Order Enterprise on premises Edition" paste: "Plak uw Enterprise Edition ondersteuningstoken" @@ -207,6 +207,15 @@ nl: ignore_filenames: > Geef een lijst van namen op om te negeren bij het verwerken van bijlagen voor inkomende mails (bijv. handtekeningen of iconen). Voer één bestandsnaam per regel in. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project leden' + overviews: 'Project overzicht' + queries: 'Werkpakketten: opgeslagen weergaven' + wiki_page_attachments: 'Wiki pagina''s: bijlagen' + work_package_attachments: 'Werkpakketten: bijlagen' + work_package_categories: 'Werkpakketten: categorieën' + work_package_file_links: 'Werkpakketten: bestandslinks' delete: scheduled: "Het verwijderen is gepland en wordt op de achtergrond uitgevoerd. U wordt op de hoogte gebracht van het resultaat." schedule_failed: "Project kan niet worden verwijderd: %{errors}" @@ -529,9 +538,9 @@ nl: end_deletion: "Einde van de verwijdering" ignore_non_working_days: "Negeer niet-werkdagen" include_non_working_days: - title: 'Working days' - false: 'working days only' - true: 'include non-working days' + title: 'Werkdagen' + false: 'Alleen werkdagen' + true: 'inclusief niet-werkdagen' parent: "Bovenliggend werkpakket" parent_issue: "Bovenliggend werkpakket" parent_work_package: "Bovenliggend werkpakket" @@ -731,8 +740,8 @@ nl: setting: attributes: base: - working_days_are_missing: 'At least one day of the week must be defined as a working day.' - previous_working_day_changes_unprocessed: 'The previous changes to the working days configuration have not been applied yet.' + working_days_are_missing: 'Ten minste één dag van de week moet worden gedefinieerd als een werkdag.' + previous_working_day_changes_unprocessed: 'De vorige wijzigingen in de configuratie van de werkdagen zijn nog niet toegepast.' time_entry: attributes: hours: @@ -1916,7 +1925,6 @@ nl: label_wiki_start: "Startpagina" label_work_package: "Werkpakket" label_work_package_attachments: "Werkpakket bijlagen" - label_work_package_file_link_plural: "Werkpakket bestandskoppelingen" label_work_package_category_new: "Nieuwe categorie" label_work_package_category_plural: "Werkpakket categorieën" label_work_package_hierarchy: "Werkpakket hiërarchie" @@ -1932,7 +1940,7 @@ nl: label_workflow: "Werkstroom" label_workflow_plural: "Workflows" label_workflow_summary: "Samenvatting" - label_working_days: "Working days" + label_working_days: "Werkdagen" label_x_closed_work_packages_abbr: one: "1 gesloten" other: "%{count} gesloten" @@ -1993,7 +2001,7 @@ nl: center: 'Naar meldingscentrum' see_in_center: 'Bekijk commentaar in notificatie center' settings: 'E-mailinstellingen wijzigen' - salutation: 'Hello %{user}' + salutation: 'Hallo %{user}' work_packages: created_at: 'Aangemaakt op %{timestamp} door %{user} ' login_to_see_all: 'Log in om alle meldingen te zien.' @@ -2171,7 +2179,7 @@ nl: tb: "TB" onboarding: heading_getting_started: "Krijg een overzicht" - text_getting_started_description: "Get a quick overview of project management and team collaboration with OpenProject. You can restart this video from the help menu." + text_getting_started_description: "Krijg een snel overzicht van projectbeheer en teamsamenwerking met OpenProject. U kunt deze video opnieuw starten vanuit het helpmenu." welcome: "Welkom bij %{app_title}" select_language: "Gelieve uw taal te kiezen" permission_add_work_package_notes: "Notities toevoegen" @@ -2563,7 +2571,7 @@ nl: text_access_token_hint: "Toegangstokens kunnen u externe toepassingen toegang verlenen tot bronnen in OpenProject." text_analyze: "Verdere analyse: %{subject}" text_are_you_sure: "Weet u het zeker?" - text_are_you_sure_continue: "Are you sure you want to continue?" + text_are_you_sure_continue: "Weet u zeker dat u wilt doorgaan?" text_are_you_sure_with_children: "Verwijder werk pakket en alle kinderen daarvan?" text_assign_to_project: "Wijs aan project toe" text_form_configuration: > @@ -2912,15 +2920,15 @@ nl: title: "Werkpakket verwijderen" working_days: info: > - Define the working days of the week
Non-working days are skipped when scheduling work packages and are excluded when calculating duration. This can be overridden at a work-package level. - change_button: "Change working days" + Definieer de werkdagen van de week
Niet-werkdagen worden overgeslagen bij het plannen van werkpakketten en worden uitgesloten bij het berekenen van de duur. Dit kan op werkpakketniveau worden overschreven. + change_button: "Wijzig werkdagen" warning: > - Changing which days of the week are considered working days can affect the start and finish days of all work packages in all projects in this instance. + Veranderen van welke dagen van de week als werkdagen worden beschouwd, kan van invloed zijn op de begin- en einddagen van alle werkpakketten in deze instantie. journal_note: - changed: _**Working days** changed (%{changes})._ + changed: _**Werkdagen** veranderd (%{changes})._ days: - working: "%{day} is now working" - non_working: "%{day} is now non-working" + working: "%{day} werkt nu" + non_working: "%{day} is nu niet actief" nothing_to_preview: "Niets om te bekijken" api_v3: attributes: diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index ce9e59c973..895c01a135 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -210,6 +210,15 @@ ignore_filenames: > Angi en liste over navn som skal ignoreres når vedlegg for innkommende meldinger (f.eks. signaturer eller ikoner). Angi ett filnavn per linje. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Prosjektet kan ikke slettes: %{errors}" @@ -1919,7 +1928,6 @@ label_wiki_start: "Startside" label_work_package: "Arbeidspakke" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Ny kategori" label_work_package_category_plural: "Kategorier for arbeidspakker" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index 3ed86d73cd..dabf1e4dbe 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -207,6 +207,15 @@ pl: ignore_filenames: > Określ listę nazw, które mają być ignorowane podczas przetwarzania załączników do poczty przychodzącej (np. sygnatur lub ikon). W każdym wierszu prowadź jedną nazwę pliku. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Członkowie projektu' + overviews: 'Przegląd projektu' + queries: 'Pakiety robocze: zapisane widoki' + wiki_page_attachments: 'Strony Wiki: załączniki' + work_package_attachments: 'Pakiety robocze: załączniki' + work_package_categories: 'Pakiety robocze: kategorie' + work_package_file_links: 'Pakiety robocze: linki do pliku' delete: scheduled: "Usunięcie zostało zaplanowane i jest wykonywane w tle. Otrzymasz powiadomienie o wyniku." schedule_failed: "Nie można usunąc projektu: %{errors}" @@ -1950,7 +1959,6 @@ pl: label_wiki_start: "Strona startowa" label_work_package: "Zadanie" label_work_package_attachments: "Załączniki pakietu roboczego" - label_work_package_file_link_plural: "Linki do plików pakietu roboczego" label_work_package_category_new: "Nowa kategoria" label_work_package_category_plural: "Kategorie pakietów roboczych" label_work_package_hierarchy: "Hierarchia zadań" diff --git a/config/locales/crowdin/pt.yml b/config/locales/crowdin/pt.yml index 3e0e5e03c6..40095efcfe 100644 --- a/config/locales/crowdin/pt.yml +++ b/config/locales/crowdin/pt.yml @@ -209,6 +209,15 @@ pt: ignore_filenames: > Especifique uma lista de nomes para ignorar quando processar anexos para mensagens recebidas (por exemplo, assinaturas ou ícones). Digite um nome de arquivo por linha. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Exclusão foi agendada e será realizada em segundo plano. Você será notificado do resultado." schedule_failed: "Projeto não pode ser excluído: %{errors}" @@ -1918,7 +1927,6 @@ pt: label_wiki_start: "Página inicial" label_work_package: "Pacote de trabalho" label_work_package_attachments: "Anexos de pacote de trabalho" - label_work_package_file_link_plural: "Links de arquivos do pacote de trabalho" label_work_package_category_new: "Nova categoria" label_work_package_category_plural: "Categorias de pacote de trabalho" label_work_package_hierarchy: "Hierarquia de pacotes de trabalho" diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index 6d44a9e63b..eadb1b758f 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -210,6 +210,15 @@ ro: ignore_filenames: > Specificați o listă de nume care trebuie ignorate la procesarea atașamentelor pentru mesajele primite (de exemplu, semnături sau pictograme). Introduceți un nume de fișier pe linie. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Ștergerea a fost programată și se efectuează în fundal. Veți fi notificat cu privire la rezultat." schedule_failed: "Proiectul nu poate fi șters: %{errors}" @@ -1936,7 +1945,6 @@ ro: label_wiki_start: "Pagină de start" label_work_package: "Pachet de lucru" label_work_package_attachments: "Anexe la pachetul de lucru" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Categorie nouă" label_work_package_category_plural: "Categorii de pachete de lucru" label_work_package_hierarchy: "Ierarhia pachetelor de lucru" diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index d541a9e955..cb94a9b93c 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -209,6 +209,15 @@ ru: ignore_filenames: > Укажите список имен для игнорирования при обработке вложений для входящих почтовых сообщений (например, подписей или значков). Введите одно имя файла в строке. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Участники проекта' + overviews: 'Обзор проекта' + queries: 'Пакеты работ: сохраненные виды' + wiki_page_attachments: 'Вики-страницы: вложения' + work_package_attachments: 'Пакеты работ: вложения' + work_package_categories: 'Пакеты работ: категории' + work_package_file_links: 'Пакеты работ: ссылки на файлы' delete: scheduled: "Удаление было запланировано и исполнено в фоновом режиме. О результатах вы будете уведомлены." schedule_failed: "Проект удален быть не может: %{errors}" @@ -735,7 +744,7 @@ ru: setting: attributes: base: - working_days_are_missing: 'At least one day of the week must be defined as a working day.' + working_days_are_missing: 'По крайней мере один день недели должен быть определен как рабочий.' previous_working_day_changes_unprocessed: 'Предыдущие изменения конфигурации рабочих дней еще не были применены.' time_entry: attributes: @@ -1952,7 +1961,6 @@ ru: label_wiki_start: "Начальная страница" label_work_package: "Пакет работ" label_work_package_attachments: "Вложения пакета работ" - label_work_package_file_link_plural: "Ссылки на файл пакета работ" label_work_package_category_new: "Новая категория" label_work_package_category_plural: "Категории пакета работ" label_work_package_hierarchy: "Иерархия пакета работ" @@ -2954,7 +2962,7 @@ ru: title: "Удалить пакет работ" working_days: info: > - Define the working days of the week
Non-working days are skipped when scheduling work packages and are excluded when calculating duration. This can be overridden at a work-package level. + Определить рабочие дни недели
Нерабочие дни пропускаются при планировании пакетов работ и исключаются при расчете продолжительности. Это можно переопределить на уровне рабочих пакетов. change_button: "Изменить рабочие дни" warning: > Изменение дней недели, считающихся рабочими днями, может повлиять на начало и завершение всех пакетов работ во всех проектах в данном случае. diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index 0f14908202..95077f4e35 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -210,6 +210,15 @@ rw: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1919,7 +1928,6 @@ rw: label_wiki_start: "Start page" label_work_package: "Work package" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "New category" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index 9467aeb05d..ab0fb0dd2f 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -210,6 +210,15 @@ si: ignore_filenames: > ලැබෙන මේල් සඳහා ඇමුණුම් සැකසීමේදී නොසලකා හැරීමට නම් ලැයිස්තුවක් සඳහන් කරන්න (උදා: අත්සන් හෝ අයිකන). එක් පේළියකට එක් ගොනු නාමයක් ඇතුළත් කරන්න. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "ව්යාපෘතිය මකා දැමිය නොහැක: %{errors}" @@ -1919,7 +1928,6 @@ si: label_wiki_start: "ආරම්භක පිටුව" label_work_package: "වැඩ පැකේජය" label_work_package_attachments: "වැඩ පැකේජ ඇමුණුම්" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "නව ප්රවර්ගය" label_work_package_category_plural: "වැඩ පැකේජ කාණ්ඩ" label_work_package_hierarchy: "වැඩ පැකේජය ධූරාවලිය" diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index bf4bbe5887..92efd38d3d 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -210,6 +210,15 @@ sk: ignore_filenames: > Zadajte zoznam názvov, ktoré chcete ignorovať pri spracovávaní príloh prichádzajúcich e-mailov (napr. podpisy alebo ikony). Zadajte jeden názov súboru na riadok. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Projekt nie je možné odstrániť: %{errors}" @@ -1953,7 +1962,6 @@ sk: label_wiki_start: "Úvodná stránka" label_work_package: "Pracovný balíček" label_work_package_attachments: "Prílohy pracovného balíčka" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Nová kategória" label_work_package_category_plural: "kategórie pracovných balíčkov" label_work_package_hierarchy: "Hierarchia Pracovného balíčka" diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index 8d9ab76cca..edd5823041 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -209,6 +209,15 @@ sl: ignore_filenames: > Določite seznam imen, ki jih ne upoštevate pri obdelavi prilog za dohodne pošte (npr. podpise ali ikone). Vnesite eno ime datoteke v vrstico. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Izbris je predviden in se izvaja v ozadju. Obveščeni boste o rezultatih." schedule_failed: "Projekta ni mogoče izbrisati: %{errors}" @@ -1950,7 +1959,6 @@ sl: label_wiki_start: "Začetna stran" label_work_package: "Zahtevek" label_work_package_attachments: "Priloge delovnega paketa" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Nova kategorija" label_work_package_category_plural: "Kategorije delovnih paketov" label_work_package_hierarchy: "Hierarhija delovnega paketa" diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index 8cb5b27d59..a8f9b79f09 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -210,6 +210,15 @@ sv: ignore_filenames: > Specificera en lista med namn att ignorera när bilagor för inkommande e-post bearbetas (t. ex. signaturer och ikoner). Ange ett filnamn per rad. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Projektet kan inte tas bort: %{errors}" @@ -1918,7 +1927,6 @@ sv: label_wiki_start: "Startsida" label_work_package: "Arbetspaket" label_work_package_attachments: "Bilagor till arbetspaket" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Ny kategori" label_work_package_category_plural: "Arbetspaketkategorier" label_work_package_hierarchy: "Arbetspaketshierarki" diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index 0286a8a8a3..7af4af46dc 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -210,6 +210,15 @@ th: ignore_filenames: > Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1902,7 +1911,6 @@ th: label_wiki_start: "เพจเริ่มต้น" label_work_package: "ชุดภารกิจ" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "ประเภทใหม่" label_work_package_category_plural: "ประเภทของชุดภารกิจ" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index 2e1caa253e..829f210c3f 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -210,6 +210,15 @@ tr: ignore_filenames: > Gelen postalar için ekleri işlerken yok sayılacak adların bir listesini belirtin (örneğin, imzalar veya simgeler). Her satıra bir dosya adı girin. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Silme planlandı ve arka planda gerçekleştirilir. Sonuçtan haberdar edileceksiniz." schedule_failed: "Proje silinemiyor: %{errors}" @@ -1919,7 +1928,6 @@ tr: label_wiki_start: "Başlangıç sayfası" label_work_package: "İş paketi" label_work_package_attachments: "İş paketi ekleri" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Yeni kategori" label_work_package_category_plural: "İş paketi kategorileri" label_work_package_hierarchy: "İş paketi hiyerarşisi" diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index 020f35a2b0..43906173ff 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -205,6 +205,15 @@ uk: ignore_filenames: > Вкажіть список імен, які ігноруватимуться під час обробки вкладень для вхідних повідомлень (наприклад, підписів або значків). Введіть одне ім'я файлу у рядок. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Видалення заплановано та виконується у фоновому режимі. Ви отримаєте сповіщення про результат." schedule_failed: "Проект не можна видалити: %{errors}" @@ -1948,7 +1957,6 @@ uk: label_wiki_start: "Початкова сторінка" label_work_package: "Пакет робіт" label_work_package_attachments: "Додатки до робочого пакету" - label_work_package_file_link_plural: "Посилання на файл робочого пакета" label_work_package_category_new: "Нова категорія" label_work_package_category_plural: "Категорії робочих пакетів" label_work_package_hierarchy: "Ієрархія робочих пакетів" diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index 06e08b0564..7730bff68a 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -212,6 +212,15 @@ vi: ignore_filenames: > Chỉ định danh sách các tên cần bỏ qua khi xử lý tệp đính kèm cho thư đến (ví dụ: chữ ký hoặc biểu tượng). Nhập một tên tệp trên mỗi dòng. projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Dự án không thể bị xóa: %{errors}" @@ -1904,7 +1913,6 @@ vi: label_wiki_start: "Trang bắt đầu" label_work_package: "Work Package" label_work_package_attachments: "Phần đính kèm công việc" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "Danh mục mới" label_work_package_category_plural: "Các thể loại work package" label_work_package_hierarchy: "Phân cấp công việc" diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index fe3cc71285..9b62c7ac35 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -210,6 +210,15 @@ zh-TW: ignore_filenames: > 請列出從郵箱下載文檔時需要忽視的的名稱(比如:簽名或頭像)。請把檔案名稱分別列入每個各一行。 projects: + copy: + #Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "專案無法刪除: %{errors}" @@ -1902,7 +1911,6 @@ zh-TW: label_wiki_start: "開始頁面" label_work_package: "工作項目" label_work_package_attachments: "工作項目附件" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "新增類別" label_work_package_category_plural: "工作項目類別" label_work_package_hierarchy: "工作包層次結構" diff --git a/config/locales/en.yml b/config/locales/en.yml index a98ce60457..bb315092aa 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -238,6 +238,15 @@ en: Enter one filename per line. projects: + copy: + # Contains custom strings for options when copying a project that cannot be found elsewhere. + members: 'Project members' + overviews: 'Project overview' + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' delete: scheduled: "Deletion has been scheduled and is performed in the background. You will be notified of the result." schedule_failed: "Project cannot be deleted: %{errors}" @@ -1999,7 +2008,6 @@ en: label_wiki_start: "Start page" label_work_package: "Work package" label_work_package_attachments: "Work package attachments" - label_work_package_file_link_plural: "Work package file links" label_work_package_category_new: "New category" label_work_package_category_plural: "Work package categories" label_work_package_hierarchy: "Work package hierarchy" diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index e9c22773ce..4d0d476ddc 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -596,6 +596,7 @@ en: all: 'All' all_title: 'Show all' center: + label_actor_and: 'and' and_more_users: one: 'and 1 other' other: 'and %{count} others' @@ -629,9 +630,8 @@ en: mentioned: title: 'I am @mentioned' description: 'Receive a notification every time someone mentions me anywhere' - involved: - title: 'Assigned to me or accountable' - description: 'Receive notifications for all activities on work packages for which I am assignee or accountable' + assignee: 'Assignee' + responsible: 'Accountable' watched: 'Updates on watched items' work_package_commented: 'All new comments' work_package_created: 'New work packages' diff --git a/db/migrate/20220922200908_split_involved_notification_setting.rb b/db/migrate/20220922200908_split_involved_notification_setting.rb new file mode 100644 index 0000000000..dc4dce2953 --- /dev/null +++ b/db/migrate/20220922200908_split_involved_notification_setting.rb @@ -0,0 +1,20 @@ +class SplitInvolvedNotificationSetting < ActiveRecord::Migration[7.0] + def change + change_table :notification_settings, bulk: true do |t| + t.column :assignee, :boolean, default: false, null: false + t.column :responsible, :boolean, default: false, null: false + end + + reversible do |change| + change.up do + NotificationSetting.where(involved: true).update_all(assignee: true, responsible: true) + end + + change.down do + NotificationSetting.where(assignee: true).or(NotificationSetting.where(responsible: true)).update_all(involved: true) + end + end + + remove_column :notification_settings, :involved, :boolean + end +end diff --git a/db/migrate/20220930133418_set_duration_for_milestone_work_packages.rb b/db/migrate/20220930133418_set_duration_for_milestone_work_packages.rb new file mode 100644 index 0000000000..261fc144ad --- /dev/null +++ b/db/migrate/20220930133418_set_duration_for_milestone_work_packages.rb @@ -0,0 +1,24 @@ +class SetDurationForMilestoneWorkPackages < ActiveRecord::Migration[7.0] + def up + set_duration(:work_packages) + set_duration(:work_package_journals) + end + + private + + def set_duration(table) + execute <<~SQL.squish + UPDATE + #{table} + SET + duration = 1 + WHERE + EXISTS ( + SELECT * FROM types type + WHERE + type.is_milestone = TRUE AND + type.id = #{table}.type_id + ) + SQL + end +end diff --git a/docker/ci/entrypoint.sh b/docker/ci/entrypoint.sh index 0b9e1d5cea..a225fcc7b7 100755 --- a/docker/ci/entrypoint.sh +++ b/docker/ci/entrypoint.sh @@ -5,6 +5,7 @@ export PGBIN="/usr/lib/postgresql/$PGVERSION/bin" export JOBS="${CI_JOBS:=$(nproc)}" # for parallel rspec export PARALLEL_TEST_PROCESSORS=$JOBS +export PARALLEL_TEST_FIRST_IS_1=true # if from within docker if [ $(id -u) -eq 0 ]; then @@ -32,7 +33,7 @@ execute() { } cleanup() { - rm -rf tmp/cache/parallel* + rm -rf tmp/cache/parallel* } if [ "$1" == "setup-tests" ]; then @@ -51,7 +52,9 @@ if [ "$1" == "setup-tests" ]; then done execute "time bundle install -j$JOBS" - execute "TEST_ENV_NUMBER=0 time bundle exec rake db:create db:migrate db:schema:dump webdrivers:chromedriver:update webdrivers:geckodriver:update openproject:plugins:register_frontend" + # create test database "app" and dump schema + execute "time bundle exec rake db:create db:migrate db:schema:dump webdrivers:chromedriver:update webdrivers:geckodriver:update openproject:plugins:register_frontend" + # create parallel test databases "app#n" and load schema execute "time bundle exec rake parallel:create parallel:load_schema" fi diff --git a/docker/dev/frontend/Dockerfile b/docker/dev/frontend/Dockerfile index de647b41db..a5ff35a7bc 100644 --- a/docker/dev/frontend/Dockerfile +++ b/docker/dev/frontend/Dockerfile @@ -8,6 +8,8 @@ ENV USER=node RUN apt-get update && apt-get install -y chromium +ENV CHROME_BIN=/usr/bin/chromium + RUN npm i -g npm RUN groupadd $USER || true diff --git a/docs/api/apiv3/openapi-spec.yml b/docs/api/apiv3/openapi-spec.yml index e965f46d21..7af8ce9206 100644 --- a/docs/api/apiv3/openapi-spec.yml +++ b/docs/api/apiv3/openapi-spec.yml @@ -353,8 +353,11 @@ paths: "$ref": "./paths/status.yml" "/api/v3/storages/{id}": "$ref": "./paths/storage.yml" - "/api/v3/storages/{id}/files": - "$ref": "./paths/storage_files.yml" +# Temporarily removed for release 12.3 as the specification is not finished. +# Also the implementation is not yet ready. +# Discussion is tracked here: https://community.openproject.org/work_packages/43694 +# "/api/v3/storages/{id}/files": +# "$ref": "./paths/storage_files.yml" "/api/v3/time_entries": "$ref": "./paths/time_entries.yml" "/api/v3/time_entries/{id}/form": @@ -554,8 +557,11 @@ components: "$ref": "./components/schemas/example_schema_model.yml" Execute_custom_action: "$ref": "./components/schemas/execute_custom_action.yml" - FileCollectionModel: - $ref: './components/schemas/file_collection_model.yml' +# Temporarily removed for the 12.3 release as it is not yet +# implemented. +# Discussion is tracked here: https://community.openproject.org/work_packages/43694 +# FileCollectionModel: +# $ref: './components/schemas/file_collection_model.yml' FileLinkCollectionReadModel: $ref: './components/schemas/file_link_collection_read_model.yml' FileLinkCollectionWriteModel: diff --git a/docs/installation-and-operations/installation/docker/README.md b/docs/installation-and-operations/installation/docker/README.md index 558766d5c4..a7b7603779 100644 --- a/docs/installation-and-operations/installation/docker/README.md +++ b/docs/installation-and-operations/installation/docker/README.md @@ -45,10 +45,10 @@ docker-compose pull Launch the containers: ```bash -docker-compose up -d +OPENPROJECT_HTTPS=false docker-compose up -d ``` -After a while, OpenProject should be up and running on `http://localhost:8080`. The default username and password is login: `admin`, and password: `admin`. +After a while, OpenProject should be up and running on `http://localhost:8080`. The default username and password is login: `admin`, and password: `admin`. You need to explicitly disable HTTPS mode on startup as OpenProject assumes it's running behind HTTPS in production by default. Note that the `docker-compose.yml` file present in the repository can be adjusted to your convenience. With each pull it will be overwritten. Best practice is to use the file `docker-compose.override.yml` for that case. For instance you could mount specific configuration files, override environment variables, or switch off services you don't need. Please refer to the official [Docker Compose documentation](https://docs.docker.com/compose/extends/) for more details. diff --git a/docs/installation-and-operations/misc/README.md b/docs/installation-and-operations/misc/README.md index 1cc1b956a1..0663f328be 100644 --- a/docs/installation-and-operations/misc/README.md +++ b/docs/installation-and-operations/misc/README.md @@ -12,7 +12,7 @@ sidebar_navigation: | [Migrating your packaged OpenProject installation to another environment](./migration) | | [Migrating your packaged OpenProject database to PostgreSQL](./packaged-postgresql-migration) | | [Migrating your Docker OpenProject database to PostgreSQL](./docker-postgresql-migration) | -| [Migrating from an old MySQL database](./Migrating from an old MySQL database) | +| [Migrating from an old MySQL database](./upgrading-older-openproject-versions) | | [Fixing time entries corrupted by upgrading to 10.4.0](./time-entries-corrupted-by-10-4) | | [OpenProject Textile to Markdown migration](./textile-migration) | | [Custom OpenID Connect providers](./custom-openid-connect-providers) | diff --git a/docs/release-notes/12-2-5/README.md b/docs/release-notes/12-2-5/README.md new file mode 100644 index 0000000000..e62c707e1c --- /dev/null +++ b/docs/release-notes/12-2-5/README.md @@ -0,0 +1,32 @@ +--- +title: OpenProject 12.2.5 +sidebar_navigation: + title: 12.2.5 +release_version: 12.2.5 +release_date: 2022-10-04 +--- + +# OpenProject 12.2.5 + +Release date: 2022-10-04 + +We released [OpenProject 12.2.5](https://community.openproject.com/versions/1602). +The release contains several bug fixes and we recommend updating to the newest version. + + +#### Bug fixes and changes + +## LDAP group synchronization bug + +Users synchronized from LDAP into OpenProject groups were incorrectly removed when, in the same group, a new user was added in the same synchronization step. +This results in users still being present in the group despite being removed in LDAP. + +To aid in the discovery of these users, you can use the following rake task to print synchronized groups that have members originating not from LDAP: + +- Packaged installation: `sudo openproject run bundle exec rake ldap_groups:print_unsynced_members` +- Docker-based installation: `docker exec -it bash -c "bundle exec rake ldap_groups:print_unsynced_members"` + +Please note that these affected users are not automatically removed in this patch release, due to the system not knowing if are expected to be members. + +- Fixed: User from synchronized group not removed in OpenProject group after removed in LDAP \[[#41256](https://community.openproject.com/wp/41256)\] +- Fixed: When adding users to a group, existing users are shown in the autocompleter \[[#43892](https://community.openproject.com/wp/43892)\] diff --git a/docs/release-notes/12-3-0/README.md b/docs/release-notes/12-3-0/README.md new file mode 100644 index 0000000000..19cccce4a9 --- /dev/null +++ b/docs/release-notes/12-3-0/README.md @@ -0,0 +1,122 @@ +--- +title: OpenProject 12.3.0 +sidebar_navigation: +title: 12.3.0 +release_version: 12.3.0 +release_date: 2022-10-10 +--- + +# OpenProject 12.3.0 + +Release date: 2022-10-10 + +We have now released [OpenProject 12.3.0](https://community.openproject.com/versions/1514). + +This release **improves the scheduling of work packages significantly** and will consequently save you a lot of time and make your scheduling more accurate. + +With OpenProject 12.3, administrators can define the [global work week](../../system-admin-guide/working-days/#working-days). That means which days of the week are working days and which are non-working days. The default setting for the work week is Monday-Friday. But you can set it according to your needs and define work week and weekends as needed. + +OpenProject 12.3 also adds [duration](../../user-guide/work-packages/set-change-dates/#duration) to work packages. Thereby, the duration is bound to the start and the finish date. + +With the introduction of the work week and duration, consequently also the [date picker got improved](../../user-guide/work-packages/set-change-dates/#working-days). You will now see the duration as well as a switch to consider "Working days only" for your planning. + +Addtionally, this release launches **meaningful tool tips to the most essential actions**, and **when copying a project, all file links attached to work packages will be copied as well**. + +As always, this release also contains many more improvements and bug fixes. We recommend updating to the newest version as soon as possible. + +## Introduction of the global work week + +OpenProject 12.3 gives the administrator the possibility to specify working and non-working days on an overall instance-level and consequently define a global work week. + +This helps you to create more accurate project schedules and avoid having start or finish date of a work packages on a weekend. Non-working days are displayed grey in the calendar and work packages cannot be scheduled to start or finish on those days. The default value for non-working days is set to Saturday and Sunday, but you set them as needed. + +![work-week-administration](work-week-administration.png) + +You can find out [how to set working and non-working days](../../system-admin-guide/working-days/#working-days) and [how to schedule your work packages within and without working and non-working days](../../user-guide/work-packages/set-change-dates/#working-days) in our documentation. + +## Duration of work packages + +OpenProject 12.3 introduces the duration of work packages. Schedule your work packages faster by using the new duration field. + +The duration is always expressed in days and is the number of days between start and finish dates (inclusive). It is directly related to the start and finish dates, but does not require both of them. You can define a start date for a work package, enter the duration in working days and the finish date will automatically be set. Also, you can enter duration and finish date and the start date will be set. In all cases, the duration is consistent with the start and the finish date. Updating dates will update the duration, and updating duration will update your finish date. + +![changing duration in the duration field and seeing how the dates change in the calendar](duration.gif) + +Find out more on [how to make best use of the duration for your scheduling](../../user-guide/work-packages/set-change-dates/#duration) in our documentation. + +## Upgrade of the date picker + +With the addition of duration and introduction of the global work week, the date picker got updated to reflect these changes. You will now find a duration field and a working days only switch. + +The duration field is obviously there to indicate the duration of a work package and to assist with setting start or finish date. + +With the working days only switch you can decide to either stick to the set work week or to include weekends. + +By default the **Working days only** switch is activated and the work week, as defined in the administration settings, is used to define the duration. Consequently, non-working days are not included in the calculation of the duration. These non-working days show in grey in the calendar and are not clickable. + +By moving the switch and deactivate the “Working days only”, non-working days will be included in the calculation of the duration. You can now also select non-working days as start or finish dates of work packages. Hence, all days appear the same in the calendar and all are clickable. + +![setting start and finish date and switching from working days only to include weekends](working-days-only.gif) + +The functionality of the "Working days only" switch is well [documented](../../user-guide/work-packages/set-change-dates/#working-days). + +### Please note the impact on the scheduling of all work packages + +These changes to scheduling will not impact work packages created before the release of OpenProject 12.3 or before the upgrade to OpenProject 12.3. For the work packages created before OpenProject 12.3, the setting will have automatically turned-off the switch for “Working days only”. This is important to not change any existing dates for work packages. However, for new work packages, the switch will be set by default to “Working days only”. + +## Tool tips for most essential actions + +The new and additional tool tips in the OpenProject application will make the navigation easier for users that are not yet very familiar with OpenProject. The tool tips make clear what happens when the user clicks on a certain button. + +![hover over select a project and tool tip view all projects comes up](tool-tip.png) + + + +## List of all bug fixes and changes + +- Epic: Define weekly work schedule (weekends) [#18416](https://community.openproject.com/wp/18416) +- Epic: Duration (deriving duration from dates, deriving dates from duration, updated datepicker, duration field elsewhere) [#31992](https://community.openproject.com/wp/31992) +- Fixed: Quick-add menu not showing on smaller screens [#37539](https://community.openproject.com/wp/37539) +- Fixed: Attachments are not going to be copied, when using "Copy to other project" function [#43005](https://community.openproject.com/wp/43005) +- Fixed: Filters are not working after adding a custom field with default value [#43085](https://community.openproject.com/wp/43085) +- Fixed: BIM edition unavailable on Ubuntu 22.04 packaged installation [#43531](https://community.openproject.com/wp/43531) +- Fixed: Can't delete WPs from board view [#43761](https://community.openproject.com/wp/43761) +- Fixed: Insufficient contrast ratio between activity font color and background [#43874](https://community.openproject.com/wp/43874) +- Fixed: SystemStackError (stack level too deep) when trying to assign new parent or children to a work package [#43894](https://community.openproject.com/wp/43894) +- Fixed: Strange arrangement of files when creating a new work package [#44052](https://community.openproject.com/wp/44052) +- Fixed: CKEditor not wrapping the words at the end of the sentence (edit and view mode) [#44125](https://community.openproject.com/wp/44125) +- Fixed: File storage OAuth setting fields should not get translated [#44146](https://community.openproject.com/wp/44146) +- Fixed: Log out user when delete work package from board [#44161](https://community.openproject.com/wp/44161) +- Fixed: Work packages can have start_dates > due_dates [#44243](https://community.openproject.com/wp/44243) +- Fixed: Backup failed: pg_dump: password authentication failed for user "openproject" [#44251](https://community.openproject.com/wp/44251) + +- Fixed: "Group by" options in Cost report are broken [#44265](https://community.openproject.com/wp/44265) +- Fixed: Files list: inconsistencies in spacing and colours [#44266](https://community.openproject.com/wp/44266) +- Fixed: API call for custom_options does not work custom fieleds in time_entries [#44281](https://community.openproject.com/wp/44281) +- Fixed: Email Reminder: Daily reminders can only be configured to be delivered at a full hour. [#44300](https://community.openproject.com/wp/44300) +- Changed: Cleanup placeholders of editable attributes [#40133](https://community.openproject.com/wp/40133) +- Changed: Updated date picker drop modal (including duration and non-working days) [#41341](https://community.openproject.com/wp/41341) +- Changed: Copying a project shall also copy file links attached to all work packages [#41530](https://community.openproject.com/wp/41530) +- Changed: Administration page for changing the global work schedule - Weekends only [#42316](https://community.openproject.com/wp/42316) +- Changed: Add meaningful tooltips to the most essential actions [#43299](https://community.openproject.com/wp/43299) +- Changed: Hide time stamp and avatar when there are hover actions [#43308](https://community.openproject.com/wp/43308) +- Changed: Use a disabled mouse style and tooltip for inactive files [#43399](https://community.openproject.com/wp/43399) +- Changed: Update work package table view for duration [#43636](https://community.openproject.com/wp/43636) +- Changed: Update gantt chart for duration and non-working days [#43637](https://community.openproject.com/wp/43637) +- Changed: Update team planner and calendar for duration and non-working days [#43638](https://community.openproject.com/wp/43638) +- Changed: Delete/Unlink modal [#43663](https://community.openproject.com/wp/43663) +- Changed: Add information toast to the Nextcloud Setup Documentation [#43851](https://community.openproject.com/wp/43851) +- Changed: Disregard distance (not lag) between related work packages when scheduling FS-related work packages [#44053](https://community.openproject.com/wp/44053) +- Changed: Add packaged installation support for SLES 15 [#44117](https://community.openproject.com/wp/44117) +- Changed: Replace toggles for scheduling mode and working days with on/off-switches [#44147](https://community.openproject.com/wp/44147) +- Changed: New release teaser block for 12.3 [#44212](https://community.openproject.com/wp/44212) +- Changed: Add the Switch component and Switch Field pattern to the design system [#44213](https://community.openproject.com/wp/44213) + +### Contributions + +A big thanks to community members for reporting bugs, helping us identify issues and providing fixes. + +- A very special thanks to the City of Cologne for sponsoring the feature development of work week and work package duration. +- Special thanks for reporting and finding bugs go to Stuart Malt, Herbert Cruz, Matthias Weber, Alexander Seitz, Daniel Hug, Christian Noack, Christina Vechkanova, Noel Lublovary, Hans-Gerd Sandhagen, Sky Racer. +- A big thank you to every other dedicated user who has [reported bugs](../../development/report-a-bug) and supported the community by asking and answering questions in the [forum](https://community.openproject.org/projects/openproject/boards). +- A big thank you to all the dedicated users who provided translations on [CrowdIn](https://crowdin.com/projects/opf). diff --git a/docs/release-notes/12-3-0/duration.gif b/docs/release-notes/12-3-0/duration.gif new file mode 100644 index 0000000000..00604ded30 Binary files /dev/null and b/docs/release-notes/12-3-0/duration.gif differ diff --git a/docs/release-notes/12-3-0/tool tip.png b/docs/release-notes/12-3-0/tool tip.png new file mode 100644 index 0000000000..420c8f02b8 Binary files /dev/null and b/docs/release-notes/12-3-0/tool tip.png differ diff --git a/docs/release-notes/12-3-0/tool-tip.png b/docs/release-notes/12-3-0/tool-tip.png new file mode 100644 index 0000000000..420c8f02b8 Binary files /dev/null and b/docs/release-notes/12-3-0/tool-tip.png differ diff --git a/docs/release-notes/12-3-0/work-week-admin-5063871.png b/docs/release-notes/12-3-0/work-week-admin-5063871.png new file mode 100644 index 0000000000..7925f92187 Binary files /dev/null and b/docs/release-notes/12-3-0/work-week-admin-5063871.png differ diff --git a/docs/release-notes/12-3-0/work-week-admin.png b/docs/release-notes/12-3-0/work-week-admin.png new file mode 100644 index 0000000000..7925f92187 Binary files /dev/null and b/docs/release-notes/12-3-0/work-week-admin.png differ diff --git a/docs/release-notes/12-3-0/work-week-administration.png b/docs/release-notes/12-3-0/work-week-administration.png new file mode 100644 index 0000000000..da0ec1bd16 Binary files /dev/null and b/docs/release-notes/12-3-0/work-week-administration.png differ diff --git a/docs/release-notes/12-3-0/working-days-only.gif b/docs/release-notes/12-3-0/working-days-only.gif new file mode 100644 index 0000000000..dfd12e3461 Binary files /dev/null and b/docs/release-notes/12-3-0/working-days-only.gif differ diff --git a/docs/release-notes/README.md b/docs/release-notes/README.md index 33360d4b73..819039ad01 100644 --- a/docs/release-notes/README.md +++ b/docs/release-notes/README.md @@ -14,6 +14,19 @@ Stay up to date and get an overview of the new features included in the releases +## 12.3.0 + +Release date: 2022-10-10 + +[Release Notes](12-3-0/) + +## 12.2.5 + +Release date: 2022-10-04 + +[Release Notes](12-2-5/) + + ## 12.2.4 Release date: 2022-09-15 diff --git a/docs/user-guide/time-and-costs/time-tracking/README.md b/docs/user-guide/time-and-costs/time-tracking/README.md index ca48e5fe9e..9080ef9f24 100644 --- a/docs/user-guide/time-and-costs/time-tracking/README.md +++ b/docs/user-guide/time-and-costs/time-tracking/README.md @@ -13,15 +13,15 @@ Users can book their time or units spent on an activity within a project directl > **Note**: To use the time tracking functionality, the **Time and costs module** needs to be activated in the [project settings](../../projects/project-settings/modules/). -| Topic | Content | -|---------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| -| [Logging time](#logging-time-in-the-work-package-view) | How to log time to work packages. | -| [Logging time via commit message](#logging-time-via-commit-message) | How to log time via a commit message to a work package. | -| [Edit logged time](#edit-logged-time) | How to edit logged time on a work package. | -| [Delete time entries](#delete-time-entries) | How to delete time entries logged to a work package. | -| [Logging and editing time for other users](#logging-and-editing-time-for-other-users) | How to log time to work packages. | -| [Spent time widget on the My Page](#spent-time-widget-on-the-my-page) | How to easily track and display spent time in the spent time widget on the MyPage. | -| [Track time with Toggl](./toggl-integration) | How to track spent time with Toggl integration. | +| Topic | Content | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [Logging time](#logging-time-in-the-work-package-view) | How to log time to a work package. | +| [Logging time via commit message](#logging-time-via-commit-message) | How to log time via a commit message to a work package. | +| [Edit logged time](#edit-logged-time) | How to edit logged time on a work package. | +| [Delete time entries](#delete-time-entries) | How to delete time entries logged to a work package. | +| [Logging and editing time for other users](#logging-and-editing-time-for-other-users) | How to log time to work packages. | +| [Spent time widget on the My Page](#spent-time-widget-on-the-my-page) | How to easily track and display spent time in the spent time widget on the MyPage. | +| [Track time with Toggl](./toggl-integration) | How to track spent time with Toggl integration. | ## Logging time in the work package view diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 05652d9a57..4930db40b5 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -96,6 +96,10 @@ module.exports = { // Until we do that, `void` is a good way to explicitly mark unhandled promises. "no-void": ["error", { allowAsStatement: true }], + // Disable no-use for functions and classes + "no-use-before-define": ["error", { "functions": false, "classes": false }], + "@typescript-eslint/no-use-before-define": ["error", { "functions": false, "classes": false }], + /* // Disable use before define, as irrelevant for TS interfaces "no-use-before-define": "off", diff --git a/frontend/.storybook/main.js b/frontend/.storybook/main.js index d351ae716e..11fcea1a45 100644 --- a/frontend/.storybook/main.js +++ b/frontend/.storybook/main.js @@ -19,7 +19,7 @@ module.exports = { }, features: { previewMdx2: true, - modernInlineRender: true, + // modernInlineRender: true, }, staticDirs: [ { from: '../src/stories/assets', to: '/assets' }, diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js index 94dd8e2fff..32e4f6932d 100644 --- a/frontend/.storybook/preview.js +++ b/frontend/.storybook/preview.js @@ -1,15 +1,13 @@ import { setCompodocJson } from "@storybook/addon-docs/angular"; -import { addParameters } from '@storybook/client-api'; import { themes } from '@storybook/theming'; import docJson from "../documentation.json"; setCompodocJson(docJson); -addParameters({ - viewMode: 'docs', -}); - export const parameters = { + parameters: { + viewMode: 'docs', + }, actions: { argTypesRegex: "^on[A-Z].*" }, controls: { matchers: { diff --git a/frontend/src/app/features/boards/board/add-list-modal/add-list-modal.html b/frontend/src/app/features/boards/board/add-list-modal/add-list-modal.html index a77a47c706..3b1cff9062 100644 --- a/frontend/src/app/features/boards/board/add-list-modal/add-list-modal.html +++ b/frontend/src/app/features/boards/board/add-list-modal/add-list-modal.html @@ -48,7 +48,7 @@
diff --git a/frontend/src/app/features/boards/new-board-modal/new-board-modal.html b/frontend/src/app/features/boards/new-board-modal/new-board-modal.html index 52e423ab91..8091f21aae 100644 --- a/frontend/src/app/features/boards/new-board-modal/new-board-modal.html +++ b/frontend/src/app/features/boards/new-board-modal/new-board-modal.html @@ -19,6 +19,7 @@

-
\ No newline at end of file +
diff --git a/frontend/src/app/features/boards/tile-view/tile-view.component.sass b/frontend/src/app/features/boards/tile-view/tile-view.component.sass index 023227496f..568c7e3364 100644 --- a/frontend/src/app/features/boards/tile-view/tile-view.component.sass +++ b/frontend/src/app/features/boards/tile-view/tile-view.component.sass @@ -3,8 +3,7 @@ display: grid grid-template-rows: repeat(minmax(200px, auto)) grid-template-columns: auto auto - grid-column-gap: 10px - grid-row-gap: 10px + grid-gap: 1rem &--tile border-radius: 10px @@ -16,7 +15,6 @@ justify-items: left background: #f7fafc min-height: 150px - margin: auto &:disabled background: #fafafa diff --git a/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts b/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts index 89338dca44..3c05be5e3c 100644 --- a/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts +++ b/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts @@ -99,6 +99,10 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement private alreadyLoaded = false; + text = { + cannot_drag_to_non_working_day: this.I18n.t('js.team_planner.cannot_drag_to_non_working_day'), + }; + constructor( readonly states:States, readonly $state:StateService, @@ -110,6 +114,7 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement readonly i18n:I18nService, readonly toastService:ToastService, private sanitizer:DomSanitizer, + private I18n:I18nService, private configuration:ConfigurationService, readonly calendar:OpCalendarService, readonly workPackagesCalendar:OpWorkPackagesCalendarService, @@ -202,8 +207,27 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement const workPackage = event.extendedProps.workPackage as WorkPackageResource; el.dataset.workPackageId = workPackage.id as string; }, - eventResize: (resizeInfo:EventResizeDoneArg) => this.updateEvent(resizeInfo), - eventDrop: (dropInfo:EventDropArg) => this.updateEvent(dropInfo), + eventResize: (resizeInfo:EventResizeDoneArg) => { + const due = moment(resizeInfo.event.endStr).subtract(1, 'day').toDate(); + const start = moment(resizeInfo.event.startStr).toDate(); + const wp = resizeInfo.event.extendedProps.workPackage as WorkPackageResource; + if (!wp.ignoreNonWorkingDays && (this.weekdayService.isNonWorkingDay(start) || this.weekdayService.isNonWorkingDay(due))) { + this.toastService.addError(this.text.cannot_drag_to_non_working_day); + resizeInfo?.revert(); + return; + } + void this.updateEvent(resizeInfo); + }, + eventDrop: (dropInfo:EventDropArg) => { + const start = moment(dropInfo.event.startStr).toDate(); + const wp = dropInfo.event.extendedProps.workPackage as WorkPackageResource; + if (!wp.ignoreNonWorkingDays && this.weekdayService.isNonWorkingDay(start)) { + this.toastService.addError(this.text.cannot_drag_to_non_working_day); + dropInfo?.revert(); + return; + } + void this.updateEvent(dropInfo); + }, eventResizeStart: (resizeInfo:EventResizeDoneArg) => { const wp = resizeInfo.event.extendedProps.workPackage as WorkPackageResource; if (!wp.ignoreNonWorkingDays) { @@ -314,10 +338,8 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement } private handleDateClicked(info:DateSelectArg) { - const startDay = new Date(info.start).getDate(); - const endDay = new Date(info.end).getDate(); - const duration = endDay - startDay; - const nonWorkingDays = duration !== 1 ? false : this.weekdayService.isNonWorkingDay(info.start); + const due = moment(info.endStr).subtract(1, 'day').toDate(); + const nonWorkingDays = this.weekdayService.isNonWorkingDay(info.start) || this.weekdayService.isNonWorkingDay(due); const defaults = { startDate: info.startStr, diff --git a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html index d45f065c03..bbba1842ff 100644 --- a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html +++ b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html @@ -93,17 +93,23 @@ [textContent]="relativeTime$ | async" >
- + + + {{ text.and }} + + + + - {{ text_for_additional_authors(actors.length - 1) }} + {{ text_for_additional_authors(actors.length - 3) }}
diff --git a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts index 981f523751..9ad81dcd91 100644 --- a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts @@ -63,6 +63,7 @@ export class InAppNotificationEntryComponent implements OnInit { project?:{ href:string, title:string, showUrl:string }; text = { + and: this.I18n.t('js.notifications.center.label_actor_and'), and_other_singular: this.I18n.t('js.notifications.center.and_more_users.one'), and_other_plural: (count:number):string => this.I18n.t('js.notifications.center.and_more_users.other', { count }), diff --git a/frontend/src/app/features/projects/components/copy-project/copy-project.component.ts b/frontend/src/app/features/projects/components/copy-project/copy-project.component.ts index dc36d1ecd4..f41667b3a1 100644 --- a/frontend/src/app/features/projects/components/copy-project/copy-project.component.ts +++ b/frontend/src/app/features/projects/components/copy-project/copy-project.component.ts @@ -68,7 +68,8 @@ export class CopyProjectComponent extends UntilDestroyedMixin implements OnInit } private fieldSettingsPipe(dynamicFieldsSettings:IOPFormlyFieldSettings[]):IOPFormlyFieldSettings[] { - return dynamicFieldsSettings.map((field) => ({ ...field, hide: this.isHiddenField(field.key) })); + return this.sortedCopyFields(dynamicFieldsSettings) + .map((field) => ({ ...field, hide: this.isHiddenField(field.key) })); } private isPrimaryAttribute(to?:IOPFormlyTemplateOptions):boolean { @@ -86,4 +87,30 @@ export class CopyProjectComponent extends UntilDestroyedMixin implements OnInit private isMeta(property:string|undefined):boolean { return !!property && (property.startsWith('copy') || property === 'sendNotifications'); } + + // Sorts the copy options by their label. + // The order of the rest of the fields is left unchanged but all copy options are returned first. + private sortedCopyFields(dynamicFieldsSettings:IOPFormlyFieldSettings[]):IOPFormlyFieldSettings[] { + const sortedCopyFields = dynamicFieldsSettings + .filter((field) => field.key && field.key.startsWith('_meta.copy')) + .sort((fieldA, fieldB) => { + if (!fieldA.templateOptions + || !fieldA.templateOptions.label + || !fieldB.templateOptions + || !fieldB.templateOptions.label) { + return 0; + } + + return fieldA.templateOptions.label.localeCompare(fieldB.templateOptions.label); + }); + + const nonCopyFields = dynamicFieldsSettings + .filter((field) => !field.key || !field.key.startsWith('_meta.copy')); + + // Now all copy fields are before the non Copy fields. + // That way, the "Sent notifications" is after the copy fields. + // For the rest, it does not make a difference since the _meta + // fields are rendered in a separate group. + return sortedCopyFields.concat(nonCopyFields); + } } diff --git a/frontend/src/app/features/reporting/reporting-page/functionality/reporting_engine/group_bys.js b/frontend/src/app/features/reporting/reporting-page/functionality/reporting_engine/group_bys.js index 35c9efb718..313597337c 100644 --- a/frontend/src/app/features/reporting/reporting-page/functionality/reporting_engine/group_bys.js +++ b/frontend/src/app/features/reporting/reporting-page/functionality/reporting_engine/group_bys.js @@ -27,7 +27,7 @@ //++ /*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */ -/*global window, $, $$, Reporting, Effect, Ajax, selectAllOptions, moveOptions, moveOptionUp, moveOptionDown */ +/*global _, dragula, I18n, jQuery, Reporting*/ Reporting.GroupBys = (function($){ var group_by_container_ids = function() { @@ -59,6 +59,7 @@ Reporting.GroupBys = (function($){ .attr('class', 'in_row group-by--label') .attr('for', group_by.attr('id')) .attr('id', group_by.attr('id') + '_label') + .attr('title', text) .html(text); }; @@ -106,7 +107,7 @@ Reporting.GroupBys = (function($){ }; var create_group_by = function(field, caption) { - var group_by, label, right_arrow, left_arrow, remove_button; + var group_by, label, remove_button; group_by = $(''); group_by.attr('class', 'group-by--selected-element'); group_by.attr('data-group-by', field); @@ -146,8 +147,7 @@ Reporting.GroupBys = (function($){ }; var add_group_by = function(field, caption, container) { - var group_by, add_groups_select_box, added_container; - add_groups_select_box = container.find('select').first(); + var group_by, added_container; group_by = Reporting.GroupBys.create_group_by(field, caption); added_container = container.find('.group-by--selected-elements'); added_container.append(group_by); diff --git a/frontend/src/app/features/reporting/reporting-page/styles/_reporting_group_by.sass b/frontend/src/app/features/reporting/reporting-page/styles/_reporting_group_by.sass index 2215ad7231..613afeea89 100644 --- a/frontend/src/app/features/reporting/reporting-page/styles/_reporting_group_by.sass +++ b/frontend/src/app/features/reporting/reporting-page/styles/_reporting_group_by.sass @@ -21,6 +21,8 @@ padding-left: 14px margin-left: 18px min-width: 145px + display: flex + align-items: center fieldset.collapsible.header_collapsible legend.in_row width: inherit @@ -31,8 +33,8 @@ fieldset.collapsible.header_collapsible legend.in_row .group-by--label margin: 0px - padding: 0px 18px 0 0 min-width: 60px + max-width: 110px text-align: center white-space: nowrap font-weight: bold @@ -40,6 +42,8 @@ fieldset.collapsible.header_collapsible legend.in_row height: 36px line-height: 36px cursor: move + overflow: hidden + text-overflow: ellipsis .group-by--selected-element:after, .group-by--selected-element:before border: solid transparent @@ -77,6 +81,7 @@ fieldset.collapsible.header_collapsible legend.in_row line-height: normal cursor: pointer color: #FFFFFF + padding: 0 0 0 5px .group-by--remove:hover background-color: #3493B3 !important @@ -86,6 +91,7 @@ fieldset.collapsible.header_collapsible legend.in_row .group-by--control margin: 0 padding: 0 + max-width: 200px .group-by--selected-elements background-color: #EEE diff --git a/frontend/src/app/features/team-planner/team-planner/calendar-drag-drop.service.ts b/frontend/src/app/features/team-planner/team-planner/calendar-drag-drop.service.ts index 3af7af173c..c526ee74f3 100644 --- a/frontend/src/app/features/team-planner/team-planner/calendar-drag-drop.service.ts +++ b/frontend/src/app/features/team-planner/team-planner/calendar-drag-drop.service.ts @@ -10,7 +10,6 @@ import { BehaviorSubject } from 'rxjs'; import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { OpCalendarService } from 'core-app/features/calendar/op-calendar.service'; import { OpWorkPackagesCalendarService } from 'core-app/features/calendar/op-work-packages-calendar.service'; @Injectable() @@ -119,7 +118,8 @@ export class CalendarDragDropService { const startDate = moment(workPackage.startDate); const dueDate = moment(workPackage.dueDate); - const diff = dueDate.diff(startDate, 'days') + 1; + const duration = Number(moment.duration(workPackage.duration).asDays().toFixed(0)); + const diff = duration > 0 ? duration : dueDate.diff(startDate, 'days') + 1; return { id: `${workPackage.href as string}-external`, diff --git a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts index 8d9ceeac07..2de413cc0f 100644 --- a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts +++ b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts @@ -293,6 +293,7 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit, cannot_drag_here: this.I18n.t('js.team_planner.cannot_drag_here'), updating: this.I18n.t('js.ajax.updating'), successful_update: this.I18n.t('js.notice_successful_update'), + cannot_drag_to_non_working_day: this.I18n.t('js.team_planner.cannot_drag_to_non_working_day'), }; principals$ = this.principalIds$ @@ -496,7 +497,17 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit, // DnD configuration editable: true, droppable: true, - eventResize: (resizeInfo:EventResizeDoneArg) => this.updateEvent(resizeInfo), + eventResize: (resizeInfo:EventResizeDoneArg) => { + const due = moment(resizeInfo.event.endStr).subtract(1, 'day').toDate(); + const start = moment(resizeInfo.event.startStr).toDate(); + const wp = resizeInfo.event.extendedProps.workPackage as WorkPackageResource; + if (!wp.ignoreNonWorkingDays && (this.weekdayService.isNonWorkingDay(start) || this.weekdayService.isNonWorkingDay(due))) { + this.toastService.addError(this.text.cannot_drag_to_non_working_day); + resizeInfo?.revert(); + return; + } + void this.updateEvent(resizeInfo); + }, eventResizeStart: (resizeInfo:EventResizeDoneArg) => { const wp = resizeInfo.event.extendedProps.workPackage as WorkPackageResource; if (!wp.ignoreNonWorkingDays) { @@ -520,10 +531,26 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit, this.draggingItem$.next(undefined); this.removeBackGroundEvents(); }, - eventDrop: (dropInfo:EventDropArg) => this.updateEvent(dropInfo), + eventDrop: (dropInfo:EventDropArg) => { + const start = moment(dropInfo.event.startStr).toDate(); + const wp = dropInfo.event.extendedProps.workPackage as WorkPackageResource; + if (!wp.ignoreNonWorkingDays && this.weekdayService.isNonWorkingDay(start)) { + this.toastService.addError(this.text.cannot_drag_to_non_working_day); + dropInfo?.revert(); + return; + } + void this.updateEvent(dropInfo); + }, eventReceive: async (dropInfo:EventReceiveArg) => { - await this.updateEvent(dropInfo); + const due = moment(dropInfo.event.endStr).subtract(1, 'day').toDate(); + const start = moment(dropInfo.event.startStr).toDate(); const wp = dropInfo.event.extendedProps.workPackage as WorkPackageResource; + if (!wp.ignoreNonWorkingDays && (this.weekdayService.isNonWorkingDay(start) || this.weekdayService.isNonWorkingDay(due))) { + this.toastService.addError(this.text.cannot_drag_to_non_working_day); + dropInfo?.revert(); + return; + } + await this.updateEvent(dropInfo); this.actions$.dispatch(teamPlannerEventAdded({ workPackage: wp.id as string })); }, // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -752,17 +779,15 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit, } private handleDateClicked(info:DateSelectArg) { - const startDay = new Date(info.start).getDate(); - const endDay = new Date(info.end).getDate(); - const duration = endDay - startDay; - const ignoreNonWorkingDays = duration !== 1 ? false : this.weekdayService.isNonWorkingDay(info.start); + const due = moment(info.endStr).subtract(1, 'day').toDate(); + const nonWorkingDays = this.weekdayService.isNonWorkingDay(info.start) || this.weekdayService.isNonWorkingDay(due); this.openNewSplitCreate( info.startStr, // end date is exclusive this.workPackagesCalendar.getEndDateFromTimestamp(info.endStr), info.resource?.id || '', - ignoreNonWorkingDays, + nonWorkingDays, ); } @@ -814,7 +839,6 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit, private async updateEvent(info:EventResizeDoneArg|EventDropArg|EventReceiveArg):Promise { const changeset = this.workPackagesCalendar.updateDates(info); - const resource = info.event.getResources()[0]; if (resource) { changeset.setValue('assignee', { href: resource.id }); diff --git a/frontend/src/app/features/user-preferences/notifications-settings/page/notifications-settings-page.component.html b/frontend/src/app/features/user-preferences/notifications-settings/page/notifications-settings-page.component.html index cfd46505b6..1b451ead44 100644 --- a/frontend/src/app/features/user-preferences/notifications-settings/page/notifications-settings-page.component.html +++ b/frontend/src/app/features/user-preferences/notifications-settings/page/notifications-settings-page.component.html @@ -18,19 +18,29 @@ />

{{ text.mentioned.description }}

+ + + + -

{{ text.involved.description }}

{{ text.alsoNotifyFor.title }}
@@ -40,12 +50,12 @@ [label]="text.watched" [control]="form.get('watched')" > - + projectSettings.push(new FormGroup({ project: new FormControl(setting._links.project), - involved: new FormControl(setting.involved), + assignee: new FormControl(setting.assignee), + responsible: new FormControl(setting.responsible), workPackageCreated: new FormControl(setting.workPackageCreated), workPackageProcessed: new FormControl(setting.workPackageProcessed), workPackageScheduled: new FormControl(setting.workPackageScheduled), @@ -161,7 +163,8 @@ export class NotificationsSettingsPageComponent extends UntilDestroyedMixin impl _links: { project: { href: null } }, watched: true, mentioned: true, - involved: notificationSettings.involved, + assignee: notificationSettings.assignee, + responsible: notificationSettings.responsible, workPackageCreated: notificationSettings.workPackageCreated, workPackageProcessed: notificationSettings.workPackageProcessed, workPackageScheduled: notificationSettings.workPackageScheduled, @@ -173,7 +176,8 @@ export class NotificationsSettingsPageComponent extends UntilDestroyedMixin impl _links: { project: { href: settings.project.href } }, watched: true, mentioned: true, - involved: settings.involved, + assignee: settings.assignee, + responsible: settings.responsible, workPackageCreated: settings.workPackageCreated, workPackageProcessed: settings.workPackageProcessed, workPackageScheduled: settings.workPackageScheduled, diff --git a/frontend/src/app/features/user-preferences/notifications-settings/table/notification-settings-table.component.html b/frontend/src/app/features/user-preferences/notifications-settings/table/notification-settings-table.component.html index 3aa7ed212d..9bbd16fb3d 100644 --- a/frontend/src/app/features/user-preferences/notifications-settings/table/notification-settings-table.component.html +++ b/frontend/src/app/features/user-preferences/notifications-settings/table/notification-settings-table.component.html @@ -30,15 +30,29 @@ -
{{ text.involved_header.title }}
-

{{ text.involved_header.description }}

+
{{ text.assignee }}
+ + + + + +
{{ text.responsible }}
+ + + + diff --git a/frontend/src/app/features/user-preferences/notifications-settings/table/notification-settings-table.component.ts b/frontend/src/app/features/user-preferences/notifications-settings/table/notification-settings-table.component.ts index 38cd81fc1c..28dfd1faf0 100644 --- a/frontend/src/app/features/user-preferences/notifications-settings/table/notification-settings-table.component.ts +++ b/frontend/src/app/features/user-preferences/notifications-settings/table/notification-settings-table.component.ts @@ -9,7 +9,6 @@ import { FormArray, FormGroup, FormControl } from '@angular/forms'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import idFromLink from 'core-app/features/hal/helpers/id-from-link'; -import { UserPreferencesService } from 'core-app/features/user-preferences/state/user-preferences.service'; import { HalSourceLink } from 'core-app/features/hal/resources/hal-resource'; @Component({ @@ -30,10 +29,8 @@ export class NotificationSettingsTableComponent { title: this.I18n.t('js.notifications.settings.reasons.mentioned.title'), description: this.I18n.t('js.notifications.settings.reasons.mentioned.description'), }, - involved_header: { - title: this.I18n.t('js.notifications.settings.reasons.involved.title'), - description: this.I18n.t('js.notifications.settings.reasons.involved.description'), - }, + assignee: this.I18n.t('js.notifications.settings.reasons.assignee'), + responsible: this.I18n.t('js.notifications.settings.reasons.responsible'), watched_header: this.I18n.t('js.notifications.settings.reasons.watched'), work_package_commented_header: this.I18n.t('js.notifications.settings.reasons.work_package_commented'), work_package_created_header: this.I18n.t('js.notifications.settings.reasons.work_package_created'), @@ -55,7 +52,8 @@ export class NotificationSettingsTableComponent { addProjectSettings(project:HalSourceLink):void { this.settings.push(new FormGroup({ project: new FormControl(project), - involved: new FormControl(false), + assignee: new FormControl(false), + responsible: new FormControl(false), workPackageCreated: new FormControl(false), workPackageProcessed: new FormControl(false), workPackageScheduled: new FormControl(false), diff --git a/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts index 75aff4808b..1018c1423a 100644 --- a/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts +++ b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts @@ -22,6 +22,7 @@ import { FormGroupDirective, } from '@angular/forms'; import { ConfigurationService } from 'core-app/core/config/configuration.service'; +import * as moment from 'moment'; @Component({ selector: 'op-reminder-settings-daily-time', @@ -242,9 +243,10 @@ export class ReminderSettingsDailyTimeComponent implements OnInit { } private static dateForHour(hour:number) { - const date = new Date(); - date.setTime(1000 * 60 * 60 * (hour - 1)); + const currentTime = new Date(); + currentTime.setTime(1000 * 60 * 60 * (hour - 1)); + const convertTimeObject = new Date(moment(currentTime).utc().hours(hour).format('YYYY-MM-DDTHH:mm:ss')); - return date; + return convertTimeObject; } } diff --git a/frontend/src/app/features/user-preferences/state/notification-setting.model.ts b/frontend/src/app/features/user-preferences/state/notification-setting.model.ts index d150484250..05f521205a 100644 --- a/frontend/src/app/features/user-preferences/state/notification-setting.model.ts +++ b/frontend/src/app/features/user-preferences/state/notification-setting.model.ts @@ -3,7 +3,8 @@ import { HalSourceLink } from 'core-app/features/hal/resources/hal-resource'; export interface INotificationSetting { _links:{ project:HalSourceLink }; watched:boolean; - involved:boolean; + assignee:boolean; + responsible:boolean; mentioned:boolean; workPackageCommented:boolean; workPackageCreated:boolean; @@ -28,7 +29,8 @@ export function buildNotificationSetting(project:null|HalSourceLink, params:Part title: project?.title, }, }, - involved: true, + assignee: true, + responsible: true, mentioned: true, watched: true, workPackageCommented: true, diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration.modal.html b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration.modal.html index b5c45797cf..e10b013337 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration.modal.html +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration.modal.html @@ -25,7 +25,7 @@
diff --git a/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.html b/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.html index 315ed042a0..179d199d24 100644 --- a/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.html +++ b/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.html @@ -4,6 +4,10 @@ [attr.id]="htmlId" #modalContainer data-indicator-name="modal" + tabindex="0" + cdkFocusInitial + cdkTrapFocus + [cdkTrapFocusAutoCapture]="true" (submit)="save($event)" > @@ -90,6 +94,8 @@ [ngClass]="{'op-datepicker-modal--date-field_current' : showFieldAsActive('duration')}" [ngModel]="durationFocused ? duration : displayedDuration" [showClearButton]="durationFocused" + pattern="\d*" + inputmode="numeric" (ngModelChange)="durationChanges$.next($event)" [disabled]="!isSchedulable" (focusin)="handleDurationFocusIn()" @@ -105,8 +111,9 @@
diff --git a/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.ts b/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.ts index 38a167638c..bd3884a10b 100644 --- a/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.ts +++ b/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.ts @@ -46,7 +46,6 @@ import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.servi import { DatePicker } from 'core-app/shared/components/op-date-picker/datepicker'; import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service'; import { ResourceChangeset } from 'core-app/shared/components/fields/changeset/resource-changeset'; -import { BrowserDetector } from 'core-app/core/browser/browser-detector.service'; import { ConfigurationService } from 'core-app/core/config/configuration.service'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; import { DayElement } from 'flatpickr/dist/types/instance'; @@ -77,8 +76,9 @@ import { validDate, } from 'core-app/shared/components/datepicker/helpers/date-modal.helpers'; import { WeekdayService } from 'core-app/core/days/weekday.service'; -import DateOption = flatpickr.Options.DateOption; import { FocusHelperService } from 'core-app/shared/directives/focus/focus-helper'; +import { DeviceService } from 'core-app/core/browser/device.service'; +import DateOption = flatpickr.Options.DateOption; export type DateKeys = 'start'|'end'; export type DateFields = DateKeys|'duration'; @@ -118,7 +118,7 @@ export class MultiDateModalComponent extends OpModalComponent implements AfterVi @InjectField() dateModalRelations:DateModalRelationsService; - @InjectField() browserDetector:BrowserDetector; + @InjectField() deviceService:DeviceService; @InjectField() weekdayService:WeekdayService; @@ -168,6 +168,10 @@ export class MultiDateModalComponent extends OpModalComponent implements AfterVi // Manual changes to the datepicker, with information which field was active datepickerChanged$ = new Subject(); + // We want to position the modal as soon as the datepicker gets initialized + // But if we destroy and recreate the datepicker (e.g., when toggling switches), keep current position + modalPositioned = false; + // Date updates from the datepicker or a manual change dateUpdates$ = merge( this.startDateDebounced$, @@ -369,6 +373,10 @@ export class MultiDateModalComponent extends OpModalComponent implements AfterVi // eslint-disable-next-line class-methods-use-this reposition(element:JQuery, target:JQuery):void { + if (this.deviceService.isMobile) { + return; + } + element.position({ my: 'left top', at: 'left bottom', @@ -457,10 +465,16 @@ export class MultiDateModalComponent extends OpModalComponent implements AfterVi [this.dates.start || '', this.dates.end || ''], { mode: 'range', - showMonths: this.browserDetector.isMobile ? 1 : 2, + showMonths: this.deviceService.isMobile ? 1 : 2, inline: true, onReady: (_date, _datestr, instance) => { - this.reposition(jQuery(this.modalContainer.nativeElement), jQuery(`.${activeFieldContainerClassName}`)); + instance.calendarContainer.classList.add('op-datepicker-modal--flatpickr-instance'); + + if (!this.modalPositioned) { + this.reposition(jQuery(this.modalContainer.nativeElement), jQuery(`.${activeFieldContainerClassName}`)); + this.modalPositioned = true; + } + this.ensureHoveredSelection(instance.calendarContainer); }, onChange: (dates:Date[], _datestr, instance) => { diff --git a/frontend/src/app/shared/components/datepicker/single-date-modal/single-date.modal.html b/frontend/src/app/shared/components/datepicker/single-date-modal/single-date.modal.html index ba44303af6..8a805f2702 100644 --- a/frontend/src/app/shared/components/datepicker/single-date-modal/single-date.modal.html +++ b/frontend/src/app/shared/components/datepicker/single-date-modal/single-date.modal.html @@ -5,6 +5,10 @@ #modalContainer data-indicator-name="modal" (submit)="save($event)" + tabindex="0" + cdkFocusInitial + cdkTrapFocus + [cdkTrapFocusAutoCapture]="true" > @@ -53,12 +57,14 @@
-
+
diff --git a/frontend/src/app/shared/components/header-project-select/header-project-select.component.sass b/frontend/src/app/shared/components/header-project-select/header-project-select.component.sass index f7a3a714a8..c1f68ae82f 100644 --- a/frontend/src/app/shared/components/header-project-select/header-project-select.component.sass +++ b/frontend/src/app/shared/components/header-project-select/header-project-select.component.sass @@ -1,2 +1,2 @@ -.op-project-menu-autocomplete +.op-header-project-select max-width: 100% diff --git a/frontend/src/app/shared/components/modals/confirm-dialog/confirm-dialog.modal.html b/frontend/src/app/shared/components/modals/confirm-dialog/confirm-dialog.modal.html index ac51ffb863..34257f6bbe 100644 --- a/frontend/src/app/shared/components/modals/confirm-dialog/confirm-dialog.modal.html +++ b/frontend/src/app/shared/components/modals/confirm-dialog/confirm-dialog.modal.html @@ -27,7 +27,7 @@
-
+
diff --git a/frontend/src/app/shared/components/modals/share-modal/query-sharing.modal.html b/frontend/src/app/shared/components/modals/share-modal/query-sharing.modal.html index fcc3ead750..edffc9ecfe 100644 --- a/frontend/src/app/shared/components/modals/share-modal/query-sharing.modal.html +++ b/frontend/src/app/shared/components/modals/share-modal/query-sharing.modal.html @@ -16,7 +16,7 @@
- diff --git a/frontend/src/app/shared/components/tabs/tab.interface.ts b/frontend/src/app/shared/components/tabs/tab.interface.ts index bc7da26d5a..bbfbcfa991 100644 --- a/frontend/src/app/shared/components/tabs/tab.interface.ts +++ b/frontend/src/app/shared/components/tabs/tab.interface.ts @@ -4,7 +4,7 @@ import { Injector } from '@angular/core'; export interface TabDefinition { /** Internal identifier of the tab */ id:string; - /** Human readable name */ + /** Human-readable name */ name:string; /** Manual URL to link to if set */ path?:string; diff --git a/frontend/src/app/shared/components/time_entries/shared/modal/base.modal.html b/frontend/src/app/shared/components/time_entries/shared/modal/base.modal.html index 59e40197d6..d235c28cd8 100644 --- a/frontend/src/app/shared/components/time_entries/shared/modal/base.modal.html +++ b/frontend/src/app/shared/components/time_entries/shared/modal/base.modal.html @@ -28,7 +28,7 @@
- + * + * ``` + */ + @Output() closed = new EventEmitter(); public text = { close: this.i18n.t('js.spot.drop_modal.close'), diff --git a/frontend/src/app/spot/components/drop-modal/stories/DropModalList.component.ts b/frontend/src/app/spot/components/drop-modal/stories/DropModalList.component.ts index f0c72b5c93..dbf67ecb42 100644 --- a/frontend/src/app/spot/components/drop-modal/stories/DropModalList.component.ts +++ b/frontend/src/app/spot/components/drop-modal/stories/DropModalList.component.ts @@ -12,8 +12,6 @@ import SpotDropAlignmentOption from '../../../drop-alignment-options'; templateUrl: './DropModalList.component.html', }) export class SbDropModalListComponent { - @HostBinding('class.spot-drop-modal') public className = true; - @Input() public alignment:SpotDropAlignmentOption = SpotDropAlignmentOption.BottomLeft; @Input('open') public dropModalOpen = false; diff --git a/frontend/src/app/spot/components/form-field/form-field.component.html b/frontend/src/app/spot/components/form-field/form-field.component.html index 0b936e04aa..0206c6c4b1 100644 --- a/frontend/src/app/spot/components/form-field/form-field.component.html +++ b/frontend/src/app/spot/components/form-field/form-field.component.html @@ -23,6 +23,13 @@ +
+ +
+
-
+
-
- -
-
+
@@ -72,7 +76,7 @@
- +
diff --git a/frontend/src/global_styles/content/_toast.sass b/frontend/src/global_styles/content/_toast.sass index e699342acb..57f4c6578d 100644 --- a/frontend/src/global_styles/content/_toast.sass +++ b/frontend/src/global_styles/content/_toast.sass @@ -295,6 +295,8 @@ $nm-upload-box-padding: rem-calc(15) rem-calc(25) // Use same styles for flash messages .flash, #errorExplanation + // Always above the toast + @include spot-z-index("toast", 1) border-radius: $nm-border-radius border-style: solid border-width: rem-calc(1) @@ -304,7 +306,6 @@ $nm-upload-box-padding: rem-calc(15) rem-calc(25) box-shadow: rem-calc(1px 2px 3px) rgba(0, 0, 0, 0.2) margin-bottom: 0.1875rem margin-top: 0.5rem - z-index: 15 &.ng-leave @include animation(0.5s fade-out) diff --git a/frontend/src/global_styles/layout/_main_menu_mobile.sass b/frontend/src/global_styles/layout/_main_menu_mobile.sass index a8baecf324..0bc7f4eaee 100644 --- a/frontend/src/global_styles/layout/_main_menu_mobile.sass +++ b/frontend/src/global_styles/layout/_main_menu_mobile.sass @@ -28,14 +28,14 @@ @include breakpoint(680px down) .main-menu + @include spot-z-index("main-menu") position: fixed - z-index: 12 border-bottom: 1px solid var(--main-menu-border-color) border: none box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.15) min-width: 75vw - #main-menu-toggle + #main-menu-toggle .icon-hamburger display: none .icon-close @@ -44,7 +44,7 @@ .hidden-navigation .main-menu display: none - #main-menu-toggle + #main-menu-toggle .icon-hamburger display: block .icon-close @@ -76,4 +76,4 @@ bottom: 0 right: 0 opacity: .4 - z-index: 11 + @include spot-z-index("main-menu", -1) diff --git a/frontend/src/global_styles/openproject/_onboarding.sass b/frontend/src/global_styles/openproject/_onboarding.sass index f767e8db2a..19b71c7e16 100644 --- a/frontend/src/global_styles/openproject/_onboarding.sass +++ b/frontend/src/global_styles/openproject/_onboarding.sass @@ -36,7 +36,6 @@ line-height: 1.25 .onboarding--video - margin: 2em 0 align-self: center width: 100% diff --git a/frontend/src/global_styles/vendor/foundation-apps/scss/vendor/_normalize.scss b/frontend/src/global_styles/vendor/foundation-apps/scss/vendor/_normalize.scss index ae8bc83d72..5239f2e168 100644 --- a/frontend/src/global_styles/vendor/foundation-apps/scss/vendor/_normalize.scss +++ b/frontend/src/global_styles/vendor/foundation-apps/scss/vendor/_normalize.scss @@ -229,7 +229,7 @@ kbd, pre, samp { font-family: monospace, monospace; - font-size: 1em; + font-size: 0.875em; } /* Forms diff --git a/frontend/src/stories/Divider.example.html b/frontend/src/stories/Divider.example.html new file mode 100644 index 0000000000..95297a98d9 --- /dev/null +++ b/frontend/src/stories/Divider.example.html @@ -0,0 +1,8 @@ +
+

Some header with a soft divider below

+
+

+ Lorem ipsum goes here but I'm too lazy to copy paste it from somewhere so I'll just ramble on + until I think it has been enough. That was a very long sentence so I'll do one shorter one. Blablabla is what I say. +

+
diff --git a/frontend/src/stories/Divider.example.ts b/frontend/src/stories/Divider.example.ts new file mode 100644 index 0000000000..360e3cc276 --- /dev/null +++ b/frontend/src/stories/Divider.example.ts @@ -0,0 +1,6 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './Divider.example.html', +}) +export class SbDividerExample { } diff --git a/frontend/src/stories/Divider.stories.mdx b/frontend/src/stories/Divider.stories.mdx index 93eaf01273..61556f271e 100644 --- a/frontend/src/stories/Divider.stories.mdx +++ b/frontend/src/stories/Divider.stories.mdx @@ -1,5 +1,7 @@ import { Meta, Story, Canvas } from '@storybook/addon-docs'; -import tokens from '../app/spot/styles/tokens/dist/tokens.json'; + +import { SbDividerSoftExample } from './DividerSoft.example'; +import { SbDividerStrongExample } from './DividerStrong.example'; @@ -27,14 +29,20 @@ A divider can be soft or strong. The correct one to use depends on the structure 1 px, Grey-5 (#E0E0E0) -> Example + + + {{ component: SbDividerSoftExample }} + + **Strong** 1px, Grey-4 (#CCCCCC) -_Example: In the work package details view, the main three-way split (top header, left-side description, right-side split screen) is done using strong dividers, but on the Activity tab, dates are separated with soft ones._ - -## Margins and Spacing + + + {{ component: SbDividerStrongExample }} + + -The divider itself does inherently have margin and spacing. They can however be given margins within containing elements (like a modal) if necessary. \ No newline at end of file +_Example: In the work package details view, the main three-way split (top header, left-side description, right-side split screen) is done using strong dividers, but on the Activity tab, dates are separated with soft ones._ diff --git a/frontend/src/stories/DividerSoft.example.html b/frontend/src/stories/DividerSoft.example.html new file mode 100644 index 0000000000..95297a98d9 --- /dev/null +++ b/frontend/src/stories/DividerSoft.example.html @@ -0,0 +1,8 @@ +
+

Some header with a soft divider below

+
+

+ Lorem ipsum goes here but I'm too lazy to copy paste it from somewhere so I'll just ramble on + until I think it has been enough. That was a very long sentence so I'll do one shorter one. Blablabla is what I say. +

+
diff --git a/frontend/src/stories/DividerSoft.example.ts b/frontend/src/stories/DividerSoft.example.ts new file mode 100644 index 0000000000..c430b5964d --- /dev/null +++ b/frontend/src/stories/DividerSoft.example.ts @@ -0,0 +1,6 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './DividerSoft.example.html', +}) +export class SbDividerSoftExample { } diff --git a/frontend/src/stories/DividerStrong.example.html b/frontend/src/stories/DividerStrong.example.html new file mode 100644 index 0000000000..4b0a838e6e --- /dev/null +++ b/frontend/src/stories/DividerStrong.example.html @@ -0,0 +1,8 @@ +
+

Strong divider below

+
+

+ Lorem ipsum goes here but I'm too lazy to copy paste it from somewhere so I'll just ramble on + until I think it has been enough. That was a very long sentence so I'll do one shorter one. Blablabla is what I say. +

+
diff --git a/frontend/src/stories/DividerStrong.example.ts b/frontend/src/stories/DividerStrong.example.ts new file mode 100644 index 0000000000..a7ebae0c4b --- /dev/null +++ b/frontend/src/stories/DividerStrong.example.ts @@ -0,0 +1,6 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './DividerStrong.example.html', +}) +export class SbDividerStrongExample { } diff --git a/lib/api/v3/custom_options/custom_options_api.rb b/lib/api/v3/custom_options/custom_options_api.rb index c95395f738..43540d0ae5 100644 --- a/lib/api/v3/custom_options/custom_options_api.rb +++ b/lib/api/v3/custom_options/custom_options_api.rb @@ -37,12 +37,26 @@ module API end helpers do - def authorize_view_in_activated_project(custom_option) + def authorize_custom_option_visibility(custom_option) + case custom_option.custom_field + when WorkPackageCustomField + authorized_work_package_option(custom_option) + when ProjectCustomField + authorize_any(%i[view_project], global: true) { raise API::Errors::NotFound } + when TimeEntryCustomField + authorize_any(%i[log_time log_own_time], global: true) { raise API::Errors::NotFound } + when UserCustomField, GroupCustomField + true + else + raise API::Errors::NotFound + end + end + + def authorized_work_package_option(custom_option) allowed = Project - .allowed_to(current_user, :view_work_packages) - .joins(:work_package_custom_fields) - .where(custom_fields: { id: custom_option.custom_field_id }) - .exists? + .allowed_to(current_user, :view_work_packages) + .joins(:work_package_custom_fields) + .exists?(custom_fields: { id: custom_option.custom_field_id }) unless allowed raise API::Errors::NotFound @@ -53,7 +67,7 @@ module API get do co = CustomOption.find(params[:id]) - authorize_view_in_activated_project(co) + authorize_custom_option_visibility(co) CustomOptionRepresenter.new(co, current_user:) end diff --git a/lib/open_project/scm/adapters/git.rb b/lib/open_project/scm/adapters/git.rb index 05e4d56c35..417c96878e 100644 --- a/lib/open_project/scm/adapters/git.rb +++ b/lib/open_project/scm/adapters/git.rb @@ -275,7 +275,7 @@ module OpenProject def lastrev(path, rev) return nil if path.nil? - args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1| + args = %w|log --no-abbrev-commit --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1| args << rev if rev args << '--' << path unless path.empty? lines = capture_git(args).lines @@ -387,7 +387,7 @@ module OpenProject end def build_revision_args(path, identifier_from, identifier_to, options) - args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller| + args = %w|log --no-abbrev-commit --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller| args << '--reverse' if options[:reverse] args << '--all' if options[:all] args << '-n' << options[:limit].to_i.to_s if options[:limit] @@ -404,9 +404,9 @@ module OpenProject def diff(path, identifier_from, identifier_to = nil) args = [] if identifier_to - args << 'diff' << '--no-color' << identifier_to << identifier_from + args << 'diff' << '--no-abbrev-commit' << '--no-color' << identifier_to << identifier_from else - args << 'show' << '--no-color' << identifier_from + args << 'show' << '--no-abbrev-commit' << '--no-color' << identifier_from end args << '--' << scm_encode(@path_encoding, 'UTF-8', path) unless path.empty? capture_git(args).lines diff --git a/lib/open_project/version.rb b/lib/open_project/version.rb index 0ff2a2e84b..db3d795168 100644 --- a/lib/open_project/version.rb +++ b/lib/open_project/version.rb @@ -32,7 +32,7 @@ require 'open3' module OpenProject module VERSION # :nodoc: MAJOR = 12 - MINOR = 3 + MINOR = 4 PATCH = 0 class << self diff --git a/lib/tasks/random_data.rake b/lib/tasks/random_data.rake deleted file mode 100644 index 27f6f553bf..0000000000 --- a/lib/tasks/random_data.rake +++ /dev/null @@ -1,46 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-2022 the OpenProject GmbH -# -# 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 COPYRIGHT and LICENSE files for more details. -#++ - -namespace :random_data do - desc 'seeds the data base wth random data' - task seed: :environment do - puts '*** Seeding basic data' - BasicDataSeeder.seed! - - puts '*** Seeding admin user' - AdminUserSeeder.new.seed! - - puts '*** Seeding demo data' - RandomDataSeeder.seed! - - ::Rails::Engine.subclasses.map(&:instance).each do |engine| - puts "*** Loading #{engine.engine_name} seed data" - engine.load_seed - end - end -end diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index 512891874f..af5c9ee843 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -131,7 +131,7 @@ module Redmine value = cv.value hash[key] = - if existing = hash[key] + if (existing = hash[key]) Array(existing) << value else value diff --git a/modules/bim/bin/setup_dev.sh b/modules/bim/bin/setup_dev.sh index 039a5b713b..25104c5e70 100755 --- a/modules/bim/bin/setup_dev.sh +++ b/modules/bim/bin/setup_dev.sh @@ -12,11 +12,9 @@ if [[ $EUID -ne 0 ]]; then exit 1 fi -# Specifics for BIM edition -wget -qO- https://packages.microsoft.com/keys/microsoft.asc | apt-key add - -wget -q https://packages.microsoft.com/config/debian/9/prod.list -O /etc/apt/sources.list.d/microsoft-prod.list +# Specifics for BIM edition (Ubuntu) apt-get update -qq -apt-get install -y dotnet-runtime-3.1 +apt-get install -y dotnet-runtime-6.0 wget unzip tmpdir=$(mktemp -d) cd $tmpdir @@ -31,11 +29,11 @@ wget --quiet https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.6.0-517b unzip -q IfcConvert-v0.6.0-517b819-linux64.zip mv IfcConvert "/usr/local/bin/IfcConvert" -wget --quiet https://github.com/bimspot/xeokit-metadata/releases/download/1.0.0/xeokit-metadata-linux-x64.tar.gz +wget --quiet https://github.com/bimspot/xeokit-metadata/releases/download/1.0.1/xeokit-metadata-linux-x64.tar.gz tar -zxvf xeokit-metadata-linux-x64.tar.gz chmod +x xeokit-metadata-linux-x64/xeokit-metadata -cp -r xeokit-metadata-linux-x64/ "/usr/lib/xeokit-metadata" -rm -f /usr/local/bin/xeokit-metadatarm -f /usr/local/bin/xeokit-metadata +cp -rT xeokit-metadata-linux-x64 "/usr/lib/xeokit-metadata" +rm -f /usr/local/bin/xeokit-metadata ln -s /usr/lib/xeokit-metadata/xeokit-metadata /usr/local/bin/xeokit-metadata cd / @@ -48,7 +46,7 @@ which COLLADA2GLTF echo "✔ COLLADA2GLTF is in your path." which xeokit-metadata -echo "✔ xeokit-metadata is in your path. (without .NET yet, see below)" +echo "✔ xeokit-metadata is in your path." echo "DONE - Now execute the following as your development user: $ # Install XKT converter diff --git a/modules/boards/app/services/boards/copy_service.rb b/modules/boards/app/services/boards/copy_service.rb index b740e4fa20..6f05f071e0 100644 --- a/modules/boards/app/services/boards/copy_service.rb +++ b/modules/boards/app/services/boards/copy_service.rb @@ -30,8 +30,9 @@ module Boards class CopyService < ::Grids::CopyService protected - def initialize_new_grid!(new_board, original_board, _params) - new_board.project = state.project || original_board.project + def set_attributes_params(params) + super + .merge(project: state.project || model.project) end end end diff --git a/modules/boards/app/services/boards/set_attributes_service.rb b/modules/boards/app/services/boards/set_attributes_service.rb new file mode 100644 index 0000000000..8a7676a2c7 --- /dev/null +++ b/modules/boards/app/services/boards/set_attributes_service.rb @@ -0,0 +1,29 @@ +# OpenProject is an open source project management software. +# Copyright (C) 2010-2022 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +module Boards + class SetAttributesService < ::BaseServices::SetAttributes; end +end diff --git a/modules/costs/spec/lib/api/v3/time_entries/schemas/time_entry_schema_representer_spec.rb b/modules/costs/spec/lib/api/v3/time_entries/schemas/time_entry_schema_representer_spec.rb index 3b7b5287a3..2a25aa797f 100644 --- a/modules/costs/spec/lib/api/v3/time_entries/schemas/time_entry_schema_representer_spec.rb +++ b/modules/costs/spec/lib/api/v3/time_entries/schemas/time_entry_schema_representer_spec.rb @@ -268,7 +268,7 @@ describe ::API::V3::TimeEntries::Schemas::TimeEntrySchemaRepresenter do end context 'custom value' do - let(:custom_field) { build_stubbed(:time_entry_custom_field) } + let(:custom_field) { build_stubbed(:text_time_entry_custom_field) } let(:path) { "customField#{custom_field.id}" } before do diff --git a/modules/costs/spec/lib/api/v3/time_entries/time_entry_representer_rendering_spec.rb b/modules/costs/spec/lib/api/v3/time_entries/time_entry_representer_rendering_spec.rb index 6fb622f93b..83a1a304be 100644 --- a/modules/costs/spec/lib/api/v3/time_entries/time_entry_representer_rendering_spec.rb +++ b/modules/costs/spec/lib/api/v3/time_entries/time_entry_representer_rendering_spec.rb @@ -255,7 +255,7 @@ describe ::API::V3::TimeEntries::TimeEntryRepresenter, 'rendering' do end context 'custom value' do - let(:custom_field) { build_stubbed(:time_entry_custom_field) } + let(:custom_field) { build_stubbed(:text_time_entry_custom_field) } let(:custom_value) do CustomValue.new(custom_field:, value: '1234', diff --git a/modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb b/modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb index f89e383ec9..de73bc8530 100644 --- a/modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb +++ b/modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb @@ -39,7 +39,7 @@ describe ::API::V3::TimeEntries::CreateFormAPI, content_type: :json do TimeEntryActivitiesProject.insert(project_id: project.id, activity_id: tea.id, active: false) end end - let(:custom_field) { create(:time_entry_custom_field) } + let(:custom_field) { create(:text_time_entry_custom_field) } let(:user) do create(:user, member_in_project: project, diff --git a/modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb b/modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb index 403eccd67a..eaddbe7dba 100644 --- a/modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb +++ b/modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb @@ -40,7 +40,7 @@ describe ::API::V3::TimeEntries::UpdateFormAPI, content_type: :json do TimeEntryActivitiesProject.insert(project_id: project.id, activity_id: tea.id, active: false) end end - let(:custom_field) { create(:time_entry_custom_field) } + let(:custom_field) { create(:text_time_entry_custom_field) } let(:user) do create(:user, member_in_project: project, diff --git a/modules/costs/spec/requests/api/time_entry_resource_spec.rb b/modules/costs/spec/requests/api/time_entry_resource_spec.rb index b47a7f2431..f284c79314 100644 --- a/modules/costs/spec/requests/api/time_entry_resource_spec.rb +++ b/modules/costs/spec/requests/api/time_entry_resource_spec.rb @@ -54,7 +54,7 @@ describe 'API v3 time_entry resource', type: :request do let(:other_project) { other_work_package.project } let(:role) { create(:role, permissions:) } let(:permissions) { %i(view_time_entries view_work_packages) } - let(:custom_field) { create(:time_entry_custom_field) } + let(:custom_field) { create(:text_time_entry_custom_field) } let(:custom_value) do CustomValue.create(custom_field:, value: '1234', diff --git a/modules/documents/spec/services/notifications/create_from_model_service_document_spec.rb b/modules/documents/spec/services/notifications/create_from_model_service_document_spec.rb index cf8eb98141..898cc64bc4 100644 --- a/modules/documents/spec/services/notifications/create_from_model_service_document_spec.rb +++ b/modules/documents/spec/services/notifications/create_from_model_service_document_spec.rb @@ -68,10 +68,20 @@ describe Notifications::CreateFromModelService, 'document', with_settings: { jou end end - context 'with the user having registered for involved notifications' do + context 'with the user having registered for assignee notifications' do let(:recipient_notification_settings) do [ - build(:notification_setting, **notification_settings_all_false.merge(involved: true)) + build(:notification_setting, **notification_settings_all_false.merge(assignee: true)) + ] + end + + it_behaves_like 'creates no notification' + end + + context 'with the user having registered for responsible notifications' do + let(:recipient_notification_settings) do + [ + build(:notification_setting, **notification_settings_all_false.merge(responsible: true)) ] end @@ -116,10 +126,20 @@ describe Notifications::CreateFromModelService, 'document', with_settings: { jou end end - context 'with the user having registered for involved notifications' do + context 'with the user having registered for assignee notifications' do + let(:recipient_notification_settings) do + [ + build(:notification_setting, **notification_settings_all_false.merge(assignee: true)) + ] + end + + it_behaves_like 'creates no notification' + end + + context 'with the user having registered for responsible notifications' do let(:recipient_notification_settings) do [ - build(:notification_setting, **notification_settings_all_false.merge(involved: true)) + build(:notification_setting, **notification_settings_all_false.merge(responsible: true)) ] end diff --git a/modules/ldap_groups/lib/tasks/ldap_groups.rake b/modules/ldap_groups/lib/tasks/ldap_groups.rake index cbcd107216..7dc5d14d6c 100644 --- a/modules/ldap_groups/lib/tasks/ldap_groups.rake +++ b/modules/ldap_groups/lib/tasks/ldap_groups.rake @@ -33,6 +33,25 @@ namespace :ldap_groups do ::LdapGroups::SynchronizationService.synchronize! end + desc 'Print all members of groups tied to a synchronized group that are not derived from LDAP' + task print_unsynced_members: :environment do + ::LdapGroups::SynchronizedGroup + .includes(:group) + .find_each do |sync| + + group = sync.group + unsynced_logins = User + .where(id: group.user_ids) + .where.not(id: sync.users.select(:user_id)) + .pluck(:login) + + if unsynced_logins.any? + puts "In group #{group}, #{unsynced_logins.count} user(s) exist that are not synced from LDAP:" + puts unsynced_logins.join(", ") + end + end + end + namespace :development do desc 'Create a development LDAP server from the fixtures LDIF' task :ldap_server do diff --git a/modules/overviews/app/services/overviews/copy_service.rb b/modules/overviews/app/services/overviews/copy_service.rb index dfc9152e52..6d5f10b848 100644 --- a/modules/overviews/app/services/overviews/copy_service.rb +++ b/modules/overviews/app/services/overviews/copy_service.rb @@ -30,12 +30,13 @@ module Overviews class CopyService < ::Grids::CopyService protected - def initialize_new_grid!(new_overview, _original_overview, _params) + def set_attributes_params(params) unless state.project raise ArgumentError, "Overviews can only be copied as part of a project. Each project may only contain 1 overview itself." end - new_overview.project = state.project + super + .merge(project: state.project) end end end diff --git a/modules/overviews/app/services/overviews/set_attributes_service.rb b/modules/overviews/app/services/overviews/set_attributes_service.rb new file mode 100644 index 0000000000..0cfd3ae61b --- /dev/null +++ b/modules/overviews/app/services/overviews/set_attributes_service.rb @@ -0,0 +1,29 @@ +# OpenProject is an open source project management software. +# Copyright (C) 2010-2022 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +module Overviews + class SetAttributesService < ::BaseServices::SetAttributes; end +end diff --git a/modules/reporting/app/models/cost_query/group_by/author_id.rb b/modules/reporting/app/models/cost_query/group_by/author_id.rb index 7b0b988771..87c24a2ac9 100644 --- a/modules/reporting/app/models/cost_query/group_by/author_id.rb +++ b/modules/reporting/app/models/cost_query/group_by/author_id.rb @@ -27,6 +27,8 @@ #++ class CostQuery::GroupBy::AuthorId < Report::GroupBy::Base + join_table WorkPackage + def self.label WorkPackage.human_attribute_name(:author) end diff --git a/modules/reporting/lib/widget/group_bys.rb b/modules/reporting/lib/widget/group_bys.rb index 67d266622d..62a6db4e39 100644 --- a/modules/reporting/lib/widget/group_bys.rb +++ b/modules/reporting/lib/widget/group_bys.rb @@ -30,9 +30,11 @@ class Widget::GroupBys < Widget::Base def render_options(group_by_ary) group_by_ary.sort_by(&:label).map do |group_by| next unless group_by.selectable? - - content_tag :option, value: group_by.underscore_name, 'data-label': CGI::escapeHTML(h(group_by.label)).to_s do - h(group_by.label) + label_text = CGI::escapeHTML(h(group_by.label)).to_s + option_tags = { value: group_by.underscore_name, 'data-label': label_text } + option_tags[:title] = label_text if group_by.label.length > 40 + content_tag :option, option_tags do + h(truncate_single_line(group_by.label, length: 40)) end end.join.html_safe end @@ -65,7 +67,7 @@ class Widget::GroupBys < Widget::Base class: 'hidden-for-sighted' label += content_tag :select, id: "group-by--add-#{type}", class: 'advanced-filters--select' do - content = content_tag :option, I18n.t(:label_group_by_add), value: '' + content = content_tag :option, I18n.t(:label_group_by_add), value: '', disabled: true, selected: true content += engine::GroupBy.all_grouped.sort_by do |label, _group_by_ary| I18n.t(label) diff --git a/modules/reporting/spec/models/cost_query/chaining_spec.rb b/modules/reporting/spec/models/cost_query/chaining_spec.rb index ba52163e63..bfc9c5d7cc 100644 --- a/modules/reporting/spec/models/cost_query/chaining_spec.rb +++ b/modules/reporting/spec/models/cost_query/chaining_spec.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper") describe CostQuery, type: :model, reporting_query_helper: true do let(:project) { create(:project) } @@ -37,72 +37,72 @@ describe CostQuery, type: :model, reporting_query_helper: true do before do # FIXME: is there a better way to load all filter and groups? CostQuery::Filter.all && CostQuery::GroupBy.all - CostQuery.chain_initializer.clear + described_class.chain_initializer.clear end after(:all) do - CostQuery.chain_initializer.clear + described_class.chain_initializer.clear end it "contains NoFilter" do - expect(@query.chain).to be_a(CostQuery::Filter::NoFilter) + expect(query.chain).to be_a(CostQuery::Filter::NoFilter) end it "keeps NoFilter at bottom" do - @query.filter :project_id - expect(@query.chain.bottom).to be_a(CostQuery::Filter::NoFilter) - expect(@query.chain.top).not_to be_a(CostQuery::Filter::NoFilter) + query.filter :project_id + expect(query.chain.bottom).to be_a(CostQuery::Filter::NoFilter) + expect(query.chain.top).not_to be_a(CostQuery::Filter::NoFilter) end it "remembers it's correct parent" do - @query.group_by :project_id - @query.filter :project_id - expect(@query.chain.top.child.child.parent).to eq(@query.chain.top.child) + query.group_by :project_id + query.filter :project_id + expect(query.chain.top.child.child.parent).to eq(query.chain.top.child) end it "places filter after a group_by" do - @query.group_by :project_id - expect(@query.chain.bottom.parent).to be_a(CostQuery::GroupBy::ProjectId) - expect(@query.chain.top).to be_a(CostQuery::GroupBy::ProjectId) + query.group_by :project_id + expect(query.chain.bottom.parent).to be_a(CostQuery::GroupBy::ProjectId) + expect(query.chain.top).to be_a(CostQuery::GroupBy::ProjectId) - @query.filter :project_id - expect(@query.chain.bottom.parent).to be_a(CostQuery::Filter::ProjectId) - expect(@query.chain.top).to be_a(CostQuery::GroupBy::ProjectId) + query.filter :project_id + expect(query.chain.bottom.parent).to be_a(CostQuery::Filter::ProjectId) + expect(query.chain.top).to be_a(CostQuery::GroupBy::ProjectId) end it "places rows in front of columns when adding a column first" do - @query.column :project_id - expect(@query.chain.bottom.parent.type).to eq(:column) - expect(@query.chain.top.type).to eq(:column) + query.column :project_id + expect(query.chain.bottom.parent.type).to eq(:column) + expect(query.chain.top.type).to eq(:column) - @query.row :project_id - expect(@query.chain.bottom.parent.type).to eq(:column) - expect(@query.chain.top.type).to eq(:row) + query.row :project_id + expect(query.chain.bottom.parent.type).to eq(:column) + expect(query.chain.top.type).to eq(:row) end it "places rows in front of filters" do - @query.row :project_id - expect(@query.chain.bottom.parent.type).to eq(:row) - expect(@query.chain.top.type).to eq(:row) - - @query.filter :project_id - expect(@query.chain.bottom.parent).to be_a(CostQuery::Filter::ProjectId) - expect(@query.chain.top).to be_a(CostQuery::GroupBy::ProjectId) - expect(@query.chain.top.type).to eq(:row) + query.row :project_id + expect(query.chain.bottom.parent.type).to eq(:row) + expect(query.chain.top.type).to eq(:row) + + query.filter :project_id + expect(query.chain.bottom.parent).to be_a(CostQuery::Filter::ProjectId) + expect(query.chain.top).to be_a(CostQuery::GroupBy::ProjectId) + expect(query.chain.top.type).to eq(:row) end it "returns all filters, including the NoFilter" do - @query.filter :project_id - @query.group_by :project_id - expect(@query.filters.size).to eq(2) - expect(@query.filters.map { |f| f.class.underscore_name }).to include "project_id" + query.filter :project_id + query.group_by :project_id + expect(query.filters.size).to eq(2) + expect(query.filters.map { |f| f.class.underscore_name }).to include "project_id" end it "returns all group_bys" do - @query.filter :project_id - @query.group_by :project_id - expect(@query.group_bys.size).to eq(1) - expect(@query.group_bys.map { |g| g.class.underscore_name }).to include "project_id" + query.filter :project_id + query.group_by :project_id + expect(query.group_bys.size).to eq(1) + expect(query.group_bys.map { |g| g.class.underscore_name }).to include "project_id" end it "initializes the chain through a block" do @@ -112,38 +112,39 @@ describe CostQuery, type: :model, reporting_query_helper: true do end end TestFilter.send(:initialize_query_with) { |query| query.filter(:project_id, value: project.id) } - @query.build_new_chain - expect(@query.filters.map { |f| f.class.underscore_name }).to include "project_id" - expect(@query.filters.detect { |f| f.class.underscore_name == "project_id" }.values).to eq(Array(project.id)) + query.build_new_chain + expect(query.filters.map { |f| f.class.underscore_name }).to include "project_id" + expect(query.filters.detect { |f| f.class.underscore_name == "project_id" }.values).to eq(Array(project.id)) end context "store and load" do + let(:new_query) { described_class.deserialize(query.serialize) } + before do - @query.filter :project_id, value: project.id - @query.filter :cost_type_id, value: CostQuery::Filter::CostTypeId.available_values.first - @query.filter :category_id, value: CostQuery::Filter::CategoryId.available_values.first - @query.group_by :activity_id - @query.group_by :budget_id - @query.group_by :cost_type_id - @new_query = CostQuery.deserialize(@query.serialize) + query.filter :project_id, value: project.id + query.filter :cost_type_id, value: CostQuery::Filter::CostTypeId.available_values.first + query.filter :category_id, value: CostQuery::Filter::CategoryId.available_values.first + query.group_by :activity_id + query.group_by :budget_id + query.group_by :cost_type_id end it "serializes the chain correctly" do %i[filters group_bys].each do |type| - @query.send(type).each do |chainable| - expect(@query.serialize[type].collect { |c| c[0] }).to include chainable.class.name.demodulize + query.send(type).each do |chainable| + expect(query.serialize[type].collect { |c| c[0] }).to include chainable.class.name.demodulize end end end it "deserializes a serialized query correctly" do - expect(@new_query.serialize).to eq(@query.serialize) + expect(new_query.serialize).to eq(query.serialize) end it "keeps the order of group bys" do - @query.group_bys.each_with_index do |group_by, index| + query.group_bys.each_with_index do |group_by, index| # check for order - @new_query.group_bys.each_with_index do |g, ix| + new_query.group_bys.each_with_index do |g, ix| if g.instance_of?(group_by.class) expect(ix).to eq(index) end @@ -152,11 +153,11 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "keeps the right filter values" do - @query.filters.each_with_index do |filter, _index| + query.filters.each_with_index do |filter, _index| # check for presence - expect(@new_query.filters.any? do |f| + expect(new_query.filters).to be_any do |f| f.instance_of?(filter.class) && (filter.respond_to?(:values) ? f.values == filter.values : true) - end).to be_truthy + end end end end @@ -164,33 +165,33 @@ describe CostQuery, type: :model, reporting_query_helper: true do describe Report::Chainable do describe '#top' do - before { @chain = Report::Chainable.new } + let(:chain) { described_class.new } it "returns for an one element long chain that chain as top" do - expect(@chain.top).to eq(@chain) - expect(@chain).to be_top + expect(chain.top).to eq(chain) + expect(chain).to be_top end it "does not keep the old top when prepending elements" do - Report::Chainable.new @chain - expect(@chain.top).not_to eq(@chain) - expect(@chain).not_to be_top + described_class.new chain + expect(chain.top).not_to eq(chain) + expect(chain).not_to be_top end it "sets new top when prepending elements" do - current = @chain + current = chain 10.times do old = current - current = Report::Chainable.new(current) + current = described_class.new(current) expect(old.top).to eq(current) - expect(@chain.top).to eq(current) + expect(chain.top).to eq(current) end end end describe '#inherited_attribute' do before do - @a = Class.new Report::Chainable + @a = Class.new described_class @a.inherited_attribute :foo, default: 42 @b = Class.new @a @c = Class.new @a diff --git a/modules/reporting/spec/models/cost_query/filter_spec.rb b/modules/reporting/spec/models/cost_query/filter_spec.rb index 913f59f233..60d138ec52 100644 --- a/modules/reporting/spec/models/cost_query/filter_spec.rb +++ b/modules/reporting/spec/models/cost_query/filter_spec.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper") require File.join(File.dirname(__FILE__), '..', '..', 'support', 'custom_field_filter') describe CostQuery, type: :model, reporting_query_helper: true do @@ -51,17 +51,17 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "shows all entries when no filter is applied" do - expect(@query.result.count).to eq(Entry.count) + expect(query.result.count).to eq(Entry.count) end it "always sets cost_type" do - @query.result.each do |result| + query.result.each do |result| expect(result["cost_type"]).not_to be_nil end end it "sets activity_id to -1 for cost entries" do - @query.result.each do |result| + query.result.each do |result| expect(result["activity_id"].to_i).to eq(-1) if result["type"] != "TimeEntry" end end @@ -102,29 +102,29 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "onlies return entries from the given #{filter}" do - @query.filter field, value: object.id - @query.result.each do |result| + query.filter field, value: object.id + query.result.each do |result| expect(result[field].to_s).to eq(object.id.to_s) end end it "allows chaining the same filter" do - @query.filter field, value: object.id - @query.filter field, value: object.id - @query.result.each do |result| + query.filter field, value: object.id + query.filter field, value: object.id + query.result.each do |result| expect(result[field].to_s).to eq(object.id.to_s) end end it "returns no results for excluding filters" do - @query.filter field, value: object.id - @query.filter field, value: object.id + 1 - expect(@query.result.count).to eq(0) + query.filter field, value: object.id + query.filter field, value: object.id + 1 + expect(query.result.count).to eq(0) end it "computes the correct number of results" do - @query.filter field, value: object.id - expect(@query.result.count).to eq(expected_count) + query.filter field, value: object.id + expect(query.result.count).to eq(expected_count) end end end @@ -157,49 +157,49 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "onlies return entries from the given CostQuery::Filter::AuthorId" do - @query.filter 'author_id', value: author.id - @query.result.each do |result| + query.filter 'author_id', value: author.id + query.result.each do |result| work_package_id = result["work_package_id"] expect(WorkPackage.find(work_package_id).author.id).to eq(author.id) end end it "allows chaining the same filter" do - @query.filter 'author_id', value: author.id - @query.filter 'author_id', value: author.id - @query.result.each do |result| + query.filter 'author_id', value: author.id + query.filter 'author_id', value: author.id + query.result.each do |result| work_package_id = result["work_package_id"] expect(WorkPackage.find(work_package_id).author.id).to eq(author.id) end end it "returns no results for excluding filters" do - @query.filter 'author_id', value: author.id - @query.filter 'author_id', value: author.id + 1 - expect(@query.result.count).to eq(0) + query.filter 'author_id', value: author.id + query.filter 'author_id', value: author.id + 1 + expect(query.result.count).to eq(0) end it "computes the correct number of results" do - @query.filter 'author_id', value: author.id - expect(@query.result.count).to eq(2) + query.filter 'author_id', value: author.id + expect(query.result.count).to eq(2) end end it "filters spent_on" do - @query.filter :spent_on, operator: 'w' - expect(@query.result.count).to eq(Entry.all.select { |e| e.spent_on.cweek == TimeEntry.all.first.spent_on.cweek }.count) + query.filter :spent_on, operator: 'w' + expect(query.result.count).to eq(Entry.all.select { |e| e.spent_on.cweek == TimeEntry.all.first.spent_on.cweek }.count) end it "filters created_at" do - @query.filter :created_on, operator: 't' + query.filter :created_on, operator: 't' # we assume that some of our fixtures set created_at to Time.now - expect(@query.result.count).to eq(Entry.all.select { |e| e.created_at.to_date == Date.today }.count) + expect(query.result.count).to eq(Entry.all.select { |e| e.created_at.to_date == Time.zone.today }.count) end it "filters updated_at" do - @query.filter :updated_on, value: Date.today.years_ago(20), operator: '>d' + query.filter :updated_on, value: Time.zone.today.years_ago(20), operator: '>d' # we assume that our were updated in the last 20 years - expect(@query.result.count).to eq(Entry.all.select { |e| e.updated_at.to_date > Date.today.years_ago(20) }.count) + expect(query.result.count).to eq(Entry.all.select { |e| e.updated_at.to_date > Time.zone.today.years_ago(20) }.count) end it "filters user_id" do @@ -209,8 +209,8 @@ describe CostQuery, type: :model, reporting_query_helper: true do create_work_package_with_time_entry({}, { user: anonymous }) # create matching entry create_work_package_with_time_entry - @query.filter :user_id, value: user.id, operator: '=' - expect(@query.result.count).to eq(1) + query.filter :user_id, value: user.id, operator: '=' + expect(query.result.count).to eq(1) end describe "work_package-based filters" do @@ -227,79 +227,79 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "filters overridden_costs" do - @query.filter :overridden_costs, operator: 'y' - expect(@query.result.count).to eq(Entry.all.select { |e| not e.overridden_costs.nil? }.count) + query.filter :overridden_costs, operator: 'y' + expect(query.result.count).to eq(Entry.all.reject { |e| e.overridden_costs.nil? }.count) end it "filters status" do matching_status = create(:status, is_closed: true) create_work_packages_and_time_entries(3, status: matching_status) - @query.filter :status_id, operator: 'c' - expect(@query.result.count).to eq(3) + query.filter :status_id, operator: 'c' + expect(query.result.count).to eq(3) end it "filters types" do matching_type = project.types.first create_work_packages_and_time_entries(3, type: matching_type) - @query.filter :type_id, operator: '=', value: matching_type.id - expect(@query.result.count).to eq(3) + query.filter :type_id, operator: '=', value: matching_type.id + expect(query.result.count).to eq(3) end it "filters work_package authors" do matching_author = create_matching_object_with_time_entries(:user, :author, 3) - @query.filter :author_id, operator: '=', value: matching_author.id - expect(@query.result.count).to eq(3) + query.filter :author_id, operator: '=', value: matching_author.id + expect(query.result.count).to eq(3) end it "filters priority" do matching_priority = create_matching_object_with_time_entries(:priority, :priority, 3) - @query.filter :priority_id, operator: '=', value: matching_priority.id - expect(@query.result.count).to eq(3) + query.filter :priority_id, operator: '=', value: matching_priority.id + expect(query.result.count).to eq(3) end it "filters assigned to" do matching_user = create_matching_object_with_time_entries(:user, :assigned_to, 3) - @query.filter :assigned_to_id, operator: '=', value: matching_user.id - expect(@query.result.count).to eq(3) + query.filter :assigned_to_id, operator: '=', value: matching_user.id + expect(query.result.count).to eq(3) end it "filters category" do category = create(:category, project:) create_work_packages_and_time_entries(3, category:) - @query.filter :category_id, operator: '=', value: category.id - expect(@query.result.count).to eq(3) + query.filter :category_id, operator: '=', value: category.id + expect(query.result.count).to eq(3) end it "filters target version" do matching_version = create(:version, project:) create_work_packages_and_time_entries(3, version: matching_version) - @query.filter :version_id, operator: '=', value: matching_version.id - expect(@query.result.count).to eq(3) + query.filter :version_id, operator: '=', value: matching_version.id + expect(query.result.count).to eq(3) end it "filters subject" do matching_work_package = create_work_package_with_time_entry(subject: 'matching subject') - @query.filter :subject, operator: '=', value: 'matching subject' - expect(@query.result.count).to eq(1) + query.filter :subject, operator: '=', value: 'matching subject' + expect(query.result.count).to eq(1) end it "filters start" do start_date = Date.new(2013, 1, 1) matching_work_package = create_work_package_with_time_entry(start_date:) - @query.filter :start_date, operator: '=d', value: start_date - expect(@query.result.count).to eq(1) + query.filter :start_date, operator: '=d', value: start_date + expect(query.result.count).to eq(1) end it "filters due date" do due_date = Date.new(2013, 1, 1) matching_work_package = create_work_package_with_time_entry(due_date:) - @query.filter :due_date, operator: '=d', value: due_date - expect(@query.result.count).to eq(1) + query.filter :due_date, operator: '=d', value: due_date + expect(query.result.count).to eq(1) end it "raises an error if operator is not supported" do - expect { @query.filter :spent_on, operator: 'c' }.to raise_error(ArgumentError) + expect { query.filter :spent_on, operator: 'c' }.to raise_error(ArgumentError) end end @@ -426,7 +426,7 @@ describe CostQuery, type: :model, reporting_query_helper: true do it "includes custom fields classes in CustomFieldEntries.all" do custom_field - expect(CostQuery::Filter::CustomFieldEntries.all) + expect(described_class.all) .to include(filter_class_name_string(custom_field).constantize) end @@ -457,15 +457,15 @@ describe CostQuery, type: :model, reporting_query_helper: true do it "is usable as filter" do create_searchable_fields_and_values id = WorkPackageCustomField.find_by(name: "Searchable Field").id - @query.filter "custom_field_#{id}".to_sym, operator: '=', value: "125" - expect(@query.result.count).to eq(2) + query.filter "custom_field_#{id}".to_sym, operator: '=', value: "125" + expect(query.result.count).to eq(2) end it "is usable as filter #2" do create_searchable_fields_and_values id = WorkPackageCustomField.find_by(name: "Searchable Field").id - @query.filter "custom_field_#{id}".to_sym, operator: '=', value: "finnlabs" - expect(@query.result.count).to eq(0) + query.filter "custom_field_#{id}".to_sym, operator: '=', value: "finnlabs" + expect(query.result.count).to eq(0) end end end diff --git a/modules/reporting/spec/models/cost_query/group_by_spec.rb b/modules/reporting/spec/models/cost_query/group_by_spec.rb index 041f2d4599..ad3914faa4 100644 --- a/modules/reporting/spec/models/cost_query/group_by_spec.rb +++ b/modules/reporting/spec/models/cost_query/group_by_spec.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper") require File.join(File.dirname(__FILE__), '..', '..', 'support', 'custom_field_filter') describe CostQuery, type: :model, reporting_query_helper: true do @@ -75,75 +75,80 @@ describe CostQuery, type: :model, reporting_query_helper: true do describe CostQuery::GroupBy do it "computes group_by on projects" do - @query.group_by :project_id - expect(@query.result.size).to eq(2) + query.group_by :project_id + expect(query.result.size).to eq(2) end it "keeps own and all parents' group fields in all_group_fields" do - @query.group_by :project_id - @query.group_by :work_package_id - @query.group_by :cost_type_id - expect(@query.all_group_fields).to eq(%w[entries.cost_type_id]) - expect(@query.child.all_group_fields).to eq(%w[entries.cost_type_id entries.work_package_id]) - expect(@query.child.child.all_group_fields).to eq(%w[entries.cost_type_id entries.work_package_id entries.project_id]) + query.group_by :project_id + query.group_by :work_package_id + query.group_by :cost_type_id + expect(query.all_group_fields).to eq(%w[entries.cost_type_id]) + expect(query.child.all_group_fields).to eq(%w[entries.cost_type_id entries.work_package_id]) + expect(query.child.child.all_group_fields).to eq(%w[entries.cost_type_id entries.work_package_id entries.project_id]) end it "computes group_by WorkPackage" do - @query.group_by :work_package_id - expect(@query.result.size).to eq(2) + query.group_by :work_package_id + expect(query.result.size).to eq(2) end it "computes group_by CostType" do - @query.group_by :cost_type_id + query.group_by :cost_type_id # type 'Labor' for time entries, 2 different cost types - expect(@query.result.size).to eq(3) + expect(query.result.size).to eq(3) end it "computes group_by Activity" do - @query.group_by :activity_id + query.group_by :activity_id # "-1" for time entries, 2 different cost activities - expect(@query.result.size).to eq(3) + expect(query.result.size).to eq(3) end it "computes group_by Date (day)" do - @query.group_by :spent_on - expect(@query.result.size).to eq(2) + query.group_by :spent_on + expect(query.result.size).to eq(2) end it "computes group_by Date (week)" do - @query.group_by :tweek - expect(@query.result.size).to eq(2) + query.group_by :tweek + expect(query.result.size).to eq(2) end it "computes group_by Date (month)" do - @query.group_by :tmonth - expect(@query.result.size).to eq(2) + query.group_by :tmonth + expect(query.result.size).to eq(2) end it "computes group_by Date (year)" do - @query.group_by :tyear - expect(@query.result.size).to eq(2) + query.group_by :tyear + expect(query.result.size).to eq(2) end it "computes group_by User" do - @query.group_by :user_id - expect(@query.result.size).to eq(4) + query.group_by :user_id + expect(query.result.size).to eq(4) + end + + it "computes group_by Author" do + query.group_by :author_id + expect(query.result.size).to eq(2) end it "computes group_by Type" do - @query.group_by :type_id - expect(@query.result.size).to eq(1) + query.group_by :type_id + expect(query.result.size).to eq(1) end it "computes group_by Budget" do - @query.group_by :budget_id - expect(@query.result.size).to eq(1) + query.group_by :budget_id + expect(query.result.size).to eq(1) end it "computes multiple group_by" do - @query.group_by :project_id - @query.group_by :user_id - sql_result = @query.result + query.group_by :project_id + query.group_by :user_id + sql_result = query.result expect(sql_result.size).to eq(4) # for each user the number of projects should be correct @@ -159,9 +164,9 @@ describe CostQuery, type: :model, reporting_query_helper: true do # TODO: ? it "computes multiple group_by with joins" do - @query.group_by :project_id - @query.group_by :type_id - sql_result = @query.result + query.group_by :project_id + query.group_by :type_id + sql_result = query.result expect(sql_result.size).to eq(1) # for each type the number of projects should be correct sql_sizes = [] @@ -175,39 +180,39 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "compute count correct with lots of group_by" do - @query.group_by :project_id - @query.group_by :work_package_id - @query.group_by :cost_type_id - @query.group_by :activity_id - @query.group_by :spent_on - @query.group_by :tweek - @query.group_by :type_id - @query.group_by :tmonth - @query.group_by :tyear - - expect(@query.result.count).to eq(8) + query.group_by :project_id + query.group_by :work_package_id + query.group_by :cost_type_id + query.group_by :activity_id + query.group_by :spent_on + query.group_by :tweek + query.group_by :type_id + query.group_by :tmonth + query.group_by :tyear + + expect(query.result.count).to eq(8) end it "accepts row as a specialised group_by" do - @query.row :project_id - expect(@query.chain.type).to eq(:row) + query.row :project_id + expect(query.chain.type).to eq(:row) end it "accepts column as a specialised group_by" do - @query.column :project_id - expect(@query.chain.type).to eq(:column) + query.column :project_id + expect(query.chain.type).to eq(:column) end it "has type :column as a default" do - @query.group_by :project_id - expect(@query.chain.type).to eq(:column) + query.group_by :project_id + expect(query.chain.type).to eq(:column) end it "aggregates a third group_by which owns at least 2 sub results" do - @query.group_by :tweek - @query.group_by :project_id - @query.group_by :user_id - sql_result = @query.result + query.group_by :tweek + query.group_by :project_id + query.group_by :user_id + sql_result = query.result expect(sql_result.size).to eq(4) # for each user the number of projects should be correct @@ -243,7 +248,7 @@ describe CostQuery, type: :model, reporting_query_helper: true do before do check_cache - CostQuery::GroupBy.all.merge CostQuery::GroupBy::CustomFieldEntries.all + CostQuery::GroupBy.all.merge described_class.all end def check_cache @@ -287,7 +292,7 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "includes custom fields classes in CustomFieldEntries.all" do - expect(CostQuery::GroupBy::CustomFieldEntries.all) + expect(described_class.all) .to include(group_by_class_name_string(custom_field).constantize) end @@ -301,8 +306,8 @@ describe CostQuery, type: :model, reporting_query_helper: true do check_cache - @query.group_by "custom_field_#{custom_field2.id}".to_sym - footprint = @query.result.each_direct_result.map { |c| [c.count, c.units.to_i] }.sort + query.group_by "custom_field_#{custom_field2.id}".to_sym + footprint = query.result.each_direct_result.map { |c| [c.count, c.units.to_i] }.sort expect(footprint).to eq([[8, 8]]) end end diff --git a/modules/reporting/spec/models/cost_query/integration_spec.rb b/modules/reporting/spec/models/cost_query/integration_spec.rb index eca5cc5121..99f89e4287 100644 --- a/modules/reporting/spec/models/cost_query/integration_spec.rb +++ b/modules/reporting/spec/models/cost_query/integration_spec.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper") describe CostQuery, type: :model, reporting_query_helper: true do minimal_query @@ -47,9 +47,9 @@ describe CostQuery, type: :model, reporting_query_helper: true do describe "the reporting system" do it "computes group_by and a filter" do - @query.group_by :project_id - @query.filter :status_id, operator: 'o' - sql_result = @query.result + query.group_by :project_id + query.filter :status_id, operator: 'o' + sql_result = query.result expect(sql_result.size).to eq(2) # for each project the number of entries should be correct @@ -63,11 +63,11 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "applies two filter and a group_by correctly" do - @query.filter :project_id, operator: '=', value: [project1.id] - @query.group_by :user_id - @query.filter :overridden_costs, operator: 'n' + query.filter :project_id, operator: '=', value: [project1.id] + query.group_by :user_id + query.filter :overridden_costs, operator: 'n' - sql_result = @query.result + sql_result = query.result expect(sql_result.size).to eq(2) # for each user the number of entries should be correct sql_count = [] @@ -80,10 +80,10 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "applies two different filters on the same field" do - @query.filter :project_id, operator: '=', value: [project1.id, project2.id] - @query.filter :project_id, operator: '!', value: [project2.id] + query.filter :project_id, operator: '=', value: [project1.id, project2.id] + query.filter :project_id, operator: '!', value: [project2.id] - sql_result = @query.result + sql_result = query.result expect(sql_result.count).to eq(2) end @@ -98,14 +98,14 @@ describe CostQuery, type: :model, reporting_query_helper: true do end # create a random query - @query.group_by :work_package_id - @query.column :tweek - @query.row :project_id - @query.row :user_id + query.group_by :work_package_id + query.column :tweek + query.row :project_id + query.row :user_id # count how often a sql query was created number_of_sql_queries = 0 # do some random things on it - walker = @query.transformer + walker = query.transformer walker.row_first walker.column_first # TODO - to do something diff --git a/modules/reporting/spec/models/cost_query/operator_spec.rb b/modules/reporting/spec/models/cost_query/operator_spec.rb index 6233da23b1..50547e319b 100644 --- a/modules/reporting/spec/models/cost_query/operator_spec.rb +++ b/modules/reporting/spec/models/cost_query/operator_spec.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper") describe CostQuery, type: :model, reporting_query_helper: true do minimal_query @@ -35,7 +35,7 @@ describe CostQuery, type: :model, reporting_query_helper: true do let!(:project2) { create(:project, name: "project2", created_at: 6.minutes.ago) } describe CostQuery::Operator do - def query(table, field, operator, *values) + def cost_query(table, field, operator, *values) sql = CostQuery::SqlStatement.new table yield sql if block_given? operator.to_operator.modify sql, field, *values @@ -53,11 +53,11 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "does =" do - expect(query('projects', 'id', '=', project1.id).size).to eq(1) + expect(cost_query('projects', 'id', '=', project1.id).size).to eq(1) end it "does = for multiple values" do - expect(query('projects', 'id', '=', project1.id, project2.id).size).to eq(2) + expect(cost_query('projects', 'id', '=', project1.id, project2.id).size).to eq(2) end it "does = for no values" do @@ -68,172 +68,172 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "does = for nil" do - expect(query('projects', 'id', '=', nil).size).to eq(0) + expect(cost_query('projects', 'id', '=', nil).size).to eq(0) end it "does = for empty string" do - expect(query('projects', 'id', '=', '').size).to eq(0) + expect(cost_query('projects', 'id', '=', '').size).to eq(0) end it "does <=" do - expect(query('projects', 'id', '<=', project2.id - 1).size).to eq(1) + expect(cost_query('projects', 'id', '<=', project2.id - 1).size).to eq(1) end it "does >=" do - expect(query('projects', 'id', '>=', project1.id + 1).size).to eq(1) + expect(cost_query('projects', 'id', '>=', project1.id + 1).size).to eq(1) end it "does !" do - expect(query('projects', 'id', '!', project1.id).size).to eq(1) + expect(cost_query('projects', 'id', '!', project1.id).size).to eq(1) end it "does ! for empty string" do - expect(query('projects', 'id', '!', '').size).to eq(0) + expect(cost_query('projects', 'id', '!', '').size).to eq(0) end it "does ! for multiple values" do - expect(query('projects', 'id', '!', project1.id, project2.id).size).to eq(0) + expect(cost_query('projects', 'id', '!', project1.id, project2.id).size).to eq(0) end it "does !*" do - expect(query('cost_entries', 'project_id', '!*', []).size).to eq(0) + expect(cost_query('cost_entries', 'project_id', '!*', []).size).to eq(0) end it "does ~ (contains)" do - expect(query('projects', 'name', '~', 'o').size).to eq(Project.all.select { |p| p.name =~ /o/ }.count) - expect(query('projects', 'name', '~', 'test').size).to eq(Project.all.select { |p| p.name =~ /test/ }.count) - expect(query('projects', 'name', '~', 'child').size).to eq(Project.all.select { |p| p.name =~ /child/ }.count) + expect(cost_query('projects', 'name', '~', 'o').size).to eq(Project.all.select { |p| p.name =~ /o/ }.count) + expect(cost_query('projects', 'name', '~', 'test').size).to eq(Project.all.select { |p| p.name =~ /test/ }.count) + expect(cost_query('projects', 'name', '~', 'child').size).to eq(Project.all.select { |p| p.name =~ /child/ }.count) end it "does !~ (not contains)" do - expect(query('projects', 'name', '!~', 'o').size).to eq(Project.all.select { |p| p.name !~ /o/ }.count) - expect(query('projects', 'name', '!~', 'test').size).to eq(Project.all.select { |p| p.name !~ /test/ }.count) - expect(query('projects', 'name', '!~', 'child').size).to eq(Project.all.select { |p| p.name !~ /child/ }.count) + expect(cost_query('projects', 'name', '!~', 'o').size).to eq(Project.all.reject { |p| p.name =~ /o/ }.count) + expect(cost_query('projects', 'name', '!~', 'test').size).to eq(Project.all.reject { |p| p.name =~ /test/ }.count) + expect(cost_query('projects', 'name', '!~', 'child').size).to eq(Project.all.reject { |p| p.name =~ /child/ }.count) end it "does c (closed work_package)" do - expect(query('work_packages', 'status_id', 'c') { |s| s.join Status => [WorkPackage, :status] }.size).to be >= 0 + expect(cost_query('work_packages', 'status_id', 'c') { |s| s.join Status => [WorkPackage, :status] }.size).to be >= 0 end it "does o (open work_package)" do - expect(query('work_packages', 'status_id', 'o') { |s| s.join Status => [WorkPackage, :status] }.size).to be >= 0 + expect(cost_query('work_packages', 'status_id', 'o') { |s| s.join Status => [WorkPackage, :status] }.size).to be >= 0 end it "does give the correct number of results when counting closed and open work_packages" do - a = query('work_packages', 'status_id', 'o') { |s| s.join Status => [WorkPackage, :status] }.size - b = query('work_packages', 'status_id', 'c') { |s| s.join Status => [WorkPackage, :status] }.size + a = cost_query('work_packages', 'status_id', 'o') { |s| s.join Status => [WorkPackage, :status] }.size + b = cost_query('work_packages', 'status_id', 'c') { |s| s.join Status => [WorkPackage, :status] }.size expect(WorkPackage.count).to eq(a + b) end it "does w (this week)" do # somehow this test doesn't work on sundays - n = query('projects', 'created_at', 'w').size - day_in_this_week = Time.now.at_beginning_of_week + 1.day + n = cost_query('projects', 'created_at', 'w').size + day_in_this_week = Time.zone.now.at_beginning_of_week + 1.day create(:project, created_at: day_in_this_week) - expect(query('projects', 'created_at', 'w').size).to eq(n + 1) + expect(cost_query('projects', 'created_at', 'w').size).to eq(n + 1) create(:project, created_at: day_in_this_week + 7.days) create(:project, created_at: day_in_this_week - 7.days) - expect(query('projects', 'created_at', 'w').size).to eq(n + 1) + expect(cost_query('projects', 'created_at', 'w').size).to eq(n + 1) end it "does t (today)" do - s = query('projects', 'created_at', 't').size + s = cost_query('projects', 'created_at', 't').size create(:project, created_at: Date.yesterday) - expect(query('projects', 'created_at', 't').size).to eq(s) - create(:project, created_at: Time.now) - expect(query('projects', 'created_at', 't').size).to eq(s + 1) + expect(cost_query('projects', 'created_at', 't').size).to eq(s) + create(:project, created_at: Time.zone.now) + expect(cost_query('projects', 'created_at', 't').size).to eq(s + 1) end it "does t+ (after the day which is n days in the future)" do - n = query('projects', 'created_at', '>t+', 1).size - create(:project, created_at: Time.now) - expect(query('projects', 'created_at', '>t+', 1).size).to eq(n) + n = cost_query('projects', 'created_at', '>t+', 1).size + create(:project, created_at: Time.zone.now) + expect(cost_query('projects', 'created_at', '>t+', 1).size).to eq(n) create(:project, created_at: Date.tomorrow + 1) - expect(query('projects', 'created_at', '>t+', 1).size).to eq(n + 1) + expect(cost_query('projects', 'created_at', '>t+', 1).size).to eq(n + 1) end it "does >t- (after the day which is n days ago)" do - n = query('projects', 'created_at', '>t-', 1).size + n = cost_query('projects', 'created_at', '>t-', 1).size create(:project, created_at: Date.today) - expect(query('projects', 'created_at', '>t-', 1).size).to eq(n + 1) + expect(cost_query('projects', 'created_at', '>t-', 1).size).to eq(n + 1) create(:project, created_at: Date.yesterday - 1) - expect(query('projects', 'created_at', '>t-', 1).size).to eq(n + 1) + expect(cost_query('projects', 'created_at', '>t-', 1).size).to eq(n + 1) end it "does t- (n days ago)" do - n = query('projects', 'created_at', 't-', 1).size + n = cost_query('projects', 'created_at', 't-', 1).size create(:project, created_at: Date.yesterday) - expect(query('projects', 'created_at', 't-', 1).size).to eq(n + 1) + expect(cost_query('projects', 'created_at', 't-', 1).size).to eq(n + 1) create(:project, created_at: Date.yesterday - 2) - expect(query('projects', 'created_at', 't-', 1).size).to eq(n + 1) + expect(cost_query('projects', 'created_at', 't-', 1).size).to eq(n + 1) end it "does d" do - expect(query('projects', 'created_at', '<>d', Time.now, 5.minutes.from_now).size).to eq(0) + expect(cost_query('projects', 'created_at', '<>d', Time.zone.now, 5.minutes.from_now).size).to eq(0) end it "does >d" do # assuming that all projects were created in the past - expect(query('projects', 'created_at', '>d', Time.now).size).to eq(0) + expect(cost_query('projects', 'created_at', '>d', Time.zone.now).size).to eq(0) end describe 'arity' do diff --git a/modules/reporting/spec/models/cost_query/result_spec.rb b/modules/reporting/spec/models/cost_query/result_spec.rb index 0e7af8e25f..fe73885b4a 100644 --- a/modules/reporting/spec/models/cost_query/result_spec.rb +++ b/modules/reporting/spec/models/cost_query/result_spec.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper") describe CostQuery, type: :model, reporting_query_helper: true do before do @@ -105,23 +105,23 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "computes count correctly" do - expect(@query.result.count).to eq(Entry.count) + expect(query.result.count).to eq(Entry.count) end it "computes units correctly" do - expect(@query.result.units).to eq(Entry.all.map { |e| e.units }.sum) + expect(query.result.units).to eq(Entry.all.map(&:units).sum) end it "computes real_costs correctly" do - expect(@query.result.real_costs).to eq(Entry.all.map { |e| e.overridden_costs || e.costs }.sum) + expect(query.result.real_costs).to eq(Entry.all.map { |e| e.overridden_costs || e.costs }.sum) end it "computes count for DirectResults" do - expect(@query.result.values[0].count).to eq(1) + expect(query.result.values[0].count).to eq(1) end it "computes units for DirectResults" do - id_sorted = @query.result.values.sort_by { |r| r[:id] } + id_sorted = query.result.values.sort_by { |r| r[:id] } te_result = id_sorted.select { |r| r[:type] == TimeEntry.to_s }.first ce_result = id_sorted.select { |r| r[:type] == CostEntry.to_s }.first expect(te_result.units.to_s).to eq("1.0") @@ -129,7 +129,7 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "computes real_costs for DirectResults" do - id_sorted = @query.result.values.sort_by { |r| r[:id] } + id_sorted = query.result.values.sort_by { |r| r[:id] } [CostEntry].each do |type| result = id_sorted.select { |r| r[:type] == type.to_s }.first first = type.all.first @@ -138,18 +138,18 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "is a column if created with CostQuery.column" do - @query.column :project_id - expect(@query.result.type).to eq(:column) + query.column :project_id + expect(query.result.type).to eq(:column) end it "is a row if created with CostQuery.row" do - @query.row :project_id - expect(@query.result.type).to eq(:row) + query.row :project_id + expect(query.result.type).to eq(:row) end it "shows the type :direct for its direct results" do - @query.column :project_id - expect(@query.result.first.first.type).to eq(:direct) + query.column :project_id + expect(query.result.first.first.type).to eq(:direct) end end end diff --git a/modules/reporting/spec/models/cost_query/walker_spec.rb b/modules/reporting/spec/models/cost_query/walker_spec.rb index c17267f71c..1775eadd65 100644 --- a/modules/reporting/spec/models/cost_query/walker_spec.rb +++ b/modules/reporting/spec/models/cost_query/walker_spec.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper") describe CostQuery, type: :model, reporting_query_helper: true do minimal_query @@ -40,12 +40,12 @@ describe CostQuery, type: :model, reporting_query_helper: true do describe Report::Transformer do it "walks down row_first" do - @query.group_by :work_package_id - @query.column :tweek - @query.row :project_id - @query.row :user_id + query.group_by :work_package_id + query.column :tweek + query.row :project_id + query.row :user_id - result = @query.transformer.row_first.values.first + result = query.transformer.row_first.values.first %i[user_id project_id tweek].each do |field| expect(result.fields).to include(field) result = result.values.first @@ -53,12 +53,12 @@ describe CostQuery, type: :model, reporting_query_helper: true do end it "walks down column_first" do - @query.group_by :work_package_id - @query.column :tweek - @query.row :project_id - @query.row :user_id + query.group_by :work_package_id + query.column :tweek + query.row :project_id + query.row :user_id - result = @query.transformer.column_first.values.first + result = query.transformer.column_first.values.first %i[tweek work_package_id].each do |field| expect(result.fields).to include(field) result = result.values.first diff --git a/modules/reporting/spec/support/query_helper.rb b/modules/reporting/spec/support/query_helper.rb index adf0ca804e..30ead1918e 100644 --- a/modules/reporting/spec/support/query_helper.rb +++ b/modules/reporting/spec/support/query_helper.rb @@ -32,9 +32,10 @@ require 'cost_query/operator' module OpenProject::Reporting module QueryHelper def minimal_query + let(:query) { CostQuery.new } + before do - @query = CostQuery.new - @query.send(:minimal_chain!) + query.send(:minimal_chain!) end end end diff --git a/modules/storages/app/services/projects/copy/file_links_dependent_service.rb b/modules/storages/app/services/projects/copy/file_links_dependent_service.rb index 976cb97412..4d763ddf2b 100644 --- a/modules/storages/app/services/projects/copy/file_links_dependent_service.rb +++ b/modules/storages/app/services/projects/copy/file_links_dependent_service.rb @@ -29,7 +29,7 @@ module Projects::Copy class FileLinksDependentService < ::Copy::Dependency def self.human_name - I18n.t(:label_work_package_file_link_plural) + I18n.t(:'projects.copy.work_package_file_links') end def source_count diff --git a/modules/team_planner/config/locales/crowdin/js-af.yml b/modules/team_planner/config/locales/crowdin/js-af.yml index 10ba5c56c9..6cbf7a86ca 100644 --- a/modules/team_planner/config/locales/crowdin/js-af.yml +++ b/modules/team_planner/config/locales/crowdin/js-af.yml @@ -15,6 +15,7 @@ af: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-ar.yml b/modules/team_planner/config/locales/crowdin/js-ar.yml index f5f7e3e742..e32ead1f91 100644 --- a/modules/team_planner/config/locales/crowdin/js-ar.yml +++ b/modules/team_planner/config/locales/crowdin/js-ar.yml @@ -15,6 +15,7 @@ ar: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-az.yml b/modules/team_planner/config/locales/crowdin/js-az.yml index 5324a25a8a..fe796c8d30 100644 --- a/modules/team_planner/config/locales/crowdin/js-az.yml +++ b/modules/team_planner/config/locales/crowdin/js-az.yml @@ -15,6 +15,7 @@ az: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-bg.yml b/modules/team_planner/config/locales/crowdin/js-bg.yml index 20d0e1fb64..aee5f67c05 100644 --- a/modules/team_planner/config/locales/crowdin/js-bg.yml +++ b/modules/team_planner/config/locales/crowdin/js-bg.yml @@ -15,6 +15,7 @@ bg: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-ca.yml b/modules/team_planner/config/locales/crowdin/js-ca.yml index 8812e35929..7a30fa90e6 100644 --- a/modules/team_planner/config/locales/crowdin/js-ca.yml +++ b/modules/team_planner/config/locales/crowdin/js-ca.yml @@ -15,6 +15,7 @@ ca: today: 'Avui' drag_here_to_remove: 'Arrossega aquí per eliminar l''usuari assignat i les dates d''inici i final.' cannot_drag_here: 'No s''ha pogut eliminar el paquet de treball a causa dels permisos o les restriccions d''edició.' + cannot_drag_to_non_working_day: 'Aquest paquet de treball no pot començar o acabar en un dia no laborable.' quick_add: empty_state: 'Utilitza el camp de cerca per trobar els paquets de treball i arrossegar-los dins del planificador d''equips i assignar-los a algú i definir les dates d''inici i final. ' search_placeholder: 'Cerca...' diff --git a/modules/team_planner/config/locales/crowdin/js-ckb-IR.yml b/modules/team_planner/config/locales/crowdin/js-ckb-IR.yml index 46a9ceecfd..58a59d5617 100644 --- a/modules/team_planner/config/locales/crowdin/js-ckb-IR.yml +++ b/modules/team_planner/config/locales/crowdin/js-ckb-IR.yml @@ -15,6 +15,7 @@ ckb-IR: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-cs.yml b/modules/team_planner/config/locales/crowdin/js-cs.yml index be72352ade..dde4ffd951 100644 --- a/modules/team_planner/config/locales/crowdin/js-cs.yml +++ b/modules/team_planner/config/locales/crowdin/js-cs.yml @@ -15,6 +15,7 @@ cs: today: 'Dnes' drag_here_to_remove: 'Přetažením sem odstraníte pověřenou osobu a datum začátku a ukončení.' cannot_drag_here: 'Pracovní balíček nelze odstranit kvůli oprávněním nebo úpravám omezení.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Použijte vyhledávací pole pro nalezení pracovních balíčků a přetáhněte je do plánovače, aby někomu přiřadil a definoval datum začátku a konce.' search_placeholder: 'Hledat...' diff --git a/modules/team_planner/config/locales/crowdin/js-da.yml b/modules/team_planner/config/locales/crowdin/js-da.yml index b592ca8b91..e1758267c1 100644 --- a/modules/team_planner/config/locales/crowdin/js-da.yml +++ b/modules/team_planner/config/locales/crowdin/js-da.yml @@ -15,6 +15,7 @@ da: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-de.yml b/modules/team_planner/config/locales/crowdin/js-de.yml index 991a111bd5..52c3a9613f 100644 --- a/modules/team_planner/config/locales/crowdin/js-de.yml +++ b/modules/team_planner/config/locales/crowdin/js-de.yml @@ -3,7 +3,7 @@ de: js: team_planner: add_existing: 'Bestehende hinzufügen' - add_existing_title: 'Add existing work packages' + add_existing_title: 'Vorhandenes Arbeitspaket hinzufügen' create_label: 'Team planner' create_title: 'Create new team planner' unsaved_title: 'Unbenannter Teamplaner' @@ -15,6 +15,7 @@ de: today: 'Heute' drag_here_to_remove: 'Hierher ziehen, um den zugewiesenen Benutzer zu entfernen und die Start- und Enddaten zu löschen.' cannot_drag_here: 'Das Arbeitspaket kann aufgrund von Berechtigungen oder Beschränkungen nicht entfernt werden.' + cannot_drag_to_non_working_day: 'Dieses Arbeitspaket kann nicht an einem Nicht-Arbeitstag starten/fertig werden.' quick_add: empty_state: 'Benutzen Sie das Suchfeld, um Arbeitspakete zu finden und ziehen Sie sie in den Planer, um sie Nutzern zuzuweisen und Start- und Enddaten zu setzen.' search_placeholder: 'Suchen...' diff --git a/modules/team_planner/config/locales/crowdin/js-el.yml b/modules/team_planner/config/locales/crowdin/js-el.yml index 3391071b9c..7429b9032e 100644 --- a/modules/team_planner/config/locales/crowdin/js-el.yml +++ b/modules/team_planner/config/locales/crowdin/js-el.yml @@ -15,6 +15,7 @@ el: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-eo.yml b/modules/team_planner/config/locales/crowdin/js-eo.yml index c8e1c8c943..b222740245 100644 --- a/modules/team_planner/config/locales/crowdin/js-eo.yml +++ b/modules/team_planner/config/locales/crowdin/js-eo.yml @@ -15,6 +15,7 @@ eo: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-es.yml b/modules/team_planner/config/locales/crowdin/js-es.yml index 8b90050b42..4ca40fa31a 100644 --- a/modules/team_planner/config/locales/crowdin/js-es.yml +++ b/modules/team_planner/config/locales/crowdin/js-es.yml @@ -15,6 +15,7 @@ es: today: 'Hoy' drag_here_to_remove: 'Arrastre aquí para quitar el usuario asignado y las fechas de inicio y finalización.' cannot_drag_here: 'No se puede quitar el paquete de trabajo debido a restricciones de edición o permisos.' + cannot_drag_to_non_working_day: 'Este paquete de trabajo no se puede iniciar o terminar en un día no laborable.' quick_add: empty_state: 'Use el campo de búsqueda para buscar paquetes de trabajo y arrástrelos hasta el planificador para asignarlos a un usuario y definir fechas de inicio y finalización.' search_placeholder: 'Buscar…' diff --git a/modules/team_planner/config/locales/crowdin/js-et.yml b/modules/team_planner/config/locales/crowdin/js-et.yml index 6583d3a7ea..065a5a475f 100644 --- a/modules/team_planner/config/locales/crowdin/js-et.yml +++ b/modules/team_planner/config/locales/crowdin/js-et.yml @@ -15,6 +15,7 @@ et: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-fa.yml b/modules/team_planner/config/locales/crowdin/js-fa.yml index 330166e43e..552a36510b 100644 --- a/modules/team_planner/config/locales/crowdin/js-fa.yml +++ b/modules/team_planner/config/locales/crowdin/js-fa.yml @@ -15,6 +15,7 @@ fa: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-fi.yml b/modules/team_planner/config/locales/crowdin/js-fi.yml index f2e556d88e..4f07f77e59 100644 --- a/modules/team_planner/config/locales/crowdin/js-fi.yml +++ b/modules/team_planner/config/locales/crowdin/js-fi.yml @@ -15,6 +15,7 @@ fi: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-fil.yml b/modules/team_planner/config/locales/crowdin/js-fil.yml index a9f2377bad..bfae730415 100644 --- a/modules/team_planner/config/locales/crowdin/js-fil.yml +++ b/modules/team_planner/config/locales/crowdin/js-fil.yml @@ -15,6 +15,7 @@ fil: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-fr.yml b/modules/team_planner/config/locales/crowdin/js-fr.yml index cc06758d3b..6233f67177 100644 --- a/modules/team_planner/config/locales/crowdin/js-fr.yml +++ b/modules/team_planner/config/locales/crowdin/js-fr.yml @@ -15,6 +15,7 @@ fr: today: 'Aujourd''hui' drag_here_to_remove: 'Faites glisser ici pour supprimer le responsable et les dates de début et de fin.' cannot_drag_here: 'Impossible de supprimer le lot de travaux en raison des autorisations ou des restrictions d''édition.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Utilisez le champ de recherche pour trouver des lots de travaux et faites-les glisser vers le planificateur pour l''assigner à quelqu''un et définir des dates de début et de fin.' search_placeholder: 'Rechercher...' diff --git a/modules/team_planner/config/locales/crowdin/js-he.yml b/modules/team_planner/config/locales/crowdin/js-he.yml index 1bb7d785c9..1553698ea1 100644 --- a/modules/team_planner/config/locales/crowdin/js-he.yml +++ b/modules/team_planner/config/locales/crowdin/js-he.yml @@ -15,6 +15,7 @@ he: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-hi.yml b/modules/team_planner/config/locales/crowdin/js-hi.yml index 47cade6208..c1335c4e21 100644 --- a/modules/team_planner/config/locales/crowdin/js-hi.yml +++ b/modules/team_planner/config/locales/crowdin/js-hi.yml @@ -15,6 +15,7 @@ hi: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-hr.yml b/modules/team_planner/config/locales/crowdin/js-hr.yml index 02aa6fdfee..851b7d5a20 100644 --- a/modules/team_planner/config/locales/crowdin/js-hr.yml +++ b/modules/team_planner/config/locales/crowdin/js-hr.yml @@ -15,6 +15,7 @@ hr: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-hu.yml b/modules/team_planner/config/locales/crowdin/js-hu.yml index d2daa4e54e..ef71ab87a5 100644 --- a/modules/team_planner/config/locales/crowdin/js-hu.yml +++ b/modules/team_planner/config/locales/crowdin/js-hu.yml @@ -15,6 +15,7 @@ hu: today: 'Ma' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Keresés...' diff --git a/modules/team_planner/config/locales/crowdin/js-id.yml b/modules/team_planner/config/locales/crowdin/js-id.yml index 4c9dfcc21f..fa027f8954 100644 --- a/modules/team_planner/config/locales/crowdin/js-id.yml +++ b/modules/team_planner/config/locales/crowdin/js-id.yml @@ -15,6 +15,7 @@ id: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-it.yml b/modules/team_planner/config/locales/crowdin/js-it.yml index d8a9e52328..56c5d893dc 100644 --- a/modules/team_planner/config/locales/crowdin/js-it.yml +++ b/modules/team_planner/config/locales/crowdin/js-it.yml @@ -15,6 +15,7 @@ it: today: 'Oggi' drag_here_to_remove: 'Trascina qui per rimuovere l''assegnatario e le date di inizio e fine.' cannot_drag_here: 'Impossibile rimuovere la macro-attività a causa di permessi o restrizioni di modifica.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Utilizzare il campo di ricerca per trovare macro-attività e trascinarle nel planner per assegnarle a qualcuno e definire le date di inizio e di fine.' search_placeholder: 'Cerca...' diff --git a/modules/team_planner/config/locales/crowdin/js-ja.yml b/modules/team_planner/config/locales/crowdin/js-ja.yml index 593b186646..43aff626da 100644 --- a/modules/team_planner/config/locales/crowdin/js-ja.yml +++ b/modules/team_planner/config/locales/crowdin/js-ja.yml @@ -15,6 +15,7 @@ ja: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-ko.yml b/modules/team_planner/config/locales/crowdin/js-ko.yml index 1eb97dcc35..1504d47c60 100644 --- a/modules/team_planner/config/locales/crowdin/js-ko.yml +++ b/modules/team_planner/config/locales/crowdin/js-ko.yml @@ -15,6 +15,7 @@ ko: today: '오늘' drag_here_to_remove: '담당자와 시작 날짜 및 종료 날짜를 제거하려면 여기로 드래그하세요.' cannot_drag_here: '권한 또는 편집 제한으로 인해 작업 패키지를 제거할 수 없습니다.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: '검색 필드를 사용하여 작업 패키지를 찾고 플래너로 드래그하여 누군가에게 할당하고 시작 날짜 및 종료 날짜를 정의하세요.' search_placeholder: '검색...' diff --git a/modules/team_planner/config/locales/crowdin/js-lol.yml b/modules/team_planner/config/locales/crowdin/js-lol.yml index 0f95249a85..af9d4e5280 100644 --- a/modules/team_planner/config/locales/crowdin/js-lol.yml +++ b/modules/team_planner/config/locales/crowdin/js-lol.yml @@ -15,6 +15,7 @@ lol: today: 'crwdns750842:0crwdne750842:0' drag_here_to_remove: 'crwdns729816:0crwdne729816:0' cannot_drag_here: 'crwdns729818:0crwdne729818:0' + cannot_drag_to_non_working_day: 'crwdns834618:0crwdne834618:0' quick_add: empty_state: 'crwdns653319:0crwdne653319:0' search_placeholder: 'crwdns653321:0crwdne653321:0' diff --git a/modules/team_planner/config/locales/crowdin/js-lt.yml b/modules/team_planner/config/locales/crowdin/js-lt.yml index a2dfc820e7..5b6fcff2f2 100644 --- a/modules/team_planner/config/locales/crowdin/js-lt.yml +++ b/modules/team_planner/config/locales/crowdin/js-lt.yml @@ -15,6 +15,7 @@ lt: today: 'Šiandien' drag_here_to_remove: 'Pertempkite čia, kad išimtumėte paskirtąjį ir pradžios bei pabaigos datas.' cannot_drag_here: 'Darbo paketo negalima pašalinti dėl teisių ar keitimo apribojimų.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Naudokite paieškos lauką darbo paketų paieškai, nutempkite juos į planą, kad priskirtumėte kažkam ir nustatytumėte pradžios bei pabaigos datas.' search_placeholder: 'Ieškoti...' diff --git a/modules/team_planner/config/locales/crowdin/js-lv.yml b/modules/team_planner/config/locales/crowdin/js-lv.yml index ea8fbba35f..73b920ab12 100644 --- a/modules/team_planner/config/locales/crowdin/js-lv.yml +++ b/modules/team_planner/config/locales/crowdin/js-lv.yml @@ -15,6 +15,7 @@ lv: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-ne.yml b/modules/team_planner/config/locales/crowdin/js-ne.yml index 7ab95c5514..53cfdb5e35 100644 --- a/modules/team_planner/config/locales/crowdin/js-ne.yml +++ b/modules/team_planner/config/locales/crowdin/js-ne.yml @@ -15,6 +15,7 @@ ne: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-nl.yml b/modules/team_planner/config/locales/crowdin/js-nl.yml index c2a367df6b..dd4f01d4bf 100644 --- a/modules/team_planner/config/locales/crowdin/js-nl.yml +++ b/modules/team_planner/config/locales/crowdin/js-nl.yml @@ -3,9 +3,9 @@ nl: js: team_planner: add_existing: 'Bestaande toevoegen' - add_existing_title: 'Add existing work packages' + add_existing_title: 'Bestaande werkpakketten toevoegen' create_label: 'Team planner' - create_title: 'Create new team planner' + create_title: 'Maak een nieuwe teamplanner' unsaved_title: 'Naamloze teamplanner' no_data: 'Voeg taakontvangers toe om uw team planner op te zetten.' add_assignee: 'Voeg verantwoordelijke toe' @@ -15,6 +15,7 @@ nl: today: 'Vandaag' drag_here_to_remove: 'Sleep hierheen om taakontvanger en start- en einddata te verwijderen.' cannot_drag_here: 'Kan het werkpakket niet verwijderen vanwege machtigingen of bewerkingsbeperkingen.' + cannot_drag_to_non_working_day: 'Dit werkpakket kan niet starten/eindigen op een niet-werkdag.' quick_add: empty_state: 'Gebruik het zoekveld om werkpakketten te vinden en sleep ze naar de planner om toe te wijzen aan iemand en begin- en einddatums te definiëren.' search_placeholder: 'Zoeken ...' diff --git a/modules/team_planner/config/locales/crowdin/js-no.yml b/modules/team_planner/config/locales/crowdin/js-no.yml index fda78546e5..e497865698 100644 --- a/modules/team_planner/config/locales/crowdin/js-no.yml +++ b/modules/team_planner/config/locales/crowdin/js-no.yml @@ -15,6 +15,7 @@ today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-pl.yml b/modules/team_planner/config/locales/crowdin/js-pl.yml index b2efe61f77..22af3f00e3 100644 --- a/modules/team_planner/config/locales/crowdin/js-pl.yml +++ b/modules/team_planner/config/locales/crowdin/js-pl.yml @@ -15,6 +15,7 @@ pl: today: 'Dzisiaj' drag_here_to_remove: 'Przeciągnij tutaj, aby usunąć przypisaną osobę oraz daty rozpoczęcia i zakończenia.' cannot_drag_here: 'Nie można usunąć pakietu roboczego ze względu na uprawnienia lub ograniczenia edycji.' + cannot_drag_to_non_working_day: 'Ten pakiet roboczy nie może się rozpocząć/skończyć w dniu wolnym od pracy.' quick_add: empty_state: 'Użyj pola wyszukiwania w celu znalezienia pakietów roboczych i przeciągnięcia ich do planisty, aby przypisać je do kogoś i zdefiniować daty rozpoczęcia i zakończenia.' search_placeholder: 'Wyszukaj...' diff --git a/modules/team_planner/config/locales/crowdin/js-pt.yml b/modules/team_planner/config/locales/crowdin/js-pt.yml index 85caf217fc..d7712fd99f 100644 --- a/modules/team_planner/config/locales/crowdin/js-pt.yml +++ b/modules/team_planner/config/locales/crowdin/js-pt.yml @@ -15,6 +15,7 @@ pt: today: 'Hoje' drag_here_to_remove: 'Arraste aqui para remover o responsável e as datas de início e término.' cannot_drag_here: 'Não é possível remover o pacote de trabalho devido a permissões ou restrições de edição.' + cannot_drag_to_non_working_day: 'Esse pacote de trabalho não pode começar/terminar em um fim de semana' quick_add: empty_state: 'Use o campo de pesquisa para encontrar pacotes de trabalho e arraste-os para o planejador para atribuí-los para alguém e definir as datas de início e término.' search_placeholder: 'Pesquisar...' diff --git a/modules/team_planner/config/locales/crowdin/js-ro.yml b/modules/team_planner/config/locales/crowdin/js-ro.yml index 440ced5810..accda80ab9 100644 --- a/modules/team_planner/config/locales/crowdin/js-ro.yml +++ b/modules/team_planner/config/locales/crowdin/js-ro.yml @@ -15,6 +15,7 @@ ro: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-ru.yml b/modules/team_planner/config/locales/crowdin/js-ru.yml index a38583abd7..114805f64a 100644 --- a/modules/team_planner/config/locales/crowdin/js-ru.yml +++ b/modules/team_planner/config/locales/crowdin/js-ru.yml @@ -15,6 +15,7 @@ ru: today: 'Сегодня' drag_here_to_remove: 'Перетащите сюда, чтобы удалить ответственного, а также даты начала и окончания.' cannot_drag_here: 'Невозможно удалить пакет работ из-за разрешений или ограничений редактирования.' + cannot_drag_to_non_working_day: 'Этот пакет работ не может начать/закончить работу в нерабочий день.' quick_add: empty_state: 'Используйте поле поиска для поиска пакетов работ и перетаскивания их в планировщик для назначения их кому-либо и определения даты начала и окончания.' search_placeholder: 'Поиск...' diff --git a/modules/team_planner/config/locales/crowdin/js-rw.yml b/modules/team_planner/config/locales/crowdin/js-rw.yml index f8698b9613..e0a44bbc90 100644 --- a/modules/team_planner/config/locales/crowdin/js-rw.yml +++ b/modules/team_planner/config/locales/crowdin/js-rw.yml @@ -15,6 +15,7 @@ rw: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-si.yml b/modules/team_planner/config/locales/crowdin/js-si.yml index cbd6473019..52deb11983 100644 --- a/modules/team_planner/config/locales/crowdin/js-si.yml +++ b/modules/team_planner/config/locales/crowdin/js-si.yml @@ -15,6 +15,7 @@ si: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-sk.yml b/modules/team_planner/config/locales/crowdin/js-sk.yml index 677376a09b..35dee20baf 100644 --- a/modules/team_planner/config/locales/crowdin/js-sk.yml +++ b/modules/team_planner/config/locales/crowdin/js-sk.yml @@ -15,6 +15,7 @@ sk: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-sl.yml b/modules/team_planner/config/locales/crowdin/js-sl.yml index 3bb9995fb6..bacd8a6906 100644 --- a/modules/team_planner/config/locales/crowdin/js-sl.yml +++ b/modules/team_planner/config/locales/crowdin/js-sl.yml @@ -15,6 +15,7 @@ sl: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-sv.yml b/modules/team_planner/config/locales/crowdin/js-sv.yml index 35390a97d6..3b5a8d24fc 100644 --- a/modules/team_planner/config/locales/crowdin/js-sv.yml +++ b/modules/team_planner/config/locales/crowdin/js-sv.yml @@ -15,6 +15,7 @@ sv: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-th.yml b/modules/team_planner/config/locales/crowdin/js-th.yml index e5d46fd0a2..c512e2fa33 100644 --- a/modules/team_planner/config/locales/crowdin/js-th.yml +++ b/modules/team_planner/config/locales/crowdin/js-th.yml @@ -15,6 +15,7 @@ th: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-tr.yml b/modules/team_planner/config/locales/crowdin/js-tr.yml index b2c22216bd..a7ba5aafa1 100644 --- a/modules/team_planner/config/locales/crowdin/js-tr.yml +++ b/modules/team_planner/config/locales/crowdin/js-tr.yml @@ -15,6 +15,7 @@ tr: today: 'Bugün' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Ara...' diff --git a/modules/team_planner/config/locales/crowdin/js-uk.yml b/modules/team_planner/config/locales/crowdin/js-uk.yml index 8a96974d4e..bddd1fe67a 100644 --- a/modules/team_planner/config/locales/crowdin/js-uk.yml +++ b/modules/team_planner/config/locales/crowdin/js-uk.yml @@ -15,6 +15,7 @@ uk: today: 'Сьогодні' drag_here_to_remove: 'Перетягніть сюди, щоб вилучити виконавця, а також дати початку й закінчення.' cannot_drag_here: 'Не вдалося вилучити пакет робіт через дозволи або обмеження на редагування.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Скористайтеся полем пошуку, щоб знайти пакети робіт, і перетягніть їх у планувальник, щоб призначити комусь, а також визначити дати початку й закінчення завдання.' search_placeholder: 'Пошук...' diff --git a/modules/team_planner/config/locales/crowdin/js-vi.yml b/modules/team_planner/config/locales/crowdin/js-vi.yml index 74f1ac27b4..aacd8f9951 100644 --- a/modules/team_planner/config/locales/crowdin/js-vi.yml +++ b/modules/team_planner/config/locales/crowdin/js-vi.yml @@ -15,6 +15,7 @@ vi: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/crowdin/js-zh-TW.yml b/modules/team_planner/config/locales/crowdin/js-zh-TW.yml index 56d85fdf3e..0a4bf688c9 100644 --- a/modules/team_planner/config/locales/crowdin/js-zh-TW.yml +++ b/modules/team_planner/config/locales/crowdin/js-zh-TW.yml @@ -15,6 +15,7 @@ zh-TW: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' search_placeholder: 'Search...' diff --git a/modules/team_planner/config/locales/js-en.yml b/modules/team_planner/config/locales/js-en.yml index fba6eb5e69..4dbc566d21 100644 --- a/modules/team_planner/config/locales/js-en.yml +++ b/modules/team_planner/config/locales/js-en.yml @@ -15,6 +15,7 @@ en: today: 'Today' drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.' + cannot_drag_to_non_working_day : 'This work package cannot start/finish on a non-working day.' quick_add: empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' diff --git a/modules/webhooks/app/cells/views/response_body.erb b/modules/webhooks/app/cells/views/response_body.erb index b5ad03ee9f..1c43ed6db7 100644 --- a/modules/webhooks/app/cells/views/response_body.erb +++ b/modules/webhooks/app/cells/views/response_body.erb @@ -8,14 +8,14 @@